More JDOM Classes






More JDOM Classes

The following sections discuss additional JDOM classes.

The Namespace Class

This section will briefly cover namespace support in JDOM with the Namespace class. This class acts as both an instance variable and a factory within the JDOM architecture. When you need to create a new namespace, either to create a new element or attribute or for searching for existing elements and attributes, you use the static getNamespace( ) methods on this class:

// Create namespace with prefix
Namespace schemaNamespace = 
    Namespace.getNamespace("xsd", "http://www.w3.org/XMLSchema/2001");

// Create namespace without prefix
Namespace javaxml3Namespace =
    Namespace.getNamespace("http://www.oreilly.com/javaxml3");

As you can see, there is a version for creating namespaces with prefixes and one for creating namespaces without prefixes, in which case the namespace URI is set as the default namespace. Either version can be used, with the resulting Namespace object then supplied to the various JDOM methods:

// Create element with namespace
Element schema = new Element("schema", schemaNamespace);

// Search for children in the specified namespace
List chapterElements = contentElement.getChildren("chapter", javaxml3Namespace);

// Declare a new namespace on this element
catalogElement.addNamespaceDeclaration(
    Namespace.getNamespace("tng", "http://www.truenorthguitars.com"));

These are all fairly self-explanatory. Also, when XML serialization is performed with the various outputters (SAXOutputter, DOMOutputter, and XMLOutputter), the namespace declarations are automatically handled and added to the resulting XML.

One final note: in JDOM, namespace comparison is based solely on URI. That is, two Namespace objects are equal if their URIs are equal, regardless of prefix. This is in keeping with the letter and spirit of the XML Namespace specification, which indicates that two elements are in the same namespace if their URIs are identical, regardless of prefix. Look at this XML document fragment:

<guitar xmlns="http://www.truenorthguitars.com">
  <ni:owner xmlns:ni="http://www.newInstance.com">
    <ni:name>Brett McLaughlin</ni:name>
    <tng:model xmlns:tng="http://www.truenorthguitars.com>Model 1</tng:model>
    <backWood>Madagascar Rosewood</backWood>
  </ni:owner>
</guitar>

Even though they have varying prefixes, the elements guitar, model, and backWood are all in the same namespace. This holds true in the JDOM Namespace model as well. In fact, the Namespace class's equals( ) method will return equal based solely on URIs, regardless of prefix.

XSL Transformations with JDOM

JDOM includes a simple class for performing XSL transformations called org.jdom.transform.XSLTransformer. Each instance of this class represents one stylesheet and wraps a Templates object from the TrAX API discussed in Chapter 7. You can create an XSLTransformer object by calling the constructor with the stylesheet as a JDOM Document object, a java.io.File, a java.io.InputStream, a java.io.Reader, or a system ID. Figure transforms an XML document stored as a Document object inputDocument with a stylesheet in the file stylesheet.xsl.

Transformation from Document to Document

XSLTransformer transformer = new XSLTransformer(new File("stylesheet.xsl");
Document outputDocument = transformer.transform(inputDocument);

In addition to the basic support provided by the XSLTransformer class, JDOM supplies implementations of the javax.xml.transform.Source and javax.xml.transform.Result interfaces from the TrAX API. These implementations, called org.jdom.transform. JDOMSource and org.jdom.transform.JDOMResult, let you use JDOM Document objects as the source of XSL transformations done using the TrAX API and to save the result of a TrAX transformation in a JDOM Document object. You would use these classes rather than XSLTransformer if you needed to use TrAX directly, such as when you were transforming to or from something other than JDOM, or when you needed to tweak the configuration of the TRansformerFactory. Figure transforms an XML document stored in the file input.xml with the stylesheet in the file stylesheet.xsl and stores the result in a JDOM Document object.

Transformation from a file to anorg.jdom.Document object usingTrAX directly

TransformerFactory factory = TransformerFactory.newInstance(  );
Transformer transformer = factory.
       newTransformer(new StreamSource("stylesheet.xsl"));
StreamSource in = new StreamSource("input.xml");
JDOMResult out = new JDOMResult(  );
transformer.transform(in, out);
Document resultDocument = out.getDocument(  );

In addition to a Document, both JDOMSource and JDOMResult can wrap one or more JDOM Element objects. The XSLTransformer utility class uses JDOMSource and JDOMResult internally and is therefore is a good point of reference for their usage.

XPath and JDOM

JDOM provides support for evaluating XPath expressions on JDOM objects, including Documents and Elements. As with XSLT, JDOM delegates the processing of the XPath expression to a separate library. By default, this library is Jaxen, the JAR files for which are included in the JDOM distribution as described in the earlier section "The JDOM Distribution."

The abstract XPath class, UML for which is in Figure, has a newInstance( ) method for creating concrete implementations of the XPath class, along with two utility methods for performing simple XPath expression evaluation with a single method call. The primary advantage of using the newInstance( ) method is the same as using JAXP's XPathExpression class (discussed in Chapter 7)your expression is precompiled so that you avoid the overhead of expression compilation per evaluation. In addition, compiled XPath instances are the only way to use variables or namespaces in XPath expressions.

The JDOM XPath class


If you compare JDOM's XPath class to JAXP's XPath class, you'll notice that JDOM does not support custom XPath functions. If you need this capability, you should take a look at using the Jaxen library directly (for more on Jaxen, see http://jaxen.codehaus.org) or using the Saxon library from http://www.saxonica.com. Saxon has the added benefits that it provides support for XPath 2 and plugs into JAXP 1.3 to allow you to use the JAXP XPath API with JDOM documents.


Using the XPath class's static methods couldn't be simpler. First, you need to parse a document or create a Document or Element object.[] Once you have this object, you simply pass it to XPath.selectNodes( ) or XPath.selectSingleNode( ) along with your XPath expression:

[] It is possible to evaluate XPath expressions against an Attribute object, but this is an unusual case.

SAXBuilder builder = new SAXBuilder(  );
Document doc = builder.build(new File("tds.xml"));
Attribute attr = (Attribute) XPath.selectSingleNode(doc, "[email protected]");

Compiling this XPath expression to an XPath instance is only slightly different:

XPath path = XPath.newInstance("[email protected]");
Attribute attr = (Attribute) path.selectSingleNode(doc);

And we can also get the result of the expression as a String:

String name = path.valueOf(doc);

Namespaces and XPath variables are supported through the addNamespace( ) and setVariable( ) methods, respectively. The GuestManager class from Chapter 7 can be rewritten with namespace support as:

package javaxml3;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;

public class JDOMGuestManagerNS {

    private static Namespace NS_GUEST = Namespace.getNamespace("g",
            "uri:comedy:guest");

    private static Namespace NS_SCHEDULE = Namespace.getNamespace("s",
            "uri:comedy:schedule");

    private Document document;

    private SimpleDateFormat xmlDateFormat = new SimpleDateFormat("MM.dd.yy");

    private XPath xPath;

    public JDOMGuestManagerNS(String fileName) throws JDOMException,
            IOException {
        SAXBuilder builder = new SAXBuilder(  );
        document = builder.build(new File(fileName));

        xPath = XPath.newInstance("/s:schedule/s:show[@date=$date]/g:guest");
        xPath.addNamespace(NS_SCHEDULE);
        xPath.addNamespace(NS_GUEST);
    }

    public synchronized Element getGuest(Date guestDate) throws JDOMException {
        String formattedDate = xmlDateFormat.format(guestDate);
        xPath.setVariable("date", formattedDate);
        return (Element) xPath.selectSingleNode(document);
    }

    public static void main(String[] args) throws Exception {
        JDOMGuestManagerNS gm = new JDOMGuestManagerNS("tds_ns.xml");
        Element guest = gm.getGuest(new Date(2006, 5, 14));
        System.out.println(guest.getChildText("name", NS_GUEST));
    }

}

Although this is a reduction in the number of lines of code (mostly because we did not need to write classes to resolve the variable or namespaces), we do lose some of the flexibility that comes with the JAXP XPath API discussed in Chapter 7. In the case of XPath variables, the XPathVariableResolver interface from JAXP allows you to resolve variable values using whatever logic you want. For example, in a servlet context, you could resolve variables based on request parameters or session attributes by writing an implementation of XPathVariableResolver that wraps the Request or Session objects. This is not possible with JDOM's XPath class without a call to setVariable( ) for each parameter or attribute. But in the vast majority of cases, JDOM's XPath support will fit your needs. If it doesn't and more dynamic variables or custom function support are required, you can use the JAXP XPath API instead.


JDOM Filters

The "XMLProperties" section outlined the use of the getContent( ) method of both Document and Element objects. This method is actually defined on the Parent interface, which both Document and Element implement. Parent defines a number of methods in addition to getContent( ):

public interface Parent extends Cloneable, Serializable {
    int getContentSize(  );
    int indexOf(Content child);
    List cloneContent(  );
    Content getContent(int index);
    List getContent(  );
    List getContent(Filter filter);
    List removeContent(  );
    List removeContent(Filter filter);
    boolean removeContent(Content child);
    Content removeContent(int index);
    Object clone(  );
    Iterator getDescendants(  );
    Iterator getDescendants(Filter filter);
    Parent getParent(  );
    Document getDocument(  );
}

Most significant are the methods getContent( ), getDescendants( ), and removeContent( ). Each of these has an overloaded version that accepts an instance of the org.jdom.filter.Filter interface.

The Filter performs a similar objective to the filter interfaces and classes we've discussed previously in this book. A call to the no argument getContent( ) method returns a List of the content of the element (which could be empty or could be any combination of Text, Element, Comment, ProcessingInstruction, and EntityRef objects). By creating and passing a Filter object, we can limit the resulting List. Likewise for getdescendants( ), which is a recursive version of getContent( ). The version of removeContent( ) that accepts a Filter object will remove only the content that matches that filter.

The Filter interface defines one method match( )that returns true if the object should be returned and false if it should not. Figure contains a simple filter that will only pass through Element objects.

Simple JDOM filter

package javaxml3;

import org.jdom.Element;
import org.jdom.filter.Filter;

public class ElementOnlyFilter implements Filter {

    public boolean matches(Object obj) {
        return obj instanceof Element;
    }

}

This class can be used to perform a variety of functions:


myElement.getContent(new ElementOnlyFilter( ))

Returns a list of Element objects that are children of myElement


myElement.getDescendants(new ElementOnlyFilter( ));

Returns all Element objects that are descended from myElement


myElement.removeContent(new ElementOnlyFilter( ));

Removes all elements from the list of child elements of myElement

JDOM includes a handful of Filter implementations that you should check before writing your own.

ElementFilter

Using its zero-argument constructor, org.jdom.filter.ElementFilter provides the same functionality as our ElementOnlyFilter class from Figure. However, it goes several steps further and allows you to specify a local name, a Namespace, or both to further limit the objects matched by the filter.

ContentFilter

The class org.jdom.filter.ContentFilter allows you to filter for any object type or combination of object types. By default, it will accept any valid JDOM object. You can create a customized filter by passing a mask to the constructor of ContentFilter. This mask can be constructed by combining constants defined by ContentFilter with the | operator. For example, a filter that accepts only Element and Comment objects could be created with:

new ContentFilter(ContentFilter.ELEMENT | ContentFilter.COMMENT);

ContentFilter also provides setter methods to selectively enable or disable acceptance of a particular object type:

ContentFilter filter = new ContentFilter(  ); // match(  ) now always
                                            // returns true
filter.setCommentVisible(false);
filter.setPIVisible(false);

This creates a filter that accepts all objects except for comments and processing instructions. If you want to only enable a few types, this syntax could get wordy; so instead you can pass false to the constructor to get a filter that doesn't accept anything and then selectively enable just the types you want:

ContentFilter filter = new ContentFilter(false); // match(  ) now always
                                                 // returns false
filter.setElementVisible(true);
filter.setCommentVisible(true);

NegateFilter

To reverse the result of a Filter's match( ) method, use org.jdom.filter.NegateFilter. For example, to create a filter that accepts anything except Element objects, you could write:

Filter filter = new NegateFilter(new ElementOnlyFilter(  ));

OrFilter and AndFilter

The last of the provided filters, org.jdom.filter.OrFilter and org.jdom.filter.AndFilter allow you to combine other Filter implementations by using the logical OR and AND operations to create complex filtering logic. A filter that accepts comments, elements with the local name "person," and elements in the namespace http://www.oreilly.com/javaxml3 could be created with:

Namespace ora = Namespace.getNamespace("http://www.oreilly.com/javaxml3");
Filter filter = new OrFilter(new ContentFilter(ContentFilter.COMMENT),
        new OrFilter(new ElementFilter("person"), new ElementFilter(ora));

Note that I am nesting an OrFilter within another OrFilter. This is perfectly legal and appropriate. But please be kind to anyone who might be reading your code in the future by limiting the amount of nesting that occurs in one assignment. The code above is more readable as:

Fitler filter = new OrFilter(new ElementFilter("person"), new ElementFilter(ora));
filter = new OrFilter(filter, new ContentFilter(ContentFilter.COMMENT));

The EntityRef Class

Next up on the JDOM internals list is the EntityRef class. This is another class that you may not have to use much in common cases, but is helpful to know for special coding needs. This class represents an XML entity reference in JDOM, such as the OReillyCopyright entity reference in the contents.xml document I have been using in examples:

<ora:copyright>&OReillyCopyright;</ora:copyright>

This class allows for setting and retrieval of a name, public ID, and system ID, just as is possible when defining the reference in an XML DTD or schema. It can appear anywhere in a JDOM content tree, like the Elements and Text nodes. However, like Text nodes, an EntityRef class is often a bit of an irritation in the normal case. For example, in the contents.xml document, modeled in JDOM, you are likely to be more interested in the textual value of the reference (the resolved content) rather than the reference itself. In other words, when you invoke getContent( ) on the copyright Element in a JDOM tree, you'd like to get "Copyright O'Reilly, 2007" or whatever other textual value is referred to by the entity reference. This is much more useful (again, in the most common cases) than getting a no-content indicator (an empty string), and then having to check for the existence of an EntityRef. For this reason, by default, all entity references are expanded when using the JDOM builders ( SAXBuilder and DOMBuilder) to generate JDOM from existing XML. You will rarely see EntityRefs in this default case, because you don't want to mess with them. However, if you find you need to leave entity references unexpanded and represented by EntityRefs, you can use the setExpandEntities( ) method on the builder classes:

// Create new builder
SAXBuilder builder = new SAXBuilder(  );

// Do not expand entity references (default is to expand these)
builder.setExpandEnitites(false);

// Build the tree with EntityRef objects (if needed, of course)
Document doc = builder.build(inputStream);

In this case, you may have EntityRef instances in the tree (if you were using the contents.xml document, for example). And you can always create EntityRefs directly and place them in the JDOM tree:

// Create new entity reference
EntityRef ref = new EntityRef("TrueNorthGuitarsTagline");
ref.setSystemID("tngTagline.xml");

// Insert into the tree
tagLineElement.addContent(ref);

When serializing this tree, you get XML like this:

<guitar>
  <tagLine>&TrueNorthGuitarsTagline;</tagLine>
</guitar>

And when reading the document back in using a builder, the resulting JDOM Document would depend on the expandEntities flag. If it is set to false, you'd get the original EntityRef back again with the correct name and system ID. With this value set to false (the default), you'd get the resolved content. A second serialization might result in:

<guitar>
  <tagLine>two hands, one heart</tagLine>
</guitar>

While this may seem like a lot of fuss over something simple, it's important to realize that whether or not entities are expanded can change the input and output XML you are working with. Always keep track of how the builder flags are set, and what you want your JDOM tree and XML output to look like.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows