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}