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