001/*
002 * (C) Copyright 2006-2014 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 *     Jean-Marc Orliaguet, Chalmers
018 *
019 */
020
021package org.nuxeo.theme;
022
023import java.io.ByteArrayOutputStream;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.OutputStream;
028import java.io.StringReader;
029import java.io.StringWriter;
030import java.net.URL;
031import java.net.URLConnection;
032import java.util.ArrayList;
033import java.util.Iterator;
034import java.util.List;
035import java.util.Properties;
036import java.util.regex.Matcher;
037import java.util.regex.Pattern;
038
039import org.apache.commons.csv.CSVFormat;
040import org.apache.commons.csv.CSVParser;
041import org.apache.commons.csv.CSVPrinter;
042import org.apache.commons.csv.CSVRecord;
043import org.apache.commons.csv.QuoteMode;
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046
047import org.nuxeo.common.utils.FileUtils;
048import org.nuxeo.theme.formats.styles.Style;
049
050import com.phloc.css.CCSS;
051import com.phloc.css.ECSSVersion;
052import com.phloc.css.ICSSWriterSettings;
053import com.phloc.css.decl.CSSDeclaration;
054import com.phloc.css.decl.CSSSelector;
055import com.phloc.css.decl.CSSStyleRule;
056import com.phloc.css.decl.CascadingStyleSheet;
057import com.phloc.css.reader.CSSReader;
058import com.phloc.css.writer.CSSWriterSettings;
059
060public final class Utils {
061
062    private static final Log log = LogFactory.getLog(Utils.class);
063
064    private static final String EMPTY_CSS_SELECTOR = "EMPTY";
065
066    private static final Pattern emptyCssSelectorPattern = Pattern.compile("(.*?)\\{(.*?)\\}", Pattern.DOTALL);
067
068    private Utils() {
069        // This class is not supposed to be instantiated.
070    }
071
072    public static String listToCsv(List<String> list) {
073        StringWriter sw = new StringWriter();
074        try (CSVPrinter writer = new CSVPrinter(sw, CSVFormat.DEFAULT.withDelimiter(',').withQuoteMode(QuoteMode.ALL))) {
075            writer.printRecord(list);
076        } catch (IOException e) {
077            log.error(e.getMessage(), e);
078        }
079        return sw.toString();
080    }
081
082    public static List<String> csvToList(String str) throws IOException {
083        if ("".equals(str) || str == null) {
084            return new ArrayList<>();
085        }
086        StringReader sr = new StringReader(str);
087        try (CSVParser reader = new CSVParser(sr, CSVFormat.DEFAULT.withDelimiter(','))) {
088            Iterator<CSVRecord> iterator = reader.iterator();
089            if (!iterator.hasNext()) {
090                return new ArrayList<>();
091            } else {
092                CSVRecord nextRecord = iterator.next();
093                List<String> result = new ArrayList<>(nextRecord.size());
094                for (String value : nextRecord) {
095                    result.add(value);
096                }
097                return result;
098            }
099        }
100    }
101
102    public static boolean contains(final String[] array, final String value) {
103        for (String s : array) {
104            if (s.equals(value)) {
105                return true;
106            }
107        }
108        return false;
109    }
110
111    public static String cleanUp(String text) {
112        return text.replaceAll("\n", " ").replaceAll("\\t+", " ").replaceAll("\\s+", " ").trim();
113    }
114
115    public static byte[] readResourceAsBytes(final String path) throws IOException {
116        return readResource(path).toByteArray();
117    }
118
119    public static String readResourceAsString(final String path) throws IOException {
120        return readResource(path).toString();
121    }
122
123    private static ByteArrayOutputStream readResource(final String path) throws IOException {
124        InputStream is = null;
125        ByteArrayOutputStream os = null;
126        try {
127            is = ResourceResolver.getInstance().getResourceAsStream(path);
128            if (is == null) {
129                log.warn("Resource not found: " + path);
130            } else {
131                try {
132                    os = new ByteArrayOutputStream();
133                    byte[] buffer = new byte[1024];
134                    int i;
135                    while ((i = is.read(buffer)) != -1) {
136                        os.write(buffer, 0, i);
137                    }
138                    os.flush();
139                } finally {
140                    if (os != null) {
141                        os.close();
142                    }
143                }
144            }
145        } finally {
146            if (is != null) {
147                try {
148                    is.close();
149                } finally {
150                    is = null;
151                }
152            }
153        }
154        return os;
155    }
156
157    public static byte[] fetchUrl(URL url) {
158        byte[] data = null;
159        try {
160            final InputStream in = url.openStream();
161            final ByteArrayOutputStream os = new ByteArrayOutputStream();
162            byte[] buffer = new byte[1024];
163            int i;
164            while ((i = in.read(buffer)) != -1) {
165                os.write(buffer, 0, i);
166            }
167            data = os.toByteArray();
168            in.close();
169            os.close();
170        } catch (IOException e) {
171            log.error("Could not retrieve URL: " + url.toString());
172        }
173        return data;
174    }
175
176    public static void writeFile(URL url, String text) throws IOException {
177        // local file system
178        if (url.getProtocol().equals("file")) {
179            String filepath = url.getFile();
180            File file = new File(filepath);
181            FileUtils.writeFile(file, text);
182        } else {
183            OutputStream os = null;
184            URLConnection urlc;
185            try {
186                urlc = url.openConnection();
187                os = urlc.getOutputStream();
188            } catch (IOException e) {
189                log.error(e.getMessage(), e);
190            }
191
192            if (os != null) {
193                try {
194                    os.write(text.getBytes());
195                    os.flush();
196                } catch (IOException e) {
197                    log.error(e.getMessage(), e);
198                } finally {
199                    try {
200                        os.close();
201                    } catch (IOException e) {
202                        log.error(e.getMessage(), e);
203                    } finally {
204                        os = null;
205                    }
206                }
207            }
208
209        }
210
211    }
212
213    public static void loadProperties(final Properties properties, final String resourceName) {
214        if (properties.isEmpty()) {
215            InputStream in = null;
216            try {
217                in = Utils.class.getResourceAsStream(resourceName);
218                if (in != null) {
219                    properties.load(in);
220                }
221            } catch (IOException e) {
222                log.error("Could not load properties", e);
223            } finally {
224                if (in != null) {
225                    try {
226                        in.close();
227                    } catch (IOException e) {
228                        log.error("Failed to close stream", e);
229                    }
230                }
231            }
232        }
233    }
234
235    /**
236     * Parses and loads CSS resources into given style element. If boolean merge is set to true, keep existing
237     * properties already defined in style.
238     *
239     * @since 5.5
240     */
241    public static void loadCss(final Style style, String cssSource, final String viewName, final boolean merge) {
242        // pre-processing: replace empty selectors (which are invalid
243        // selectors) with a marker selector
244
245        // replace ${basePath} occurrences with a temporary marker to avoid
246        // interfering with the regexp logic
247        cssSource = cssSource.replaceAll("\\$\\{basePath\\}", "TEMPORARY_BASE_PATH_MARKER");
248        final Matcher matcher = emptyCssSelectorPattern.matcher(cssSource);
249        final StringBuilder buf = new StringBuilder();
250        while (matcher.find()) {
251            if (matcher.group(1).trim().equals("")) {
252                buf.append(EMPTY_CSS_SELECTOR);
253            }
254            buf.append(matcher.group(0));
255        }
256        cssSource = buf.toString();
257        cssSource = cssSource.replaceAll("TEMPORARY_BASE_PATH_MARKER", "\\$\\{basePath\\}");
258
259        CascadingStyleSheet styleSheet = CSSReader.readFromString(cssSource, "utf-8", ECSSVersion.CSS30);
260        if (styleSheet == null) {
261            log.error("Could not parse CSS:\n" + cssSource);
262            return;
263        }
264
265        if (!merge) {
266            // remove existing properties
267            style.clearPropertiesFor(viewName);
268        }
269
270        ICSSWriterSettings writerSettings = new CSSWriterSettings(ECSSVersion.CSS30, true);
271        for (CSSStyleRule rule : styleSheet.getAllStyleRules()) {
272            Properties properties = new Properties();
273            for (CSSDeclaration declaration : rule.getAllDeclarations()) {
274                String expression = declaration.getExpression().getAsCSSString(writerSettings, 0);
275                if (declaration.isImportant()) {
276                    expression = expression + CCSS.IMPORTANT_SUFFIX;
277                }
278                properties.put(declaration.getProperty(), expression);
279            }
280
281            for (CSSSelector cssSelector : rule.getAllSelectors()) {
282                String selector = cssSelector.getAsCSSString(writerSettings, 0);
283                if (selector.equals(EMPTY_CSS_SELECTOR) || selector.toLowerCase().equals("body")
284                        || selector.toLowerCase().equals("html")) {
285                    selector = "";
286                }
287                style.setPropertiesFor(viewName, selector, properties);
288            }
289        }
290    }
291
292    public static void loadCss(final Style style, String cssSource, final String viewName) {
293        loadCss(style, cssSource, viewName, false);
294    }
295}