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