001/*
002 *  Copyright 2011 Anyware Services
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 */
016package org.ametys.skinfactory.parameters;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.nio.charset.StandardCharsets;
022import java.nio.file.Files;
023import java.nio.file.Path;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.io.IOUtils;
034import org.xml.sax.ContentHandler;
035import org.xml.sax.SAXException;
036
037import org.ametys.runtime.i18n.I18nizableText;
038import org.ametys.web.skin.SkinModel;
039
040/**
041 * Implementation of {@link AbstractSkinParameter} for a CSS property
042 */
043public class CSSParameter extends AbstractSkinParameter
044{
045    private final List<Path> _cssFiles;
046    private final String _cssProperty;
047    private final String _defaultValue;
048    
049    /**
050     * Constructor
051     * @param id the unique id
052     * @param label the label
053     * @param description the description
054     * @param cssFile the css file
055     * @param cssProperty the css property
056     * @param defaultValue the default value
057     */
058    public CSSParameter(String id, I18nizableText label, I18nizableText description, Path cssFile, String cssProperty, String defaultValue)
059    {
060        super(id, label, description);
061        _cssFiles = new ArrayList<>();
062        _cssFiles.add(cssFile);
063        _cssProperty = cssProperty;
064        _defaultValue = defaultValue;
065    }
066    
067    /**
068     * Constructor
069     * @param id the unique id
070     * @param label the label
071     * @param description the description
072     * @param cssFiles the css files
073     * @param cssProperty the css property
074     * @param defaultValue the default value
075     */
076    public CSSParameter(String id, I18nizableText label, I18nizableText description, List<Path> cssFiles, String cssProperty, String defaultValue)
077    {
078        super(id, label, description);
079        _cssFiles = cssFiles;
080        _cssProperty = cssProperty;
081        _defaultValue = defaultValue;
082    }
083    
084    @Override
085    public SkinParameterType getType()
086    {
087        return SkinParameterType.CSS;
088    }
089    
090    /**
091     * Get the CSS files path
092     * @return the CSS files path
093     */
094    public List<Path> getCSSFiles ()
095    {
096        return _cssFiles;
097    }
098    
099    /**
100     * Add a CSS file
101     * @param cssFile The CSS file
102     */
103    public void addCSSFile (Path cssFile)
104    {
105        for (Path f : _cssFiles)
106        {
107            if (f.normalize().toString().equals(cssFile.normalize().toString()))
108            {
109                // The file is already registered
110                return;
111            }
112        }
113        _cssFiles.add(cssFile);
114    }
115    
116    /**
117     * Get the CSS property
118     * @return the CSS file path
119     */
120    public String getCSSProperty ()
121    {
122        return _cssProperty;
123    }
124    
125    @Override
126    public void apply(Path tempDir, Path modelDir, Object value, String lang) throws SkinParameterException
127    {
128        for (Path file : _cssFiles)
129        {
130            String relPath = modelDir.relativize(file).toString();
131            Path cssFile = tempDir.resolve(relPath);
132            String string = cssFileToString(cssFile);
133            
134            Pattern pattern = getCSSPattern();
135            Matcher matcher = pattern.matcher(string);
136            
137            StringBuffer sb = new StringBuffer();
138            int startIndex = 0;
139            while (matcher.find())
140            {
141                int propertyStart = matcher.start(2);
142                
143                String beforeValue = string.substring(startIndex, propertyStart);
144                String oldValue = string.substring(propertyStart, matcher.end(2));
145                sb.append(beforeValue);
146
147                sb.append((String) value + (oldValue.endsWith(" ") ? " " : ""));
148                sb.append(string.substring(matcher.end(2), matcher.end(3)));
149                
150                startIndex = matcher.end(3);
151            }
152            
153            sb.append(string.substring(startIndex));
154
155            try (OutputStream os = Files.newOutputStream(cssFile))
156            {
157                IOUtils.write(sb.toString(), os, StandardCharsets.UTF_8);
158            }
159            catch (IOException e)
160            {
161                throw new SkinParameterException ("Unable to apply css parameter '" + getId() + "'", e);
162            }
163        }
164    }
165    
166    /**
167     * Get the CSS pattern
168     * @return The CSS pattern
169     */
170    protected Pattern getCSSPattern ()
171    {
172        return  Pattern.compile("\\s*([^,:\\s]*)\\s*:\\s*([^:;!]*)\\s*(?:!important)?\\s*\\/\\*\\s*AMETYS\\s*\\(\\s*\"(" + getId() + ")\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*\\*\\/\\s*;\\s*?", Pattern.MULTILINE); 
173    }
174    
175    /**
176     * Get the css file input stream as String
177     * @param cssFile The css file
178     * @return The css file content as String
179     * @throws SkinParameterException if the css file was not parsable
180     */
181    protected String cssFileToString (Path cssFile) throws SkinParameterException
182    {
183        try (InputStream is = Files.newInputStream(cssFile))
184        {
185            return org.apache.commons.io.IOUtils.toString(is, StandardCharsets.UTF_8);
186        }
187        catch (IOException e)
188        {
189            throw new SkinParameterException ("Unable to parse file '" + cssFile.getFileName().toString() + "'", e);
190        }
191    }
192    
193    @Override
194    public void toSAX(ContentHandler contentHandler, String modelName) throws SAXException
195    {
196        AttributesImpl attrs = new AttributesImpl();
197        attrs.addCDATAAttribute("id", getId());
198        attrs.addCDATAAttribute("type", SkinParameterType.CSS.name().toLowerCase());
199        
200        XMLUtils.startElement(contentHandler, "parameter", attrs);
201        
202        getLabel().toSAX(contentHandler, "label");
203        getDescription().toSAX(contentHandler, "description");
204        XMLUtils.createElement(contentHandler, "property", getCSSProperty());
205        
206        XMLUtils.endElement(contentHandler, "parameter");
207    }
208    
209    @Override
210    public Map<String, Object> toJson(String modelName)
211    {
212        Map<String, Object> jsonObject = new HashMap<>();
213        
214        jsonObject.put("id", getId());
215        jsonObject.put("type", SkinParameterType.CSS.name().toLowerCase());
216        jsonObject.put("label", getLabel());
217        jsonObject.put("description", getDescription());
218        jsonObject.put("property", getCSSProperty());
219        
220        return jsonObject;
221    }
222    
223    @Override
224    public String getDefaultValue(SkinModel model)
225    {
226        return _defaultValue;
227    }
228    
229    @Override
230    public String getDefaultValue(SkinModel model, String lang)
231    {
232        return getDefaultValue(model);
233    }
234}