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