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