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 }