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