001    /*
002     * This file is part of the Echo Point Project.  This project is a collection
003     * of Components that have extended the Echo Web Application Framework.
004     *
005     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006     *
007     * The contents of this file are subject to the Mozilla Public License Version
008     * 1.1 (the "License"); you may not use this file except in compliance with
009     * the License. You may obtain a copy of the License at
010     * http://www.mozilla.org/MPL/
011     *
012     * Software distributed under the License is distributed on an "AS IS" basis,
013     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014     * for the specific language governing rights and limitations under the
015     * License.
016     *
017     * Alternatively, the contents of this file may be used under the terms of
018     * either the GNU General Public License Version 2 or later (the "GPL"), or
019     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020     * in which case the provisions of the GPL or the LGPL are applicable instead
021     * of those above. If you wish to allow use of your version of this file only
022     * under the terms of either the GPL or the LGPL, and not to allow others to
023     * use your version of this file under the terms of the MPL, indicate your
024     * decision by deleting the provisions above and replace them with the notice
025     * and other provisions required by the GPL or the LGPL. If you do not delete
026     * the provisions above, a recipient may use your version of this file under
027     * the terms of any one of the MPL, the GPL or the LGPL.
028     */
029    package echopoint.util;
030    
031    import java.io.FileReader;
032    import java.io.FileWriter;
033    import java.io.IOException;
034    import java.io.InputStream;
035    import java.io.OutputStream;
036    import java.io.Reader;
037    import java.io.Writer;
038    import java.util.Iterator;
039    import java.util.List;
040    import org.jdom.Attribute;
041    import org.jdom.Content;
042    import org.jdom.Document;
043    import org.jdom.Element;
044    import org.jdom.JDOMException;
045    import org.jdom.input.SAXBuilder;
046    import org.jdom.output.XMLOutputter;
047    
048    /**
049     * <code>StyleMerger</code> is an Component which alllows you to merge two
050     * xml style definitions into a single one, which can then be loaded by echo.
051     *
052     * We use it in the following way:
053     *
054     * Our application has a default style which is used for normal installations
055     * If we build special versions of our application to a customer, we then
056     * add a second stylesheet with only the modifications for that client.
057     * On system startup we then merge the two XML styles into one and then load
058     * the resulting one.
059     *
060     * @author a.schild
061     */
062    public class StyleMerger
063    {
064        /**
065         *
066         * @param baseStylesFile   File with default styles
067         * @param patchStylesFile  File which contains the styles to be added/patched
068         * @param outFile          File with the merged styles
069         *
070         * @throws IOException
071         * @throws JDOMException
072         */
073        public static void mergeXMLStyles(String baseStylesFile, String patchStylesFile, String outFile) throws IOException, JDOMException
074        {
075            mergeXMLStyles(new FileReader(baseStylesFile), new FileReader(patchStylesFile), new FileWriter(outFile));
076        }
077    
078        /**
079         *
080         * @param mainStyles        Reader with with default styles
081         * @param patchStyles       Reader which contains the styles to be added/patched
082         * @param out               Output with the merged styles
083         *
084         * @throws IOException
085         * @throws JDOMException
086         */
087        public static void mergeXMLStyles(Reader mainStyles, Reader patchStyles, Writer out) throws IOException, JDOMException
088        {
089            SAXBuilder builder= new SAXBuilder();
090            Document doc = builder.build(mainStyles);
091            Document doc2 = builder.build(patchStyles);
092    
093            Document outDoc= mergeStyles(doc, doc2);
094    
095            XMLOutputter xmlOut= new XMLOutputter();
096            xmlOut.output(doc, out);
097        }
098    
099    
100        /**
101         *
102         * @param mainStyles        Stream with with default styles
103         * @param patchStyles       Stream which contains the styles to be added/patched
104         * @param out               Output with the merged styles
105         * 
106         * @throws IOException
107         * @throws JDOMException
108         */
109        public static void mergeXMLStyles(InputStream mainStyles, InputStream patchStyles, OutputStream out) throws IOException, JDOMException
110        {
111            SAXBuilder builder= new SAXBuilder();
112            Document doc = builder.build(mainStyles);
113            Document doc2 = builder.build(patchStyles);
114    
115            Document outDoc= mergeStyles(doc, doc2);
116    
117            XMLOutputter xmlOut= new XMLOutputter();
118            xmlOut.output(outDoc, out);
119        }
120    
121        /**
122         * Merge the two docs and return the merged one.
123         *
124         * @param doc1
125         * @param doc2
126         *
127         * @return doc1 is containing the merged document
128         */
129        protected static Document mergeStyles(Document doc1, Document doc2)
130        {
131            // Get List of children to be added/patched into the main style
132            // Should be the ss entry
133            Element doc1RootElement= doc1.getRootElement();
134            Element doc2RootElement= doc2.getRootElement();
135            if (doc2RootElement.getName().equals("ss"))
136            {
137                List<Element> l1Children= doc2RootElement.getChildren();
138    
139                for(Element style : l1Children)
140                {
141                    String name= style.getName();
142                    if (name.equals("s"))
143                    {
144                        // Build style name, so search fo rin the original document
145                        String attrName= makeStyleName(style);
146                        // Now look if we have such a style in the original styles
147                        Element targetElement= findStyleDefinition(doc1RootElement, attrName);
148                        if (targetElement != null)
149                        {
150                            // Found style in original document, so we have to merge it
151                            mergeStyle(targetElement, style);
152                        }
153                        else
154                        {
155                            // Not found in ogiginal document, so copy it over
156                            Element newElement= (Element) style.clone();
157                            doc1RootElement.addContent(newElement);
158                        }
159                    }
160                    else
161                    {
162                        //_log.fatal("Style is not named <s> but <"+name+"> instead");
163                    }
164                    //List<Content> styles= (List<Content>)ssChild..getContent();
165                }
166            }
167            else
168            {
169                //_log.fatal("StyleSheet is not named <ss> but <"+doc2RootElement.getName()+"> instead");
170            }
171            return doc1;
172        }
173    
174        /**
175         * Make up a stylename which respects all attributes to uniquely identify
176         * a style.
177         * 
178         * @param style
179         * @return
180         */
181        protected static String makeStyleName(Element style)
182        {
183            String retVal= "";
184            List<Attribute> sAttrs= style.getAttributes();
185            for (Attribute thisAttr : sAttrs)
186            {
187                retVal+=">"+thisAttr.getName()+">"+thisAttr.getValue();
188            }
189            return retVal;
190        }
191    
192        /**
193         * Sear this tree for a matching style
194         * 
195         * @param rootNode
196         * @param styleName
197         * @return
198         */
199        protected static Element findStyleDefinition(Element rootNode, String styleName)
200        {
201            Element retVal= null;
202            List<Element> children= rootNode.getChildren();
203            Iterator<Element> iChildren= children.iterator();
204            while (retVal == null && iChildren.hasNext())
205            {
206                Element thisChild= iChildren.next();
207                String thisName= makeStyleName(thisChild);
208                if (thisName.equals(styleName))
209                {
210                    retVal= thisChild;
211                }
212            }
213            return retVal;
214        }
215    
216        /**
217         * Merge two styles.
218         * All attribuets/values found in partialStyle are replacing existing
219         * values in targetStyle.
220         * If no such attributes exist in targetStyle, then we add them
221         *
222         * @param targetStyle
223         * @param partialStyle
224         */
225        protected static void mergeStyle(Element targetStyle, Element partialStyle)
226        {
227            // Don't copy over attributes
228            // partialStyle.getAttributes()
229    
230            // Now the definitions
231            List<Element> children= partialStyle.getChildren();
232            for (Element child: children)
233            {
234                String cName= makeStyleName(child);
235                // Now search in target
236                List<Content> tChildren= targetStyle.getChildren(child.getName());
237                if (tChildren.isEmpty())
238                {
239                    // Ok, add it
240                    Element tElement= (Element)child.clone();
241                    targetStyle.addContent(tElement);
242                }
243                else
244                {
245                    // .. merge ..
246                    Element targetNode= null;
247                    for (Content childContent : tChildren)
248                    {
249                        if (childContent instanceof org.jdom.Text)
250                        {
251                            // Skip
252                        }
253                        else
254                        {
255                            // A node, add it
256                            Element myElement= (Element) childContent;
257                            String eName= makeStyleName(myElement);
258                            if (targetNode == null)
259                            {
260                                if (eName.equals(cName))
261                                {
262                                    targetNode= myElement;
263                                }
264                            }
265                        }
266                    }
267                    if (targetNode == null)
268                    {
269                        // Add node
270    
271                    }
272                    else
273                    {
274                        // Merge/Replace?
275                        targetStyle.removeContent(targetNode);
276                        targetStyle.addContent((Content) child.clone());
277                    }
278                }
279            }
280    
281        }
282    
283    }