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.util.ArrayList; 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Properties; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.theme.Manager; 027import org.nuxeo.theme.Utils; 028import org.nuxeo.theme.elements.CellElement; 029import org.nuxeo.theme.elements.Element; 030import org.nuxeo.theme.elements.ElementFormatter; 031import org.nuxeo.theme.elements.ElementType; 032import org.nuxeo.theme.elements.PageElement; 033import org.nuxeo.theme.elements.SectionElement; 034import org.nuxeo.theme.elements.ThemeElement; 035import org.nuxeo.theme.formats.Format; 036import org.nuxeo.theme.formats.FormatFactory; 037import org.nuxeo.theme.formats.layouts.Layout; 038import org.nuxeo.theme.formats.styles.Style; 039import org.nuxeo.theme.formats.widgets.Widget; 040import org.nuxeo.theme.nodes.Node; 041 042public final class ThemeRepairer { 043 044 private static final Log log = LogFactory.getLog(ThemeRepairer.class); 045 046 private static final String[] LAYOUT_PROPERTIES = { "width", "height", "text-align", "padding", "margin", 047 "margin-left", "margin-right", "margin-top", "margin-bottom", "padding-left", "padding-right", 048 "padding-bottom", "padding-top" }; 049 050 private static final String[] PAGE_LAYOUT_PROPERTIES = { "margin", "padding" }; 051 052 private static final String[] SECTION_LAYOUT_PROPERTIES = { "width", "height", "margin-right", "margin-left" }; 053 054 private static final String[] CELL_LAYOUT_PROPERTIES = { "width", "padding", "text-align" }; 055 056 private static final String[] PAGE_STYLE_PROPERTIES = { "border-top", "border-left", "border-bottom", 057 "border-right", "background" }; 058 059 private static final String[] SECTION_STYLE_PROPERTIES = { "border-top", "border-left", "border-bottom", 060 "border-right", "background" }; 061 062 private static final String[] CELL_STYLE_PROPERTIES = { "border-top", "border-left", "border-bottom", 063 "border-right", "background" }; 064 065 public static void repair(ThemeElement theme) throws ThemeException { 066 // Make sure that all shared formats are assigned to elements of a same 067 // type 068 checkSharedFormats(theme); 069 070 List<Node> allElements = theme.getDescendants(); 071 allElements.add(theme); 072 073 // Move layout-related properties found in styles to layout formats 074 for (Node node : allElements) { 075 Element element = (Element) node; 076 if (element instanceof PageElement || element instanceof SectionElement || element instanceof CellElement) { 077 moveLayoutProperties(element); 078 } 079 } 080 081 // Clean up styles and layouts 082 for (Node node : allElements) { 083 Element element = (Element) node; 084 if (element instanceof PageElement || element instanceof SectionElement || element instanceof CellElement) { 085 cleanupStyles(element); 086 cleanupLayouts(element); 087 } 088 } 089 } 090 091 public static void checkSharedFormats(ThemeElement theme) throws ThemeException { 092 ThemeManager themeManager = Manager.getThemeManager(); 093 for (Format format : Manager.getThemeManager().listFormats()) { 094 Collection<Element> elements = ElementFormatter.getElementsFor(format); 095 if (elements.size() < 2) { 096 continue; 097 } 098 Map<ElementType, Format> formatsByElementTypes = new HashMap<ElementType, Format>(); 099 for (Element element : elements) { 100 if (!element.isChildOf(theme)) { 101 continue; 102 } 103 ElementType elementType = element.getElementType(); 104 if (formatsByElementTypes.isEmpty()) { 105 formatsByElementTypes.put(elementType, format); 106 } else if (!formatsByElementTypes.containsKey(elementType)) { 107 log.debug("Created format of type '" + format.getFormatType().getTypeName() + "' for element: '" 108 + element.computeXPath() + "' "); 109 formatsByElementTypes.put(elementType, themeManager.duplicateFormat(format)); 110 } 111 } 112 for (Map.Entry<ElementType, Format> entry : formatsByElementTypes.entrySet()) { 113 Format f = entry.getValue(); 114 ElementType elementType = entry.getKey(); 115 for (Element element : elements) { 116 if (element.getElementType().equals(elementType)) { 117 ElementFormatter.setFormat(element, f); 118 } 119 } 120 } 121 } 122 } 123 124 private static void moveLayoutProperties(Element element) throws ThemeException { 125 Widget widget = (Widget) ElementFormatter.getFormatFor(element, "widget"); 126 Style style = (Style) ElementFormatter.getFormatFor(element, "style"); 127 Layout layout = (Layout) ElementFormatter.getFormatFor(element, "layout"); 128 String xpath = element.computeXPath(); 129 130 // Add missing layout formats 131 if (layout == null) { 132 layout = (Layout) FormatFactory.create("layout"); 133 Manager.getThemeManager().registerFormat(layout); 134 ElementFormatter.setFormat(element, layout); 135 log.debug("Added layout to element: " + xpath); 136 } 137 138 if (ElementFormatter.getFormatFor(element, "widget") == null) { 139 log.error("Element " + xpath + " has no widget."); 140 } 141 142 // Move layout-related properties to layout formats 143 if (style != null) { 144 if (widget != null) { 145 String viewName = widget.getName(); 146 if (viewName != null) { 147 Properties styleProperties = style.getPropertiesFor(viewName, ""); 148 if (styleProperties != null) { 149 Collection<String> propertiesToMove = new ArrayList<String>(); 150 for (String key : LAYOUT_PROPERTIES) { 151 String value = (String) styleProperties.get(key); 152 if (value != null) { 153 propertiesToMove.add(key); 154 } 155 } 156 157 if (!propertiesToMove.isEmpty()) { 158 for (String key : propertiesToMove) { 159 layout.setProperty(key, styleProperties.getProperty(key)); 160 log.debug("Moved property '" + key + "' from <style> to <layout> for element " + xpath); 161 } 162 } 163 } 164 } 165 } 166 } 167 } 168 169 private static void cleanupStyles(Element element) { 170 Widget widget = (Widget) ElementFormatter.getFormatFor(element, "widget"); 171 Style style = (Style) ElementFormatter.getFormatFor(element, "style"); 172 String xpath = element.computeXPath(); 173 174 // Simplify styles by removing disallowed layout properties and by 175 // cleaning up paths without properties 176 if (style != null && widget != null) { 177 String viewName = widget.getName(); 178 List<String> pathsToClear = new ArrayList<String>(); 179 180 for (String path : style.getPathsForView(viewName)) { 181 Properties styleProperties = style.getPropertiesFor(viewName, path); 182 if (styleProperties == null) { 183 continue; 184 } 185 for (String key : LAYOUT_PROPERTIES) { 186 if (styleProperties.containsKey(key)) { 187 styleProperties.remove(key); 188 log.debug("Removed property: '" + key + "' from <style> for element " + xpath); 189 } 190 } 191 192 if (styleProperties.isEmpty()) { 193 pathsToClear.add(path); 194 continue; 195 } 196 197 List<String> stylePropertiesToRemove = new ArrayList<String>(); 198 for (Object key : styleProperties.keySet()) { 199 String propertyName = (String) key; 200 if ((widget instanceof PageElement && !Utils.contains(PAGE_STYLE_PROPERTIES, propertyName)) 201 || (widget instanceof SectionElement && !Utils.contains(SECTION_STYLE_PROPERTIES, 202 propertyName)) 203 || (widget instanceof CellElement && !Utils.contains(CELL_STYLE_PROPERTIES, propertyName))) { 204 stylePropertiesToRemove.add(propertyName); 205 } 206 } 207 208 for (String propertyName : stylePropertiesToRemove) { 209 styleProperties.remove(propertyName); 210 log.debug("Removed style property: '" + propertyName + " in path: " + path + "' for element " 211 + xpath); 212 } 213 } 214 215 for (String path : pathsToClear) { 216 style.clearPropertiesFor(viewName, path); 217 log.debug("Removed empty style path: '" + path + "' for element " + xpath); 218 } 219 } 220 } 221 222 private static void cleanupLayouts(Element element) { 223 Layout layout = (Layout) ElementFormatter.getFormatFor(element, "layout"); 224 String xpath = element.computeXPath(); 225 Properties layoutProperties = layout.getProperties(); 226 227 List<String> layoutPropertiesToRemove = new ArrayList<String>(); 228 for (Object key : layoutProperties.keySet()) { 229 String propertyName = (String) key; 230 if ((element instanceof PageElement && !Utils.contains(PAGE_LAYOUT_PROPERTIES, propertyName)) 231 || (element instanceof SectionElement && !Utils.contains(SECTION_LAYOUT_PROPERTIES, propertyName)) 232 || (element instanceof CellElement && !Utils.contains(CELL_LAYOUT_PROPERTIES, propertyName))) { 233 layoutPropertiesToRemove.add(propertyName); 234 } 235 } 236 237 for (String propertyName : layoutPropertiesToRemove) { 238 layoutProperties.remove(propertyName); 239 log.debug("Removed property '" + propertyName + "' from <layout> for element " + xpath); 240 } 241 } 242 243}