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}