1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.xmerge.converter.xml;
25 
26 import org.w3c.dom.NodeList;
27 import org.w3c.dom.Node;
28 import org.w3c.dom.NamedNodeMap;
29 import org.w3c.dom.Element;
30 import org.openoffice.xmerge.util.*;
31 import java.util.Vector;
32 import java.lang.reflect.Constructor;
33 
34 
35 /**
36  *  A <code>StyleCatalog</code> holds a collection of <code>Style</code>
37  *  objects.  It is intended for use when parsing or building a DOM
38  *  document.
39  *
40  *  Each entry in the <code>StyleCatalog</code> represents a
41  *  <code>Style</code>, and is an object which is a subclass of
42  *  <code>Style</code>.
43  *
44  *  @author David Proulx
45  *  @see <a href="Style.html">Style</a>
46  */
47 public class StyleCatalog {
48 
49     private Vector styles;  // The actual styles
50 
51     /**
52      *  Constructor
53      *
54      *  @param  initialEntries  Expected number of entries to set
55      *                          for efficiency purposes.
56      */
StyleCatalog(int initialEntries)57     public StyleCatalog(int initialEntries) {
58         styles = new Vector(initialEntries);
59     }
60 
61 
62     /**
63      *  <p>Parse the <code>Document</code> starting from <code>node</code>
64      *  and working downward, and add all styles found, so long as their
65      *  family name is listed in <code>families</code>.  For each
66      *  family name in <code>families</code> there must be a corresponding
67      *  element in <code>classes</code>, which specifies the class type
68      *  to use for that family.  All of these classes must be
69      *  subclasses of <code>Style</code>.  There can be multiple
70      *  classes specified for a particular family.</p>
71      *
72      *  <p>If <code>defaultClass</code> is non-null, then all styles that
73      *  are found will be added.  Any <code>Style</code> whose family is
74      *  not listed in <code>families</code> will be added using defaultClass,
75      *  which, of course, must be a subclass of <code>Style</code>.
76      *  If <code>alwaysCreateDefault</code> is true, then a class
77      *  of type <code>defaultClass</code> will always be created,
78      *  regardless of whether there was also a match in
79      *  <code>families</code>.</p>
80      *
81      *  <p>DJP Todo: make it recursive so that <code>node</code> can be
82      *  higher up in the <code>Document</code> tree.</p>
83      *
84      *  @param  node                 The node to be searched for
85      *                               <code>Style</code> objects.
86      *  @param  families             An array of <code>Style</code> families
87      *                               to add.
88      *  @param  classes              An array of class types corresponding
89      *                               to the families array.
90      *  @param  defaultClass         All <code>Style</code> objects that are
91      *                               found are added to this class.
92      *  @param  alwaysCreateDefault  A class of type <code>defaultClass</code>
93      *                               will always be created, regardless of
94      *                               whether there is a match in the
95      *                               families array.
96      */
add(Node node, String families[], Class classes[], Class defaultClass, boolean alwaysCreateDefault)97     public void add(Node node, String families[], Class classes[],
98     Class defaultClass, boolean alwaysCreateDefault) {
99 
100     if (node == null)
101             return;
102 
103         if (families == null)
104             families = new String[0];
105         if (classes == null)
106             classes = new Class[0];
107         if (node.hasChildNodes()) {
108             NodeList children = node.getChildNodes();
109             int len = children.getLength();
110 
111             for (int i = 0; i < len; i++) {
112                 boolean found = false;
113                 Node child = children.item(i);
114                 String name = child.getNodeName();
115                 if (name.equals("style:default-style") || name.equals("style:style")) {
116                     String familyName = getFamilyName(child);
117                     if (familyName == null) {
118                         Debug.log(Debug.ERROR, "familyName is null!");
119                         continue;
120                     }
121 
122                     for (int j = 0; j < families.length; j++) {
123                         if (families[j].equals(familyName)) {
124                             Class styleClass = classes[j];
125                             callConstructor(classes[j], child);
126                             found = true;
127                         }
128                     }
129                     if ((!found || alwaysCreateDefault) && (defaultClass != null))
130                         callConstructor(defaultClass, child);
131                 }
132             }
133         }
134     }
135 
136 
137     /**
138      *  Call the constructor of class <code>cls</code> with parameters
139      *  <code>node</code>, and add the resulting <code>Style</code> to
140      *  the catalog.
141      *
142      *  @param  cls   The class whose constructor will be called.
143      *  @param  node  The constructed class will be added to this node.
144      */
callConstructor(Class cls, Node node)145     private void callConstructor(Class cls, Node node) {
146         Class params[] = new Class[2];
147         params[0] = Node.class;
148         params[1] = this.getClass();
149         try {
150             Constructor c = cls.getConstructor(params);
151             Object p[] = new Object[2];
152             p[0] = node;
153             p[1] = this;
154             styles.add(c.newInstance(p));
155         } catch (Exception e) {
156             Debug.log(Debug.ERROR, "Exception when calling constructor", e);
157         }
158     }
159 
160 
161     /**
162      *  Add a <code>Style</code> to the catalog.
163      *
164      *  @param  s  The <code>Style</code> to add.
165      */
add(Style s)166     public void add(Style s) {
167         styles.addElement(s);
168     }
169 
170 
171     /**
172      *  Return the first <code>Style</code> matching the specified names.
173      *
174      *  @param  name        Name to match, null is considered
175      *                      <i>always match</i>.
176      *  @param  family      Family to match, null is considered
177      *                      <i>always match</i>.
178      *  @param  parent      Parent to match, null is considered
179      *                      <i>always match</i>.
180      *  @param  styleClass  styleClass to match, null is considered
181      *                      <i>always match</i>.
182      *
183      *  @return  <code>Style</code> value if all parameters match,
184      *           null otherwise
185      */
lookup(String name, String family, String parent, Class styleClass)186     public Style lookup(String name, String family, String parent,
187                         Class styleClass) {
188         int nStyles = styles.size();
189         for (int i = 0; i < nStyles; i++) {
190             Style s = (Style)styles.elementAt(i);
191             if ((name != null) && (s.getName() != null)
192             && (!s.getName().equals(name)))
193                 continue;
194             if ((family != null) && (s.getFamily() != null)
195             && (!s.getFamily().equals(family)))
196                 continue;
197             if ((parent != null) && (s.getParent() != null)
198             && (!s.getParent().equals(parent)))
199                 continue;
200             if ((styleClass != null) && (s.getClass() != styleClass))
201                 continue;
202             if (s.getName() == null) continue;  // DJP: workaround for "null name" problem
203             return s;
204         }
205         return null;  // none found
206     }
207 
208 
209     /**
210      *  Given a <code>Style</code> <code>s</code> return all
211      *  <code>Style</code> objects that match.
212      *
213      *  @param  s  <code>Style</code> to match.
214      *
215      *  @return  An array of <code>Style</code> objects that match, an
216      *           empty array if none match.
217      */
getMatching(Style s)218     public Style[] getMatching(Style s) {
219 
220         // Run through and count the matching styles so we know how big of
221         // an array to allocate.
222         int matchCount = 0;
223         int nStyles = styles.size();
224         for (int j = 0; j < nStyles; j++) {
225             Style p = ((Style)styles.elementAt(j)).getResolved();
226             if (p.isSubset(s)) matchCount++;
227         }
228 
229         // Now allocate the array, and run through again, populating it.
230         Style[] matchArray = new Style[matchCount];
231         matchCount = 0;
232         for (int j = 0; j < nStyles; j++) {
233             Style p = ((Style)styles.elementAt(j)).getResolved();
234             if (p.isSubset(s)) matchArray[matchCount++] = p;
235         }
236         return matchArray;
237     }
238 
239 
240     /**
241      *  Given a <code>Style</code> <code>s</code>, return the
242      *  <code>style</code> that is the closest match.  Not currently
243      *  implemented.
244      *
245      *  @param  s  <code>Style</code> to match.
246      *
247      *  @return  The <code>Style</code> that most closely matches.
248      */
getBestMatch(Style s)249     public Style getBestMatch(Style s) {
250         // DJP: is this needed?
251         // DJP ToDo: implement this
252         return null;
253     }
254 
255 
256     /**
257      *  <p>Create a <code>Node</code> named <code>name</code> in
258      *  <code>Document</code> <code>parentDoc</code>, and write the
259      *  entire <code>StyleCatalog</code> to it.</p>
260      *
261      *  <p>Note that the resulting node is returned, but is not connected
262      *  into the document.  Placing the output node in the document is
263      *  left to the caller.</p>
264      *
265      *  @param  parentDoc  The <code>Document</code> to add the
266      *                     <code>Node</code>.
267      *  @param  name       The name of the <code>Node</code> to add.
268      *
269      *  @return  The <code>Element</code> that was created.
270      */
writeNode(org.w3c.dom.Document parentDoc, String name)271     public Element writeNode(org.w3c.dom.Document parentDoc, String name) {
272         Element rootNode = parentDoc.createElement(name);
273 
274         int len = styles.size();
275         for (int j = 0; j < len; j++) {
276             Style s = (Style)styles.get(j);
277 
278             Element styleNode = parentDoc.createElement("style:style");
279 
280             if (s.getName() != null)
281                 styleNode.setAttribute("style:name", s.getName());
282             if (s.getParent() != null)
283                 styleNode.setAttribute("style:parent-style-name", s.getParent());
284             if (s.getFamily() != null)
285                 styleNode.setAttribute("style:family", s.getFamily());
286 
287             Element propertiesNode = (Element) s.createNode(parentDoc, "style:properties");
288             // if (propertiesNode.getFirstChild() != null)
289             // DJP: only add node if has children OR attributes
290             if (propertiesNode != null)
291                 styleNode.appendChild(propertiesNode);
292 
293             rootNode.appendChild(styleNode);
294         }
295 
296         return rootNode;
297     }
298 
299 
300     /**
301      *  Dump the <code>Style</code> table in Comma Separated Value (CSV)
302      *  format
303      *
304      *  @param  para  If true, dump in paragraph <code>Style</code>,
305      *                otherwise dump in text style.
306      */
dumpCSV(boolean para)307     public void dumpCSV(boolean para) {
308         if (!para) {
309             TextStyle.dumpHdr();
310             int nStyles = styles.size();
311             for (int i = 0; i < nStyles; i++) {
312                 Style s = (Style)styles.get(i);
313                 if (s.getClass().equals(TextStyle.class))
314                     ((TextStyle)s).dumpCSV();
315             }
316         } else {
317             ParaStyle.dumpHdr();
318             int nStyles = styles.size();
319             for (int i = 0; i < nStyles; i++) {
320                 Style s = (Style)styles.get(i);
321                 if (s.getClass().equals(ParaStyle.class))
322                     ((ParaStyle)s).dumpCSV();
323             }
324         }
325 
326     }
327 
328 
329     /**
330      *  Check whether a given node represents a <code>Style</code>
331      *  that should be added to the catalog, and if so, return the
332      *  class type for it.  If <code>Style</code> should not be added,
333      *  or if node is not a <code>Style</code>, return null.
334      *
335      *  @param  node          The <code>Node</code> to be checked.
336      *  @param  families      An array of <code>Style</code> families.
337      *  @param  classes       An array of class types corresponding to the
338      *                        families array.
339      *  @param  defaultClass  The default class.
340      *
341      *  @return  The class that is appropriate for this node.
342      */
getClass(Node node, String[] families, Class[] classes, Class defaultClass)343     private Class getClass(Node node, String[] families, Class[] classes,
344     Class defaultClass) {
345         NamedNodeMap attributes = node.getAttributes();
346         if (attributes != null) {
347             int len = attributes.getLength();
348             for (int i = 0; i < len; i++) {
349                 Node attr = attributes.item(i);
350                 if (attr.getNodeName().equals("style:family")) {
351                     String familyName = attr.getNodeValue();
352                     for (int j = 0; j < families.length; j++) {
353                         if (families[j].equals(familyName))
354                             return classes[j];
355                     }
356                     return defaultClass;
357                 }
358             }
359         }
360         return null;
361     }
362 
363 
364     /**
365      *  Find the family attribute of a <code>Style</code> <code>Node</code>.
366      *
367      *  @param  node  The <code>Node</code> to check.
368      *
369      *  @return  The family attribute, or null if one does not
370      *           exist.
371      */
getFamilyName(Node node)372     private String getFamilyName(Node node) {
373         NamedNodeMap attributes = node.getAttributes();
374         if (attributes != null) {
375             int len = attributes.getLength();
376             for (int i = 0; i < len; i++) {
377                 Node attr = attributes.item(i);
378                 if (attr.getNodeName().equals("style:family")) {
379                     return attr.getNodeValue();
380                 }
381             }
382         }
383         return null;
384     }
385 }
386 
387