package org.crosswire.xml; /** * Copyright (c) 2001 CrossWire Bible Society. * Distributable under the terms of the GNU GPL V2. */ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.sql.Timestamp; import java.util.Vector; import java.util.Iterator; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import java.io.IOException; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; //import org.w3c.dom.traversal.NodeIterator; import org.apache.log4j.Logger; /** The Provider class contains all * the "read" functionality of the XMLBlock, XMLTag, * and XMLDataExement classes. * All of the "write" functionality of the three listed * class can be found in the Resolver interface and * classes. * * @version $Id: LocalXMLProvider.java,v 1.10.2.17 2001/12/31 01:57:25 troy Exp $ */ public class LocalXMLProvider implements java.io.Serializable, XMLProvider { private static Logger logger = Logger.getLogger(LocalXMLProvider.class); Node node = null; XMLMetaData myMetaData = null; public LocalXMLProvider(Node node) { this.node = node; } /** A static method to construct and return an XMLBlock by asking a * DOM Parser to parse a String of XML and assign this XMLBlock's root * to the root of the parsed tree. * @param xml the String of XML to parse. * @return the created XMLBlock. * @exception XMLParseException if the string is not well formed XML */ public static XMLBlock getRoot(String xml) throws XMLParseException { try { return new XMLBlock(stringToNode(xml)); } catch (Exception e) { throw new XMLParseException(e.getMessage()); } } /** Gets a value from a tag block (eg. <TAG>VALUE</TAG>) that is a child * of this XMLBlock. * @param key the tag name to find and for which to retrieve the value. * @return VALUE text for the tag. */ public String getValue(String key) { XMLDataElement elements[] = getElements(key); if (elements.length > 0) return elements[0].getText(); return null; } /** Gets first data element found with the given key in the data block * @param key the tag name to find * @return first data element found with the requested tag name. */ public XMLDataElement getElement(String key) { XMLDataElement elements[] = getElements(key); if (elements != null) { if (elements.length > 0) return elements[0]; } return null; } /** Gets all <TAG>VALUE</TAG> type elements that are immediate children * of this XMLBlock. * @return array of XMLDataElement objects. */ public XMLDataElement [] getElements(String key) { return getElements(node, key); } /** Gets all <TAG>VALUE</TAG> type elements that are immediate children * of this XMLBlock. * @return array of XMLDataElement objects. */ public static XMLDataElement [] getElements(Node node, String key) { Vector elements = new Vector(); for (int i = 0; i < node.getChildNodes().getLength(); i++) { Node maybe = node.getChildNodes().item(i); if (maybe.getNodeType() == Node.TEXT_NODE) continue; NodeList children = maybe.getChildNodes(); int j; for (j = 0; j < children.getLength(); j++) { Node child = children.item(j); if (child.getNodeType() != Node.TEXT_NODE) break; } if (j == children.getLength()) { // we're a data element XMLDataElement element = new XMLDataElement(maybe); if (key != null) { // assert key filter if (!element.getName().equals(key)) continue; } else { if (element.getText().trim().length() < 1) { if (element.getAttributeKeys().length > 0) continue; } } elements.addElement(element); } } XMLDataElement[] retVal = new XMLDataElement[elements.size()]; elements.copyInto(retVal); return retVal; } /** Gets all <TAG>VALUE</TAG> type elements that are immediate children * of this XMLBlock. * @return array of XMLDataElement objects. */ public XMLDataElement [] getElements() { return getElements(node, null); } /** Gets all the children of this XMLBlock. * @return array of XMLTag objects. */ public XMLTag [] getChildren() { Vector tags = new Vector(); XMLTag tag = null; // go through all the child nodes and add them to a vector for returning for (int i = 0; i < node.getChildNodes().getLength(); i++) { Node maybe = node.getChildNodes().item(i); tag = new XMLTag(maybe); tags.addElement(tag); } XMLTag[] retVal = new XMLTag[tags.size()]; tags.copyInto(retVal); return retVal; } /** Gets all block type elements that are immediate children of this * XMLBlock and that have a specified tag name. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @return array of XMLBlock objects. */ public XMLBlock [] getBlocks(String key) { return getBlocks(key, null); } public XMLBlock [] getBlocks(String key, Class clas) { // return (XMLBlock[])getTagsFromXPath(key, (clas == null) ? XMLBlock.class : clas); Vector blocks = new Vector(); XMLBlock block = null; Class[] carray = null; Constructor constr = null; for (Node maybe = node.getFirstChild(); (maybe != null); maybe = maybe.getNextSibling()) { boolean noSubblocks = true; boolean textChild = false; if (key != null) { // assert key = node name if (!maybe.getNodeName().equals(key)) continue; } // else { for (Node testNode = maybe.getFirstChild(); (testNode != null); testNode = testNode.getNextSibling()) { if (testNode.getNodeType() == Node.TEXT_NODE) { if (testNode.getNodeValue() != null) { if (testNode.getNodeValue().trim().length() > 0) { textChild = true; break; } } } else noSubblocks = false; } // } if (!textChild) { // no empty nodes or nodes with any non-whitespace text if (clas != null) { try { if (carray == null) { carray = new Class[] {Node.class}; constr = clas.getConstructor(carray); } if(constr != null) { block = (XMLBlock)constr.newInstance(new Object[] {maybe}); } else { throw new RuntimeException("No " + clas.getName() + "(Node) constructor"); } } catch (RuntimeException re) { throw re; } catch (NoSuchMethodException e) { throw new RuntimeException("No " + clas.getName() + "(Node) constructor"); } catch (Throwable e) { e.printStackTrace(); } } else { block = new XMLBlock(maybe); } if (key == null) { // if we're not asking by name (ie. we want anything that 'looks' like a block), we should not return empty elements with no attribute if (noSubblocks) if (new XMLTag(maybe).getAttributeKeys().length < 1) continue; } blocks.addElement(block); } } XMLBlock[] retVal = (XMLBlock[])java.lang.reflect.Array.newInstance((clas != null) ? clas:XMLBlock.class, blocks.size()); blocks.copyInto(retVal); return retVal; } /** Gets all block type elements that are immediate children of this * XMLBlock. * @return array of XMLBlock objects. */ public XMLBlock [] getBlocks() { return getBlocks(null); } /** Gets all block type elements that are immediate children of this * XMLBlock and that have a specified tag name and attribute value pair. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @param attr1 the attribute on which to filter. * @param att1Val the attribute value for which to filter. * @return array of XMLBlock objects. */ public XMLBlock [] getBlocks(String key, String attr1, String att1Val) { return getBlocks(key, attr1, att1Val, null); } public XMLBlock [] getBlocks(String key, String attr1, String att1Val, Class clas) { // return (XMLBlock[])getTagsFromXPath(key + "[@" + attr1 + "=\"" + att1Val + "\"]", (clas == null) ? XMLBlock.class : clas); XMLBlock [] retVal = getBlocks(key, clas); retVal = filterBlocks(retVal, attr1, att1Val); return retVal; } /** Gets all block type elements that are immediate children of this * XMLBlock and that have a specified tag name and 2 attribute value pairs. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @param attr1 the first attribute on which to filter. * @param att1Val the first attribute value for which to filter. * @param attr2 the second attribute on which to filter. * @param att2Val the second attribute value for which to filter. * @return array of XMLBlock objects. */ public XMLBlock [] getBlocks(String key, String attr1, String att1Val, String attr2, String att2Val) { return getBlocks(key, attr1, att1Val, attr2, att2Val, null); } public XMLBlock [] getBlocks(String key, String attr1, String att1Val, String attr2, String att2Val, Class clas) { XMLBlock [] retVal = getBlocks(key, attr1, att1Val, clas); retVal = filterBlocks(retVal, attr2, att2Val); return retVal; } /** Gets the first block type element that is an immediate child of this * XMLBlock and that has a specified tag name. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @return the first XMLBlock that meets the criteria, otherwise null. */ public XMLBlock getBlock(String key) { XMLBlock [] superset = getBlocks(key); if (superset != null) { if (superset.length > 0) return superset[0]; } return null; } /** Gets the first block type element that is an immediate child of this * XMLBlock and that has a specified tag name and attribute value pair. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @param attr1 the attribute on which to filter. * @param att1Val the attribute value for which to filter. * @return the first XMLBlock that meets the criteria, otherwise null. */ public XMLBlock getBlock(String key, String attr1, String att1Val) { XMLBlock [] superset = getBlocks(key, attr1, att1Val); if (superset != null) { if (superset.length > 0) return superset[0]; } return null; } /** Gets the first block type element that is an immediate child of this * XMLBlock and that has a specified tag name and 2 attribute value pairs. * @param key the tag name for which to find corresponding child XMLBlock * objects. * @param attr1 the first attribute on which to filter. * @param att1Val the first attribute value for which to filter. * @param attr2 the second attribute on which to filter. * @param att2Val the second attribute value for which to filter. * @return array of XMLBlock objects */ public XMLBlock getBlock(String key, String attr1, String att1Val, String attr2, String att2Val) { XMLBlock [] superset = getBlocks(key, attr1, att1Val, attr2, att2Val); if (superset != null) { if (superset.length > 0) return superset[0]; } return null; } /** Filters an array of XMLBlocks by an attribute, value pair * @param attr the attribute on which to filter. * @param attVal the attribute value for which to filter. * @return the new XMLBlock array filtered by the attribute, value pair. */ public XMLBlock [] filterBlocks(XMLBlock[]superset, String attr, String attVal) { return filterBlocksOneAtATime(superset, attr, attVal); } public static XMLBlock [] filterBlocksOneAtATime(XMLBlock[]superset, String attr, String attVal) { Vector blocks = new Vector(); for (int i = 0; i < superset.length; i++) { String val = superset[i].getAttribute(attr); if (val != null) { if (val.equals(attVal)) blocks.addElement(superset[i]); } } XMLBlock[] retVal = null; if (superset.length > 0) { retVal = (XMLBlock[])java.lang.reflect.Array.newInstance(superset[0].getClass(), blocks.size()); } else { retVal = new XMLBlock[blocks.size()]; } blocks.copyInto(retVal); return retVal; } /** * If this block is related to a remote copy then this method will * return true if the block has been modified since its divergence * from the remote block and false if not. * * @return boolean indicates whether the block has been modified */ public boolean isModified() { Boolean mod = (Boolean)node.getOwnerDocument().getUserData("modified"); return (mod != null && mod.booleanValue()); } /** Retrieves the String representation of this XMLBlock and children. * @return the String representation of this XMLBlock and children. */ public String toString() { // not so cool way String retVal = nodeToString(node); if (retVal != null) { int quest = retVal.indexOf('?'); if ((quest > 0) && (quest < 5)) { int start = retVal.indexOf('<', quest+1); if (start > 0) { retVal = retVal.substring(start, retVal.length()); } } } return retVal; } /** Retrieves the Formatted String representation of this XMLBlock and children. * @return the Formatted String representation of this XMLBlock and children. */ // public String formattedString() { // } /** Start of methods from XMLDataElement */ /** * This method get the text data from the XMLDataElement. * **/ public String getText() { StringBuffer retVal = new StringBuffer(""); for (int i = 0; i < node.getChildNodes().getLength(); i++) { Node child = node.getChildNodes().item(i); if (child.getNodeType() == Node.TEXT_NODE) { if (child.getNodeValue() != null) { if (child.getNodeValue().trim().length() > 0) retVal.append(node.getChildNodes().item(i).getNodeValue()); } } } return retVal.toString(); } /** Start of methods from XMLTag */ /** * This method gets the name of the tag. * @return the tag name. **/ public String getName() { return node.getNodeName(); } /** * This method gets the value of a attribute for this tag. * @param key The name of the attribute. * @return the value of the attribute. **/ public String getAttribute(String key) { String retVal = null; try { retVal = node.getAttributes().getNamedItem(key).getNodeValue(); } catch (Exception e) {} // we didn't find it, so what? NULL is fine return retVal; } /** * This method tests to see if any attributes exist. * @return true if no attributes exists, false otherwise. * **/ public boolean isAttributesEmpty() { return (node.getAttributes().getLength() == 0); } /** * This method gets all the attribute's names. * @return an array of keys * **/ public String[] getAttributeKeys() { Attr [] attrs = sortAttributes(node.getAttributes()); String[] retVal = new String[attrs.length]; for (int i = 0; i < attrs.length; i++) { retVal[i] = attrs[i].getName(); } return retVal; } public Node getNode() { return node; } /** * Sets the node. */ public void setNode(Node node) { this.node = node; } /** * Gets the XMLMetaData. */ public XMLMetaData getXMLMetaData() { return myMetaData; } /** * Sets the XMLMetaData. */ public void setXMLMetaData(XMLMetaData xmd) { this.myMetaData = xmd; } /****** Default Serialization should work private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeObject(myMetaData); out.writeObject(node); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { node = (Node) in.readObject(); } *********/ /** XMLMetaData methods */ /** Gets the state of the order. */ public char getObjectState() { return myMetaData.getObjectState(); } /** Gets the key value for requested key * from the xmlobject. */ public String getKey(int keyConstant){ return myMetaData.getKey(keyConstant); } /** Gets the Timestamp of the xmlobject. */ public Timestamp getTimeStamp(int keyConstant) { return myMetaData.getTimeStamp(keyConstant); } /** Gets the StatusMask */ public String getStatusMask() { return myMetaData.getStatusMask(); } /** Gets the current Status */ // for uniqueMetaData (NA are turned to OFF) public String getStatus() { return myMetaData.getStatus(); } public long getStatusLong() { return myMetaData.getStatusLong(); } /** * Gets the absolute XPath for this provider's XMLTag. */ public String getXPath() { String xpath = ""; String separator = ""; Node n = getNode(); while (n != null) { if (n.getNodeType() == Node.DOCUMENT_NODE) { xpath = "/" + xpath; // end if we're all the way up to the document break; } NamedNodeMap attrMap = n.getAttributes(); String attrString = ""; if (attrMap != null) { for(int j=0; j < attrMap.getLength(); j++) { Node attr = attrMap.item(j); String attVal = attr.getNodeValue(); char tick = '\"'; if (attVal.indexOf("\"") >= 0) { tick = '\''; if (attVal.indexOf("\'") >= 0) { throw new RuntimeException("data contains both ' and \", which is not allowed in XPath"); } } attrString = "[@" + attr.getNodeName() + "=" + tick + attVal + tick +"]" + attrString; } } xpath = n.getNodeName() + attrString + separator + xpath; separator = "/"; n = n.getParentNode(); } return xpath; } public XMLTag[] getTagsFromXPath(String xPath, String nsPrefix, String nsURI) { return getTagsFromXPath(xPath, null, nsPrefix, nsURI); } public XMLTag[] getTagsFromXPath(String xPath) { return getTagsFromXPath(xPath, null, null, null); } /** Gets any XMLTag objects from this XMLTag block that is a descendant * of this XMLBlock with matching XPath. * @param xPath the XPath of the desired descendant XMLTags relative to * the XPath for this XMLTag. * @return an XMLTag[] of all matching descendants or an XMLTag[] of * size 0. */ public XMLTag[] getTagsFromXPath(String xPathExpression, Class cls) { return getTagsFromXPath(xPathExpression, cls, null, null); } public XMLTag[] getTagsFromXPath(String xPathExpression, Class cls, final String nsPrefix, final String nsURI) { Vector retVals = new Vector(); Constructor ctor = null; if (cls != null) { try { Class[] carray = new Class[] {Node.class}; ctor = cls.getConstructor(carray); if(ctor == null) { throw new RuntimeException("No " + cls.getName() + "(Node) constructor"); } } catch (RuntimeException re) { throw re; } catch (NoSuchMethodException e) { throw new RuntimeException("No " + cls.getName() + "(Node) constructor"); } catch (Throwable e) { e.printStackTrace(); } } try { XPath xPath = XPathFactory.newInstance().newXPath(); if (nsPrefix != null && nsURI != null) { xPath.setNamespaceContext(new NamespaceContext() { public String getNamespaceURI(String prefix) { return prefix.equals(nsPrefix) ? nsURI : null; } public Iterator getPrefixes(String val) { return null; } public String getPrefix(String uri) { return null; } }); } else { final String rootNamespace = getNode().getNamespaceURI(); if (rootNamespace != null) { xPath.setNamespaceContext(new NamespaceContext() { public String getNamespaceURI(String prefix) { if ("ns".equals(prefix)) return rootNamespace; else return null; } public String getPrefix(String uri) { return null; } public Iterator getPrefixes(String uri) { return null; } }); } } NodeList nodes = (NodeList) xPath.evaluate(xPathExpression, getNode(), XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); ++i) { Node n = nodes.item(i); retVals.add((ctor == null) ? new XMLTag(n) : (XMLTag)ctor.newInstance(new Object[] {n})); } } catch (Exception e) {e.printStackTrace();} XMLTag [] retTags = null; if (retVals.size() < 1) { retTags = (XMLTag[])java.lang.reflect.Array.newInstance(((ctor == null) ? XMLTag.class : cls), 0); } else { retTags = (XMLTag[])java.lang.reflect.Array.newInstance(((ctor == null) ? XMLTag.class : cls), retVals.size()); retVals.copyInto(retTags); } return retTags; } /** Gets the XMLBlock object that is the parent of this XMLTag. * @return an XMLBlock or null if this XMLTag is the root tag. */ public XMLBlock getParent() { Node node = getNode(); Node parent = node.getParentNode(); if (parent.getNodeType() == Node.DOCUMENT_NODE) { return null; } return (new XMLBlock(parent)); } public boolean equals(Object obj) { LocalXMLProvider other = null; try { other = (LocalXMLProvider) obj; if (node == other.node) return true; } catch (Exception e) { } return false; } public static String nodeToString(Node node) { return nodeToString(node, false); } public static String nodeToString(Node node, boolean canonical) { java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); nodeToStream(node, out, canonical); return out.toString(); } public static void nodeToStream(Node node, OutputStream out) { nodeToStream(node, out, false); } public static void nodeToStream(Node node, OutputStream out, boolean canonical) { try { javax.xml.transform.TransformerFactory.newInstance().newTransformer().transform(new javax.xml.transform.dom.DOMSource(node), new javax.xml.transform.stream.StreamResult(out)); } catch (Exception e) { e.printStackTrace(); } } public static Node stringToNode(String xml) throws XMLParseException { return streamToNode(new ByteArrayInputStream(xml.getBytes())); } public static Node streamToNode(InputStream in) throws XMLParseException { try { DOMResult result = new DOMResult(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // dbf.setNamespaceAware(true); Document doc = dbf.newDocumentBuilder().parse(in); Node n = null; for (n = doc.getFirstChild(); n != null; n = n.getNextSibling()) { if (n.getNodeType() == Node.ELEMENT_NODE) break; } return n; } catch (ParserConfigurationException e) { throw new XMLParseException(e.toString()); } catch (SAXException e) { throw new XMLParseException(e.toString()); } catch (IOException e) { throw new XMLParseException(e.toString()); } } static Attr[] sortAttributes(NamedNodeMap attrs) { int len = (attrs != null) ? attrs.getLength() : 0; Attr array[] = new Attr[len]; for ( int i = 0; i < len; i++ ) { array[i] = (Attr)attrs.item(i); } for ( int i = 0; i < len - 1; i++ ) { String name = array[i].getNodeName(); int index = i; for ( int j = i + 1; j < len; j++ ) { String curName = array[j].getNodeName(); if ( curName.compareTo(name) < 0 ) { name = curName; index = j; } } if ( index != i ) { Attr temp = array[i]; array[i] = array[index]; array[index] = temp; } } return (array); } // sortAttributes(NamedNodeMap):Attr[] }