001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:grenard@nuxeo.com">Guillaume Renard</a>
018 *
019 */
020
021package org.nuxeo.ecm.web.resources.wro.processor;
022
023import java.io.ByteArrayInputStream;
024import java.io.IOException;
025import java.io.InputStreamReader;
026import java.io.Reader;
027import java.io.Writer;
028import java.util.Arrays;
029import java.util.List;
030import org.apache.commons.io.IOUtils;
031import org.apache.commons.lang.ArrayUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.runtime.api.Framework;
036import org.nuxeo.theme.styling.service.ThemeStylingService;
037import org.nuxeo.theme.styling.service.descriptors.FlavorDescriptor;
038import org.nuxeo.theme.styling.service.descriptors.SassImport;
039import org.w3c.css.sac.InputSource;
040
041import com.vaadin.sass.internal.ScssStylesheet;
042import com.vaadin.sass.internal.handler.SCSSDocumentHandlerImpl;
043import com.vaadin.sass.internal.handler.SCSSErrorHandler;
044import com.vaadin.sass.internal.parser.ParseException;
045import com.vaadin.sass.internal.parser.Parser;
046import com.vaadin.sass.internal.parser.SCSSParseException;
047import com.vaadin.sass.internal.tree.Node;
048
049import ro.isdc.wro.WroRuntimeException;
050import ro.isdc.wro.model.resource.Resource;
051import ro.isdc.wro.model.resource.ResourceType;
052import ro.isdc.wro.model.resource.SupportedResourceType;
053
054/**
055 * Use Sass css processor to replace variables, mixin, etc. according to a given flavor.
056 *
057 * @since 7.4
058 */
059@SupportedResourceType(ResourceType.CSS)
060public class SassCssFlavorProcessor extends AbstractFlavorProcessor {
061
062    private static final Log log = LogFactory.getLog(SassCssFlavorProcessor.class);
063
064    public static final String ALIAS = "sassCss";
065
066    @Override
067    public void process(final Resource resource, final Reader reader, final Writer writer, String flavorName)
068            throws IOException {
069        if (isEnabled(resource)) {
070            Reader finalReader = null;
071            try {
072                String varContents = "";
073                if (flavorName != null) {
074                    ThemeStylingService s = Framework.getService(ThemeStylingService.class);
075                    FlavorDescriptor fd = s.getFlavor(flavorName);
076                    if (fd != null) {
077                        List<SassImport> sassVars = fd.getSassImports();
078                        if (sassVars != null) {
079                            for (SassImport var : sassVars) {
080                                varContents += var.getContent();
081                            }
082                        }
083                    }
084                }
085
086                InputSource source = null;
087                if (StringUtils.isNoneBlank(varContents)) {
088                    byte[] varBytes = varContents.getBytes();
089                    byte[] initalBytes = IOUtils.toByteArray(reader);
090                    reader.close();
091                    byte[] finalBytes = ArrayUtils.addAll(varBytes, initalBytes);
092                    finalReader = new InputStreamReader(new ByteArrayInputStream(finalBytes));
093                } else {
094                    finalReader = reader;
095                }
096                source = new InputSource(finalReader);
097                source.setEncoding(getEncoding());
098                SCSSDocumentHandlerImpl scssDocumentHandlerImpl = new SCSSDocumentHandlerImpl();
099                ScssStylesheet stylesheet = scssDocumentHandlerImpl.getStyleSheet();
100
101                Parser parser = new Parser();
102                parser.setErrorHandler(new SCSSErrorHandler());
103                parser.setDocumentHandler(scssDocumentHandlerImpl);
104
105                try {
106                    parser.parseStyleSheet(source);
107                } catch (ParseException e) {
108                    log.error("Error while parsing resource " + resource.getUri(), e);
109                    throw WroRuntimeException.wrap(new SCSSParseException(e, resource.getUri()));
110                }
111
112                stylesheet.setCharset(getEncoding());
113                stylesheet.addSourceUris(Arrays.asList(resource.getUri()));
114
115                stylesheet.compile();
116
117                StringBuilder string = new StringBuilder("");
118                String delimeter = "\n\n";
119                List<Node> children = stylesheet.getChildren();
120                if (children.size() > 0) {
121                    string.append(ScssStylesheet.PRINT_STRATEGY.build(children.get(0)));
122                }
123                if (children.size() > 1) {
124                    for (int i = 1; i < children.size(); i++) {
125                        String childString = ScssStylesheet.PRINT_STRATEGY.build(children.get(i));
126                        if (childString != null) {
127                            string.append(delimeter).append(childString);
128                        }
129                    }
130                }
131
132                String content = string.toString();
133
134                writer.write(content);
135                writer.flush();
136                if (finalReader != null) {
137                    finalReader.close();
138                }
139            } catch (final Exception e) {
140                log.error("Error while serving resource " + resource.getUri(), e);
141                throw WroRuntimeException.wrap(e);
142            } finally {
143                IOUtils.closeQuietly(finalReader);
144            }
145
146        } else {
147            IOUtils.copy(reader, writer);
148        }
149    }
150
151    @Override
152    public String getAlias() {
153        return ALIAS;
154    }
155
156}