blob: 0e1b294db8ca6620ed41e97a4fad3bac76b429fe [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package org.openoffice.xmerge.converter.xml;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Element;
import org.openoffice.xmerge.util.*;
import java.util.Vector;
import java.lang.reflect.Constructor;
/**
* A <code>StyleCatalog</code> holds a collection of <code>Style</code>
* objects. It is intended for use when parsing or building a DOM
* document.
*
* Each entry in the <code>StyleCatalog</code> represents a
* <code>Style</code>, and is an object which is a subclass of
* <code>Style</code>.
*
* @author David Proulx
* @see <a href="Style.html">Style</a>
*/
public class StyleCatalog {
private Vector styles; // The actual styles
/**
* Constructor
*
* @param initialEntries Expected number of entries to set
* for efficiency purposes.
*/
public StyleCatalog(int initialEntries) {
styles = new Vector(initialEntries);
}
/**
* <p>Parse the <code>Document</code> starting from <code>node</code>
* and working downward, and add all styles found, so long as their
* family name is listed in <code>families</code>. For each
* family name in <code>families</code> there must be a corresponding
* element in <code>classes</code>, which specifies the class type
* to use for that family. All of these classes must be
* subclasses of <code>Style</code>. There can be multiple
* classes specified for a particular family.</p>
*
* <p>If <code>defaultClass</code> is non-null, then all styles that
* are found will be added. Any <code>Style</code> whose family is
* not listed in <code>families</code> will be added using defaultClass,
* which, of course, must be a subclass of <code>Style</code>.
* If <code>alwaysCreateDefault</code> is true, then a class
* of type <code>defaultClass</code> will always be created,
* regardless of whether there was also a match in
* <code>families</code>.</p>
*
* <p>DJP Todo: make it recursive so that <code>node</code> can be
* higher up in the <code>Document</code> tree.</p>
*
* @param node The node to be searched for
* <code>Style</code> objects.
* @param families An array of <code>Style</code> families
* to add.
* @param classes An array of class types corresponding
* to the families array.
* @param defaultClass All <code>Style</code> objects that are
* found are added to this class.
* @param alwaysCreateDefault A class of type <code>defaultClass</code>
* will always be created, regardless of
* whether there is a match in the
* families array.
*/
public void add(Node node, String families[], Class classes[],
Class defaultClass, boolean alwaysCreateDefault) {
if (node == null)
return;
if (families == null)
families = new String[0];
if (classes == null)
classes = new Class[0];
if (node.hasChildNodes()) {
NodeList children = node.getChildNodes();
int len = children.getLength();
for (int i = 0; i < len; i++) {
boolean found = false;
Node child = children.item(i);
String name = child.getNodeName();
if (name.equals("style:default-style") || name.equals("style:style")) {
String familyName = getFamilyName(child);
if (familyName == null) {
Debug.log(Debug.ERROR, "familyName is null!");
continue;
}
for (int j = 0; j < families.length; j++) {
if (families[j].equals(familyName)) {
Class styleClass = classes[j];
callConstructor(classes[j], child);
found = true;
}
}
if ((!found || alwaysCreateDefault) && (defaultClass != null))
callConstructor(defaultClass, child);
}
}
}
}
/**
* Call the constructor of class <code>cls</code> with parameters
* <code>node</code>, and add the resulting <code>Style</code> to
* the catalog.
*
* @param cls The class whose constructor will be called.
* @param node The constructed class will be added to this node.
*/
private void callConstructor(Class cls, Node node) {
Class params[] = new Class[2];
params[0] = Node.class;
params[1] = this.getClass();
try {
Constructor c = cls.getConstructor(params);
Object p[] = new Object[2];
p[0] = node;
p[1] = this;
styles.add(c.newInstance(p));
} catch (Exception e) {
Debug.log(Debug.ERROR, "Exception when calling constructor", e);
}
}
/**
* Add a <code>Style</code> to the catalog.
*
* @param s The <code>Style</code> to add.
*/
public void add(Style s) {
styles.addElement(s);
}
/**
* Return the first <code>Style</code> matching the specified names.
*
* @param name Name to match, null is considered
* <i>always match</i>.
* @param family Family to match, null is considered
* <i>always match</i>.
* @param parent Parent to match, null is considered
* <i>always match</i>.
* @param styleClass styleClass to match, null is considered
* <i>always match</i>.
*
* @return <code>Style</code> value if all parameters match,
* null otherwise
*/
public Style lookup(String name, String family, String parent,
Class styleClass) {
int nStyles = styles.size();
for (int i = 0; i < nStyles; i++) {
Style s = (Style)styles.elementAt(i);
if ((name != null) && (s.getName() != null)
&& (!s.getName().equals(name)))
continue;
if ((family != null) && (s.getFamily() != null)
&& (!s.getFamily().equals(family)))
continue;
if ((parent != null) && (s.getParent() != null)
&& (!s.getParent().equals(parent)))
continue;
if ((styleClass != null) && (s.getClass() != styleClass))
continue;
if (s.getName() == null) continue; // DJP: workaround for "null name" problem
return s;
}
return null; // none found
}
/**
* Given a <code>Style</code> <code>s<code> return all
* <code>Style</code> objects that match.
*
* @param s <code>Style</code> to match.
*
* @return An array of <code>Style</code> objects that match, an
* empty array if none match.
*/
public Style[] getMatching(Style s) {
// Run through and count the matching styles so we know how big of
// an array to allocate.
int matchCount = 0;
int nStyles = styles.size();
for (int j = 0; j < nStyles; j++) {
Style p = ((Style)styles.elementAt(j)).getResolved();
if (p.isSubset(s)) matchCount++;
}
// Now allocate the array, and run through again, populating it.
Style[] matchArray = new Style[matchCount];
matchCount = 0;
for (int j = 0; j < nStyles; j++) {
Style p = ((Style)styles.elementAt(j)).getResolved();
if (p.isSubset(s)) matchArray[matchCount++] = p;
}
return matchArray;
}
/**
* Given a <code>Style</code> <code>s</code>, return the
* <code>style</code> that is the closest match. Not currently
* implemented.
*
* @param s <code>Style</code> to match.
*
* @return The <code>Style</code> that most closely matches.
*/
public Style getBestMatch(Style s) {
// DJP: is this needed?
// DJP ToDo: implement this
return null;
}
/**
* <p>Create a <code>Node</code> named <code>name</code> in
* <code>Document</code> <code>parentDoc</code>, and write the
* entire <code>StyleCatalog</code> to it.</p>
*
* <p>Note that the resulting node is returned, but is not connected
* into the document. Placing the output node in the document is
* left to the caller.</p>
*
* @param parentDoc The <code>Document</code> to add the
* <code>Node</code>.
* @param name The name of the <code>Node</code> to add.
*
* @return The <code>Element</code> that was created.
*/
public Element writeNode(org.w3c.dom.Document parentDoc, String name) {
Element rootNode = parentDoc.createElement(name);
int len = styles.size();
for (int j = 0; j < len; j++) {
Style s = (Style)styles.get(j);
Element styleNode = parentDoc.createElement("style:style");
if (s.getName() != null)
styleNode.setAttribute("style:name", s.getName());
if (s.getParent() != null)
styleNode.setAttribute("style:parent-style-name", s.getParent());
if (s.getFamily() != null)
styleNode.setAttribute("style:family", s.getFamily());
Element propertiesNode = (Element) s.createNode(parentDoc, "style:properties");
// if (propertiesNode.getFirstChild() != null)
// DJP: only add node if has children OR attributes
if (propertiesNode != null)
styleNode.appendChild(propertiesNode);
rootNode.appendChild(styleNode);
}
return rootNode;
}
/**
* Dump the <code>Style</code> table in Comma Separated Value (CSV)
* format
*
* @param para If true, dump in paragraph <code>Style</code>,
* otherwise dump in text style.
*/
public void dumpCSV(boolean para) {
if (!para) {
TextStyle.dumpHdr();
int nStyles = styles.size();
for (int i = 0; i < nStyles; i++) {
Style s = (Style)styles.get(i);
if (s.getClass().equals(TextStyle.class))
((TextStyle)s).dumpCSV();
}
} else {
ParaStyle.dumpHdr();
int nStyles = styles.size();
for (int i = 0; i < nStyles; i++) {
Style s = (Style)styles.get(i);
if (s.getClass().equals(ParaStyle.class))
((ParaStyle)s).dumpCSV();
}
}
}
/**
* Check whether a given node represents a <code>Style</code>
* that should be added to the catalog, and if so, return the
* class type for it. If <code>Style</code> should not be added,
* or if node is not a <code>Style</code>, return null.
*
* @param node The <code>Node</code> to be checked.
* @param families An array of <code>Style</code> families.
* @param classes An array of class types corresponding to the
* families array.
* @param defaultClass The default class.
*
* @return The class that is appropriate for this node.
*/
private Class getClass(Node node, String[] families, Class[] classes,
Class defaultClass) {
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node attr = attributes.item(i);
if (attr.getNodeName().equals("style:family")) {
String familyName = attr.getNodeValue();
for (int j = 0; j < families.length; j++) {
if (families[j].equals(familyName))
return classes[j];
}
return defaultClass;
}
}
}
return null;
}
/**
* Find the family attribute of a <code>Style</code> <code>Node</code>.
*
* @param node The <code>Node</code> to check.
*
* @return The family attribute, or null if one does not
* exist.
*/
private String getFamilyName(Node node) {
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node attr = attributes.item(i);
if (attr.getNodeName().equals("style:family")) {
return attr.getNodeValue();
}
}
}
return null;
}
}