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}