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 static java.nio.charset.StandardCharsets.UTF_8;
024
025import java.io.ByteArrayInputStream;
026import java.io.IOException;
027import java.io.InputStreamReader;
028import java.io.Reader;
029import java.io.Writer;
030import java.util.Collections;
031import java.util.List;
032
033import org.apache.commons.io.IOUtils;
034import org.apache.commons.lang3.ArrayUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.runtime.api.Framework;
039import org.nuxeo.theme.styling.service.ThemeStylingService;
040import org.nuxeo.theme.styling.service.descriptors.FlavorDescriptor;
041import org.nuxeo.theme.styling.service.descriptors.SassImport;
042import org.w3c.css.sac.InputSource;
043
044import com.vaadin.sass.internal.ScssStylesheet;
045import com.vaadin.sass.internal.handler.SCSSDocumentHandlerImpl;
046import com.vaadin.sass.internal.handler.SCSSErrorHandler;
047import com.vaadin.sass.internal.parser.ParseException;
048import com.vaadin.sass.internal.parser.Parser;
049import com.vaadin.sass.internal.parser.SCSSParseException;
050import com.vaadin.sass.internal.tree.Node;
051
052import ro.isdc.wro.WroRuntimeException;
053import ro.isdc.wro.model.resource.Resource;
054import ro.isdc.wro.model.resource.ResourceType;
055import ro.isdc.wro.model.resource.SupportedResourceType;
056
057/**
058 * Use Sass css processor to replace variables, mixin, etc. according to a given flavor.
059 *
060 * @since 7.4
061 */
062@SupportedResourceType(ResourceType.CSS)
063public class SassCssFlavorProcessor extends AbstractFlavorProcessor {
064
065    private static final Log log = LogFactory.getLog(SassCssFlavorProcessor.class);
066
067    public static final String ALIAS = "sassCss";
068
069    @Override
070    public void process(final Resource resource, final Reader reader, final Writer writer, String flavorName)
071            throws IOException {
072        if (isEnabled(resource)) {
073            Reader finalReader = null;
074            try {
075                StringBuilder varContentsBuidler = new StringBuilder();
076                if (flavorName != null) {
077                    ThemeStylingService s = Framework.getService(ThemeStylingService.class);
078                    FlavorDescriptor fd = s.getFlavor(flavorName);
079                    if (fd != null) {
080                        List<SassImport> sassVars = fd.getSassImports();
081                        if (sassVars != null) {
082                            for (SassImport var : sassVars) {
083                                varContentsBuidler.append(var.getContent());
084                            }
085                        }
086                    }
087                }
088                String varContents = varContentsBuidler.toString();
089
090                InputSource source;
091                if (StringUtils.isNoneBlank(varContents)) {
092                    byte[] varBytes = varContents.getBytes();
093                    byte[] initalBytes = IOUtils.toByteArray(reader, UTF_8);
094                    reader.close();
095                    byte[] finalBytes = ArrayUtils.addAll(varBytes, initalBytes);
096                    finalReader = new InputStreamReader(new ByteArrayInputStream(finalBytes));
097                } else {
098                    finalReader = reader;
099                }
100                source = new InputSource(finalReader);
101                source.setEncoding(getEncoding());
102                SCSSDocumentHandlerImpl scssDocumentHandlerImpl = new SCSSDocumentHandlerImpl();
103                ScssStylesheet stylesheet = scssDocumentHandlerImpl.getStyleSheet();
104
105                Parser parser = new Parser();
106                parser.setErrorHandler(new SCSSErrorHandler());
107                parser.setDocumentHandler(scssDocumentHandlerImpl);
108
109                try {
110                    parser.parseStyleSheet(source);
111                } catch (ParseException e) {
112                    log.error("Error while parsing resource " + resource.getUri(), e);
113                    throw WroRuntimeException.wrap(new SCSSParseException(e, resource.getUri()));
114                }
115
116                stylesheet.setCharset(getEncoding());
117                stylesheet.addSourceUris(Collections.singletonList(resource.getUri()));
118
119                stylesheet.compile();
120
121                StringBuilder string = new StringBuilder("");
122                String delimeter = "\n\n";
123                List<Node> children = stylesheet.getChildren();
124                if (children.size() > 0) {
125                    string.append(ScssStylesheet.PRINT_STRATEGY.build(children.get(0)));
126                }
127                if (children.size() > 1) {
128                    for (int i = 1; i < children.size(); i++) {
129                        String childString = ScssStylesheet.PRINT_STRATEGY.build(children.get(i));
130                        if (childString != null) {
131                            string.append(delimeter).append(childString);
132                        }
133                    }
134                }
135
136                String content = string.toString();
137
138                writer.write(content);
139                writer.flush();
140                if (finalReader != null) {
141                    finalReader.close();
142                }
143            } catch (final Exception e) {
144                log.error("Error while serving resource " + resource.getUri(), e);
145                throw WroRuntimeException.wrap(e);
146            } finally {
147                IOUtils.closeQuietly(finalReader);
148            }
149
150        } else {
151            IOUtils.copy(reader, writer);
152        }
153    }
154
155    @Override
156    public String getAlias() {
157        return ALIAS;
158    }
159
160}