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