001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS <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 * $Id$
013 */
014
015package org.nuxeo.theme.themes;
016
017import java.io.BufferedWriter;
018import java.io.IOException;
019import java.io.StringWriter;
020import java.io.Writer;
021import java.util.ArrayList;
022import java.util.Enumeration;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Properties;
027
028import javax.xml.parsers.DocumentBuilder;
029import javax.xml.parsers.DocumentBuilderFactory;
030import javax.xml.parsers.ParserConfigurationException;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.xml.serialize.OutputFormat;
035import org.apache.xml.serialize.XMLSerializer;
036import org.nuxeo.theme.Manager;
037import org.nuxeo.theme.Utils;
038import org.nuxeo.theme.elements.Element;
039import org.nuxeo.theme.elements.ElementFormatter;
040import org.nuxeo.theme.elements.ThemeElement;
041import org.nuxeo.theme.formats.Format;
042import org.nuxeo.theme.formats.styles.Style;
043import org.nuxeo.theme.fragments.Fragment;
044import org.nuxeo.theme.nodes.Node;
045import org.nuxeo.theme.perspectives.PerspectiveType;
046import org.nuxeo.theme.presets.PresetManager;
047import org.nuxeo.theme.presets.PresetType;
048import org.nuxeo.theme.properties.FieldIO;
049import org.nuxeo.theme.uids.Identifiable;
050import org.w3c.dom.DOMException;
051import org.w3c.dom.Document;
052
053public class ThemeSerializer {
054
055    private static final Log log = LogFactory.getLog(ThemeSerializer.class);
056
057    private static final String DOCROOT_NAME = "theme";
058
059    private Document doc;
060
061    private List<Element> elements;
062
063    public Document serialize(final String src) throws ParserConfigurationException, DOMException, ThemeException,
064            ThemeIOException {
065        final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
066        try {
067            dbf.setFeature("http://xml.org/sax/features/validation", false);
068            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
069        } catch (ParserConfigurationException e) {
070            log.debug("Could not set DTD non-validation feature");
071        }
072        final DocumentBuilder db = dbf.newDocumentBuilder();
073        doc = db.newDocument();
074        elements = new ArrayList<Element>();
075        final org.w3c.dom.Element root = doc.createElement(DOCROOT_NAME);
076        final ThemeManager themeManager = Manager.getThemeManager();
077        ThemeElement theme = themeManager.getThemeBySrc(src);
078
079        // Theme description and name
080        final String description = theme.getDescription();
081        if (description != null) {
082            root.setAttribute("description", description);
083        }
084
085        final String themeName = theme.getName();
086        root.setAttribute("name", themeName);
087
088        ThemeDescriptor themeDef = ThemeManager.getThemeDescriptor(src);
089        List<String> templateEngines = themeDef.getTemplateEngines();
090        if (templateEngines != null && templateEngines.size() > 0) {
091            final StringBuilder sb = new StringBuilder();
092            final Iterator<String> it = templateEngines.iterator();
093            while (it.hasNext()) {
094                sb.append(it.next());
095                if (it.hasNext()) {
096                    sb.append(",");
097                }
098            }
099            root.setAttribute("template-engines", sb.toString());
100        }
101
102        // Resource bank
103        String resourceBankName = themeDef.getResourceBankName();
104        if (resourceBankName != null) {
105            root.setAttribute("resource-bank", resourceBankName);
106        }
107
108        doc.appendChild(root);
109
110        // layout
111        final org.w3c.dom.Element layoutNode = doc.createElement("layout");
112        root.appendChild(layoutNode);
113
114        for (Node page : theme.getChildren()) {
115            serializeLayout((Element) page, layoutNode);
116        }
117
118        // element properties
119        for (Element element : elements) {
120            serializeProperties(element, root);
121        }
122
123        // presets
124        List<PresetType> customPresets = PresetManager.getCustomPresets(themeName);
125        if (!customPresets.isEmpty()) {
126            final org.w3c.dom.Element presetsNode = doc.createElement("presets");
127            root.appendChild(presetsNode);
128            for (PresetType preset : customPresets) {
129                final org.w3c.dom.Element presetNode = doc.createElement("preset");
130                presetNode.setAttribute("name", preset.getName());
131                presetNode.setAttribute("category", preset.getCategory());
132                presetNode.setAttribute("label", preset.getLabel());
133                presetNode.setAttribute("description", preset.getDescription());
134                presetNode.appendChild(doc.createTextNode(preset.getValue()));
135                presetsNode.appendChild(presetNode);
136            }
137        }
138
139        // formats
140        final org.w3c.dom.Element formatNode = doc.createElement("formats");
141        root.appendChild(formatNode);
142
143        for (String formatTypeName : themeManager.getFormatTypeNames()) {
144            // export named styles
145            for (Identifiable object : themeManager.getNamedObjects(themeName, formatTypeName)) {
146                Format format = (Format) object;
147                // skip unused remote styles
148                if (!format.isCustomized() && ThemeManager.listFormatsDirectlyInheritingFrom(format).isEmpty()) {
149                    continue;
150                }
151                serializeFormat(format, formatNode);
152            }
153            for (Format format : themeManager.getFormatsByTypeName(formatTypeName)) {
154                if (format.isNamed()) {
155                    continue;
156                }
157                // make sure that the format is used by this theme
158                boolean isUsedByThisTheme = false;
159                for (Element element : ElementFormatter.getElementsFor(format)) {
160                    if (element.isChildOf(theme) || element == theme) {
161                        isUsedByThisTheme = true;
162                        break;
163                    }
164                }
165                if (isUsedByThisTheme) {
166                    serializeFormat(format, formatNode);
167                }
168            }
169        }
170        return doc;
171    }
172
173    private void serializeProperties(final Element parent, final org.w3c.dom.Element domParent) throws DOMException,
174            ThemeIOException {
175        final org.w3c.dom.Element domProperties = doc.createElement("properties");
176        domProperties.setAttribute("element", parent.computeXPath());
177        for (Map.Entry<Object, Object> entry : FieldIO.dumpFieldsToProperties(parent).entrySet()) {
178            final org.w3c.dom.Element domProperty = doc.createElement((String) entry.getKey());
179            final String value = (String) entry.getValue();
180            domProperty.appendChild(doc.createTextNode(Utils.cleanUp(value)));
181            domProperties.appendChild(domProperty);
182        }
183        if (domProperties.hasChildNodes()) {
184            domParent.appendChild(domProperties);
185        }
186    }
187
188    private void serializeLayout(final Element parent, final org.w3c.dom.Element domParent) {
189        final String typeName = parent.getElementType().getTypeName();
190        final org.w3c.dom.Element domElement = doc.createElement(typeName);
191
192        elements.add(parent);
193
194        final String elementName = parent.getName();
195        if (elementName != null) {
196            domElement.setAttribute("name", elementName);
197        }
198
199        final String elementClassName = parent.getCssClassName();
200        if (elementClassName != null) {
201            domElement.setAttribute("class", elementClassName);
202        }
203
204        if (parent instanceof Fragment) {
205            domElement.setAttribute("type", ((Fragment) parent).getFragmentType().getTypeName());
206
207            // perspectives
208            final StringBuilder s = new StringBuilder();
209            final Iterator<PerspectiveType> it = ((Fragment) parent).getVisibilityPerspectives().iterator();
210            while (it.hasNext()) {
211                PerspectiveType perspective = it.next();
212                s.append(perspective.getTypeName());
213                if (it.hasNext()) {
214                    s.append(",");
215                }
216            }
217            if (s.length() > 0) {
218                domElement.setAttribute("perspectives", s.toString());
219            }
220        }
221
222        String description = parent.getDescription();
223        if (description != null) {
224            domParent.appendChild(doc.createComment(String.format(" %s ", description)));
225        }
226
227        domParent.appendChild(domElement);
228        for (Node child : parent.getChildren()) {
229            serializeLayout((Element) child, domElement);
230        }
231    }
232
233    private void serializeFormat(final Format format, final org.w3c.dom.Element domParent) {
234        final String typeName = format.getFormatType().getTypeName();
235        final org.w3c.dom.Element domElement = doc.createElement(typeName);
236
237        final String description = format.getDescription();
238        if (description != null) {
239            domParent.appendChild(doc.createComment(String.format(" %s ", description)));
240        }
241
242        StringBuilder s = new StringBuilder();
243        Iterator<Element> iter = ElementFormatter.getElementsFor(format).iterator();
244        boolean hasElement = iter.hasNext();
245        while (iter.hasNext()) {
246            Element element = iter.next();
247            s.append(element.computeXPath());
248            if (iter.hasNext()) {
249                s.append("|");
250            }
251        }
252        if (hasElement) {
253            domElement.setAttribute("element", s.toString());
254        }
255
256        // widgets
257        if ("widget".equals(typeName)) {
258            // view name
259            String viewName = format.getName();
260            org.w3c.dom.Element domView = doc.createElement("view");
261            domView.appendChild(doc.createTextNode(viewName));
262            domElement.appendChild(domView);
263
264            // properties
265            Properties properties = format.getProperties();
266            Enumeration<?> names = properties.propertyNames();
267
268            while (names.hasMoreElements()) {
269                String name = (String) names.nextElement();
270                if ("view".equals(name)) {
271                    continue;
272                }
273                String value = properties.getProperty(name);
274                org.w3c.dom.Element domAttr = doc.createElement(name);
275                domAttr.appendChild(doc.createTextNode(Utils.cleanUp(value)));
276                domElement.appendChild(domAttr);
277            }
278        }
279
280        // layout
281        else if ("layout".equals(typeName)) {
282            Properties properties = format.getProperties();
283            Enumeration<?> names = properties.propertyNames();
284            while (names.hasMoreElements()) {
285                String name = (String) names.nextElement();
286                String value = properties.getProperty(name);
287                org.w3c.dom.Element domView = doc.createElement(name);
288                domView.appendChild(doc.createTextNode(Utils.cleanUp(value)));
289                domElement.appendChild(domView);
290            }
291        }
292
293        // style
294        else if ("style".equals(typeName)) {
295            Style style = (Style) format;
296            if (style.isExternal()) {
297                return;
298            }
299            String styleName = style.getName();
300            Style ancestor = (Style) ThemeManager.getAncestorFormatOf(style);
301            if (styleName != null) {
302                domElement.setAttribute("name", styleName);
303            }
304            if (ancestor != null) {
305                domElement.setAttribute("inherit", ancestor.getName());
306            }
307            if (style.isRemote()) {
308                domElement.setAttribute("remote", "true");
309            }
310            if ((!style.isRemote() || style.isCustomized())) {
311                for (String viewName : style.getSelectorViewNames()) {
312                    for (String path : style.getPathsForView(viewName)) {
313                        Properties styleProperties = style.getPropertiesFor(viewName, path);
314                        if (styleProperties.isEmpty()) {
315                            continue;
316                        }
317                        org.w3c.dom.Element domSelector = doc.createElement("selector");
318                        path = Utils.cleanUp(path);
319                        domSelector.setAttribute("path", path);
320                        if (!"*".equals(viewName)) {
321                            domSelector.setAttribute("view", viewName);
322                        }
323
324                        for (Map.Entry<Object, Object> entry : styleProperties.entrySet()) {
325                            org.w3c.dom.Element domProperty = doc.createElement((String) entry.getKey());
326                            String value = (String) entry.getValue();
327                            String presetName = PresetManager.extractPresetName(null, value);
328                            if (presetName != null) {
329                                domProperty.setAttribute("preset", presetName);
330                            } else {
331                                domProperty.appendChild(doc.createTextNode(Utils.cleanUp(value)));
332                            }
333                            domSelector.appendChild(domProperty);
334                        }
335
336                        // Set selector description
337                        String selectorDescription = style.getSelectorDescription(path, viewName);
338                        if (selectorDescription != null) {
339                            domElement.appendChild(doc.createComment(String.format(" %s ", selectorDescription)));
340                        }
341
342                        domElement.appendChild(domSelector);
343                    }
344                }
345            }
346        }
347        domParent.appendChild(domElement);
348    }
349
350    public String serializeToXml(final String src) throws ThemeIOException {
351        return serializeToXml(src, 0);
352    }
353
354    public String serializeToXml(final String src, final int indent) throws ThemeIOException {
355        // serialize the theme into a document
356        try {
357            serialize(src);
358            // convert the document to XML
359            StringWriter sw = new StringWriter();
360            OutputFormat format = new OutputFormat(doc);
361            format.setIndenting(true);
362            format.setIndent(indent);
363            Writer output = new BufferedWriter(sw);
364            XMLSerializer serializer = new XMLSerializer(output, format);
365            serializer.serialize(doc);
366            return sw.toString();
367        } catch (IOException | DOMException | ParserConfigurationException | ThemeException e) {
368            throw new ThemeIOException(e);
369        }
370
371    }
372
373}