By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
446,266 Members | 1,862 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 446,266 IT Pros & Developers. It's quick & easy.

Code Added: Facade to read & WRITE using XPath

P: n/a
I refined my attempt a little further, and the following code does
seem to work, however it has 2 major problems:

1. Very limited support for XPath features
Basic paths are supported for elements, attributes, ".", and "..",
plus also the "[expr='value']" predicate format is supported -
however, only one predicate per path step is supported, and expr must
be a relative path.

2. Poor performance
My knowledge of the full API for XML/XPath in Java is limited - so if
anyone out there knows more about it maybe you can give me some tips
to make this code work faster and use less memory.

If these don't bother you, feel free to use the following code. Enjoy!

//---------- SNIP ----------//
import java.io.*;
import java.util.*;
import org.apache.xerces.parsers.*;
import org.apache.xml.serialize.*;
import org.apache.xpath.*;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.xml.sax.*;

/**
* A Facade that encapsulates the complexities of the XML DOM API,
* providing a simple get/set interface for persisting information in
* XML using XPath.
*
* @author Will Hains
* @version 1.19
*/
public class XmlFacade
{
//// XmlFacade setup ////

/**
* The parsed XML tree.
*/
protected final Document _doc;

/**
* Loads the specified XML as the new document tree,
* discarding the previous tree.
*/
protected XmlFacade(String xmlText)
{
try
{
// Parse the XML document
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new StringReader(xmlText)));
_doc = parser.getDocument();
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error loading XML: \n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

//// Read operations ////

/**
* @return the value of the specified Node.
*/
protected String getValue(Node nd) throws Exception
{
if(nd == null) return null;

// Element
if(nd.getNodeType() == Node.ELEMENT_NODE)
{
String flattenedValue = "";
nd.normalize();
NodeIterator i = XPathAPI.selectNodeIterator
(
nd,
"descendant::text()"
);
for(Node t = i.nextNode(); t != null; t = i.nextNode())
{
flattenedValue += t.getNodeValue();
}
return flattenedValue;
}

// Non-element
else return nd.getNodeValue();
}

/**
* @return the first value found at the specified path,
* or null if the path was not found.
*/
public String get(String xpath)
{
try
{
return getValue(XPathAPI.selectSingleNode(_doc, xpath));
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error retrieving from XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

/**
* @return a List of all values found at the specified path.
*/
public List getList(String xpath)
{
try
{
List list = new Vector();
NodeIterator i = XPathAPI.selectNodeIterator(_doc, xpath);
for(Node nd = i.nextNode(); nd != null; nd = i.nextNode())
{
list.add(getValue(nd));
}
return list;
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error retrieving from XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

/**
* @return an ordered Map of keys to values rooted at the
* specified path, and defined by the specified relative key and
* value paths.
*/
public Map getMap(String xpath, String keyPath, String valuePath)
{
Map map = new LinkedHashMap();
Object[] keys = getList(xpath + "/" + keyPath).toArray();
Object[] values = getList(xpath + "/" + valuePath).toArray();
for(int i = 0, length = keys.length; i < length; i++)
{
String key = (String)keys[i];
if(key != null) map.put(key, values[i]);
}
return map;
}

//// Delete operations ////

/**
* Deletes the specified node.
*/
protected void delete(Node nd) throws Exception
{
if(nd != null)
{
if(nd.getNodeType() == Node.ELEMENT_NODE)
{
nd.getParentNode().removeChild(nd);
}
else
{
Attr a = (Attr)nd;
a.getOwnerElement().removeAttributeNode(a);
}
}
}

/**
* Deletes the value at the specified path.
*/
public void delete(String xpath)
{
try
{
delete(XPathAPI.selectSingleNode(_doc, xpath));
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error deleting from XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

//// Write operations ////

/**
* Lazily creates nodes based on the specified path.
* The implementation of this method determines the extent that
* this class can simulate read-write XPath support.
*
* @return a new node inserted into the doc at the specified path,
* or the node that already existed there.
*/
protected Node getNode(String xpath)
{
try
{
// Find the closest existing ancestor
Node nd = null;
String validPath = xpath;
for
(
int endOfValidPath = xpath.length();
endOfValidPath > 0;
endOfValidPath = validPath.lastIndexOf('/')
)
{
validPath = validPath.substring(0, endOfValidPath);
nd = XPathAPI.selectSingleNode(_doc, validPath);
if(nd != null) break;
}

// Create the remainder of the path
for
(
StringTokenizer tokens = new StringTokenizer
(
xpath.substring(validPath.length()),
"/"
);
tokens.hasMoreTokens();
)
{
// References to self can be ignored
String ndName = tokens.nextToken();
if(ndName.equals(".")) continue;

// Find predicate (supports only single condition)
String predicate = null;
int predicatePos = ndName.indexOf('[');
StringTokenizer predTokens = null;
if(predicatePos > 0)
{
predicate = ndName.substring
(
predicatePos + 1,
ndName.indexOf(']')
);
predTokens = new StringTokenizer
(
predicate,
" \t\n\r\f=\'\""
);
ndName = ndName.substring(0, predicatePos);
}

// Attribute
Node newNode;
if(ndName.indexOf('@') == 0)
{
String attrName = ndName.substring(1);
newNode = _doc.createAttribute(attrName);
((Element)nd).setAttributeNode((Attr)newNode);
}

// Element
else
{
newNode = _doc.createElement(ndName);
nd.appendChild(newNode);
}

// Update path to existing ancestor
validPath += "/" + ndName;
nd = newNode;

// Process predicates
if(predTokens != null)
{
// Find the predicate name and value
String predNdName = "";
String predNdValue = "";
try
{
while(predNdName.length() < 1)
{
predNdName = predTokens.nextToken();
}
while(predNdValue.length() < 1)
{
predNdValue = predTokens.nextToken();
}
}
catch(NoSuchElementException e)
{
throw new IllegalArgumentException
(
"This implementation does not support " +
"the predicate format: " +
predicate
);
}

// Element
if(predNdName.indexOf('@') < 0)
{
Element predicateElement = _doc.createElement(predNdName);
Node predicateTextNode = _doc.createTextNode(predNdValue);
predicateElement.appendChild(predicateTextNode);
nd.appendChild(predicateElement);
}

// Attribute
else ((Element)nd).setAttribute
(
predNdName.substring(1),
predNdValue
);
}
}

return nd;
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error retrieving/creating from XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

/**
* Sets the value of the specified node.
*/
protected void setValue(Node nd, String value) throws Exception
{
// Node is an element - replace its contents
if(nd.getNodeType() == Node.ELEMENT_NODE)
{
for(int i = 0; i < nd.getChildNodes().getLength(); i++)
{
nd.removeChild(nd.getChildNodes().item(i));
}
nd.appendChild(_doc.createTextNode(value));
}

// Node is a non-element - set its value directly
else nd.setNodeValue(value);
}

/**
* Sets the value at the specified path.
*/
public void set(String xpath, boolean value)
{
set(xpath, Boolean.toString(value));
}

/**
* Sets the value at the specified path.
*/
public void set(String xpath, int value)
{
set(xpath, Integer.toString(value));
}

/**
* Sets the value at the specified path.
*/
public void set(String xpath, java.lang.Object value)
{
if(value == null) delete(xpath);
else set(xpath, value.toString());
}

/**
* Sets the value at the specified path.
*/
public void set(String xpath, String value)
{
try
{
setValue(getNode(xpath), value);
fireXmlChanged();
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error writing to XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

/**
* Sets the values matching the specified path.
*/
public void setList(String xpath, Collection c)
{
try
{
// Make sure there is at least one matching node
if(c.size() > 0) getNode(xpath);

// Find existing nodes matching XPath query
Node prevNode = null;
NodeIterator i = XPathAPI.selectNodeIterator(_doc, xpath);
for(Iterator j = c.iterator(); j.hasNext();)
{
// Get the next value to be set
Object v = j.next();
String value = v != null ? v.toString() : null;

// A node exists in the current list position - change it
Node nd = i.nextNode();
if(nd != null) setValue(nd, value);

// The list is longer than there are existing nodes
else
{
// Element
if(prevNode.getNodeType() == Node.ELEMENT_NODE)
{
Element el = _doc.createElement(prevNode.getNodeName());
nd = prevNode.getParentNode().appendChild(el);
}

// Attribute
else
{
// Copy the previous node's owner element / parent node
Element owner = ((Attr)prevNode).getOwnerElement();
Element copy = _doc.createElement(owner.getNodeName());
owner = (Element)owner.getParentNode().appendChild(copy);

// Add a copy of the previous node and set its value
nd = _doc.createAttribute(((Attr)prevNode).getName());
owner.setAttributeNode((Attr)nd);
}

// Set the value of the new node
setValue(nd, value);
}

// Keep the current node for copying later
prevNode = nd;
}

// Remove redundant nodes when existing list is longer
List redundantNodes = new Vector();
for(Node nd = i.nextNode(); nd != null; nd = i.nextNode())
{
redundantNodes.add(nd);
}
for(Iterator r = redundantNodes.iterator(); r.hasNext();)
{
delete((Node)r.next());
}

// Clean up parent nodes that contain no other information
String voidParentsPath =
xpath.substring(0, xpath.lastIndexOf('/')) +
"[not(@*|node())]";
NodeIterator k = XPathAPI.selectNodeIterator
(
_doc,
voidParentsPath
);
for(Node n = k.nextNode(); n != null; n = k.nextNode())
{
delete(n);
}
fireXmlChanged();
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error writing to XPath: " + xpath + "\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

/**
* Sets a key-value pair list matching the specified paths to the
* contents of the specified Map.
* If you want to maintain the order of the keys, use an ordered Map
* implementation, such as TreeMap or LinkedHashMap.
*
* @param xpath the absolute path to the element or elements
* that contain the key-value pairs.
* @param keyPath the relative path from the element(s) specified by
* xpath to the element/attribute containing each key.
* @param valuePath the relative path from the element(s) specified
* by xpath to the element/attribute containing each value.
*/
public void setMap
(
String xpath,
String keyPath,
String valuePath,
Map map
)
{
try
{
// Delete existing nodes
List redundantNodes = new Vector();
NodeIterator n = XPathAPI.selectNodeIterator(_doc, xpath);
for(Node nd = n.nextNode(); nd != null; nd = n.nextNode())
{
redundantNodes.add(nd);
}
for(Iterator r = redundantNodes.iterator(); r.hasNext();)
{
delete((Node)r.next());
}

// Set map values
if(valuePath == null || valuePath.equals("")) valuePath = ".";
for(Iterator i = map.keySet().iterator(); i.hasNext();)
{
String key = (String)i.next();
set
(
xpath + "[" + keyPath + " = '" + key + "']/" + valuePath,
map.get(key)
);
fireXmlChanged();
}
}
catch(Exception e)
{
throw new IllegalArgumentException
(
"Error writing to XPath: " + xpath +
"{ " + keyPath + " => " + valuePath + " }\n" +
e.getClass().getName() + ": " +
e.getMessage()
);
}
}

public String toString()
{
try
{
OutputFormat format = new OutputFormat(_doc);
format.setStandalone(false);
format.setIndent(4);
format.setLineWidth(0);
StringWriter stringOut = new StringWriter();
XMLSerializer serial = new XMLSerializer(stringOut, format);
serial.serialize(_doc.getDocumentElement());
return stringOut.toString();
}
catch(IOException e)
{
return null;
}
}

//// Events & Listeners ////

private final Set _listeners = new HashSet();

public void addXmlFacadeListener(XmlFacadeListener l)
{
_listeners.add(l);
}

public void removeXmlFacadeListener(XmlFacadeListener l)
{
_listeners.remove(l);
}

protected void fireXmlChanged()
{
for(Iterator i = _listeners.iterator(); i.hasNext();)
{
((XmlFacadeListener)i.next()).dataChanged();
}
}
}

/**
* Unit test class.
* (Just testing the set methods for now.)
*/
class TestXmlFacade
{
public static void main(String[] args)
{
XmlFacade xml = new XmlFacade("<XF/>");
xml.set("/XF/Test[@no='1']", true);
xml.set("/XF/Test[@no='2']", 14);
xml.set("/XF/Test[@no='3']", "Hello World");
System.out.println(xml);

List list = new Vector();
list.add("Daria");
list.add("Trent");
list.add("Quinn");
list.add("Helen");
list.add("Jake");
list.add("Jane");
list.add("Tiffany");
xml.setList("/XF/Test[@no='4']/Morgendorffer/@name", list);
list.remove("Helen");
xml.setList("/XF/Test[@no='4']/Morgendorffer/@name", list);
list.add("Sandi");
xml.setList("/XF/Test[@no='4']/Morgendorffer/@name", list);
list.add(3, "Stacey");
xml.setList("/XF/Test[@no='4']/Morgendorffer/@name", list);
System.out.println(xml);

Map map = new LinkedHashMap();
map.put("Daria","F");
map.put("Quinn","F");
map.put("Helen","F");
map.put("Jane","F");
map.put("Tiffany","F");
map.put("Trent","M");
map.put("Jake","M");
xml.setMap("/XF/Test[@no='5']/Morgendorffer", "@name", null, map);
map.remove("Tiffany");
xml.setMap("/XF/Test[@no='5']/Morgendorffer", "@name", null, map);
map.put("Stacey","F");
xml.setMap("/XF/Test[@no='5']/Morgendorffer", "@name", null, map);
System.out.println(xml);
}
}
//---------- SNIP ----------//
public interface XmlFacadeListener
{
public void dataChanged();
}
//---------- SNIP ----------//
Jul 17 '05 #1
Share this Question
Share on Google+
1 Reply


P: n/a
Whoops! Shouldn't have changed the subject. That post was intended to
be in the same thread as:

http://groups.google.co.jp/groups?q=...gle.com&rnum=4
Jul 17 '05 #2

This discussion thread is closed

Replies have been disabled for this discussion.