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}