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}