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.File;
019import java.io.FileInputStream;
020import java.io.FileWriter;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.Writer;
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<File> _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, File 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<File> 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<File> getCSSFiles ()
095    {
096        return _cssFiles;
097    }
098    
099    /**
100     * Add a CSS file
101     * @param cssFile The CSS file
102     */
103    public void addCSSFile (File cssFile)
104    {
105        for (File f : _cssFiles)
106        {
107            if (f.getPath().equals(cssFile.getPath()))
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(File tempDir, File modelDir, Object value, String lang) throws SkinParameterException
127    {
128        for (File file : _cssFiles)
129        {
130            String relPath = file.getAbsolutePath().substring(modelDir.getAbsolutePath().length() + 1);
131            File cssFile = new File (tempDir, relPath);
132            String string = cssFileToString(cssFile);
133            
134            Pattern pattern = getCSSPattern();
135            Matcher matcher = pattern.matcher(string);
136            
137            try (Writer writer = new FileWriter(cssFile))
138            {
139                int startIndex = 0;
140                while (matcher.find())
141                {
142                    int propertyStart = matcher.start(2);
143                    
144                    String beforeValue = string.substring(startIndex, propertyStart);
145                    String oldValue = string.substring(propertyStart, matcher.end(2));
146                    IOUtils.write(beforeValue, writer);
147
148                    IOUtils.write((String) value + (oldValue.endsWith(" ") ? " " : ""), writer);
149                    IOUtils.write(string.substring(matcher.end(2), matcher.end(3)), writer);
150                    
151                    startIndex = matcher.end(3);
152                }
153                
154                IOUtils.write(string.substring(startIndex), writer);
155            }
156            catch (IOException e)
157            {
158                throw new SkinParameterException ("Unable to apply css parameter '" + getId() + "'", e);
159            }
160        }
161    }
162    
163    /**
164     * Get the CSS pattern
165     * @return The CSS pattern
166     */
167    protected Pattern getCSSPattern ()
168    {
169        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); 
170    }
171    
172    /**
173     * Get the css file input stream as String
174     * @param cssFile The css file
175     * @return The css file content as String
176     * @throws SkinParameterException if the css file was not parsable
177     */
178    protected String cssFileToString (File cssFile) throws SkinParameterException
179    {
180        try (InputStream is = new FileInputStream(cssFile))
181        {
182            return org.apache.commons.io.IOUtils.toString(is);
183        }
184        catch (IOException e)
185        {
186            throw new SkinParameterException ("Unable to parse file '" + cssFile.getName() + "'", e);
187        }
188    }
189    
190    @Override
191    public void toSAX(ContentHandler contentHandler, String modelName) throws SAXException
192    {
193        AttributesImpl attrs = new AttributesImpl();
194        attrs.addCDATAAttribute("id", getId());
195        attrs.addCDATAAttribute("type", SkinParameterType.CSS.name().toLowerCase());
196        
197        XMLUtils.startElement(contentHandler, "parameter", attrs);
198        
199        getLabel().toSAX(contentHandler, "label");
200        getDescription().toSAX(contentHandler, "description");
201        XMLUtils.createElement(contentHandler, "property", getCSSProperty());
202        
203        XMLUtils.endElement(contentHandler, "parameter");
204    }
205    
206    @Override
207    public Map<String, Object> toJson(String modelName)
208    {
209        Map<String, Object> jsonObject = new HashMap<>();
210        
211        jsonObject.put("id", getId());
212        jsonObject.put("type", SkinParameterType.CSS.name().toLowerCase());
213        jsonObject.put("label", getLabel());
214        jsonObject.put("description", getDescription());
215        jsonObject.put("property", getCSSProperty());
216        
217        return jsonObject;
218    }
219    
220    @Override
221    public String getDefaultValue(SkinModel model)
222    {
223        return _defaultValue;
224    }
225    
226    @Override
227    public String getDefaultValue(SkinModel model, String lang)
228    {
229        return getDefaultValue(model);
230    }
231}