Handling xs:any with the XMLBeans API

Compiling schema for use with XMLBeans generates a kind of custom API specific to your schema. This API includes types with accessors designed to get and set parts of the XML defined by the schema. But if you've compiled schema that includes xs:any particles, you may have noticed that XMLBeans doesn't generate accessors for these these particles.

For example, imagine the accessors generated by compiling the following schema snippet:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://xmlbeans.apache.org/samples/any"
    targetNamespace="http://xmlbeans.apache.org/samples/any" elementFormDefault="qualified">
    <xs:element name="root">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="stringelement"/>
                <xs:any processContents="lax"/>
                <xs:element name="arrayofany">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="stringelement" type="xs:string"/>    
    <xs:complexType name="ListOfStrings">
        <xs:sequence>
            <xs:element ref="stringelement" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="id" type="xs:string"/>
    </xs:complexType>
</xs:schema>

After compilation, you'd have the follow methods for Root, the type that gives you access to the <root> element:

addNewArrayofany()

getArrayofany()

getStringelement()

setArrayofany(Arrayofany)

setStringelement(String)

xgetStringelement()

xsetStringelement(XmlString)

What's missing? There's no getAny or setAny. How do you get or set the <root> element's second child? As it turns out, you do this by leaving behind (at least for a moment) JavaBeans-style accessors, and picking up any of a number of tools the API provides. These tools include:

Using Cursors to Add XML

As described in Navigating XML with Cursors, with an XmlCursor instance you can traverse your XML instance's full infoset. A cursor views XML as tokens, and you move a cursor from one token to another as if they were cars in a train.

The following example illustrates how you might, in the course of building out the <root> document, create a second child element <anyfoo> where schema specifies xs:any. You add the element by creating it with a cursor, then (in lieu of a setter) using the XmlCursor.copyXml or XmlCursor.moveXml method to put the element where it needs to go.

// Start by creating a <root> element that will contain
// the children built by this code.
RootDocument rootDoc = RootDocument.Factory.newInstance();
RootDocument.Root root = rootDoc.addNewRoot();

// Add the first element, <stringelement>.
root.setStringelement("some text");

// Create an XmlObject in which to build the second
// element in the sequence, <anyfoo>. Here, the 
// XmlObject instance is simply a kind of incubator
// for the XML. Later the XML will be moved into the
// document this code is building.
XmlObject anyFoo = XmlObject.Factory.newInstance();

// Add a cursor to do the work of building the XML.
XmlCursor anyFooCursor = anyFoo.newCursor();
anyFooCursor.toNextToken();

// Add the element in the schema's namespace, then add
// element content.
anyFooCursor.beginElement(new QName(m_namespaceUri, "anyfoo"));
anyFooCursor.insertChars("some text");

// Move the cursor back to the new element's top, where 
// it can grab all of the element's XML.
anyFooCursor.toStartDoc();
anyFooCursor.toNextToken();

// Finally, move the XML into the <root> document by moving it
// from a position at one cursor to a position at
// another.
XmlCursor rootCursor = root.newCursor();
rootCursor.toEndToken();
anyFooCursor.moveXml(rootCursor);

You might find that this build-and-move-cursor-to-cursor pattern is common when you're creating or moving XML when accessors aren't available. For example, you could do the same sort of thing when your schema defines a type that you want to place into an xs:any space in an instance. The following code adds a <stringelement> element as a child of the <arrayofany> element, which schema defines as containing a sequence of xs:any particles. The <stringlement> element is simple, but it could just as easily be a complex schema type.

// Create a simple <stringelement>.
StringelementDocument stringElementDoc = 
    StringelementDocument.Factory.newInstance();        
stringElementDoc.setStringelement("some text");
XmlCursor stringElementCursor = stringElementDoc.newCursor();
stringElementCursor.toFirstContentToken();

// Add a cursor to mark the position to which the new child 
// XML will be moved.
XmlCursor arrayCursor = arrayOfAny.newCursor();
arrayCursor.toNextToken();

// Move the new <stringelement> into place.
stringElementCursor.moveXml(arrayCursor);
stringElementCursor.dispose();

Using XPath and the selectPath Method to Find XML

XPath is a convenient, direct way to get at specific chunks of XML. In the XMLBeans API, you execute XPath expressions with the XmlObject.selectPath or XmlCursor.selectPath methods. The example in Java below assumes the following instance conforming to the schema introduced at the beginning of this topic:

<root xmlns="http://xmlbeans.apache.org/samples/any">
    <stringelement>some text</stringelement>
    <anyfoo>some text</anyfoo>
    <arrayofany>
        <stringelement>some text</stringelement>
        <someelement>
            <stringlist id="001">
                <stringelement>string1</stringelement>
                <stringelement>string2</stringelement>
            </stringlist>
        </someelement>
    </arrayofany>
</root>

The following code uses XPath to reach the <stringelement> element because there is no accessor available. It then shifts the XML around a little, moving <stringelement> up in the hierarchy to replace its parent, <someelement>.

public boolean editExistingDocWithSelectPath(RootDocument rootDoc)
{
    String namespaceUri = "http://xmlbeans.apache.org/samples/any";

    // Put a cursor at the top of the <arrayofany> element.
    XmlCursor selectionCursor = rootDoc.getRoot().getArrayofany().newCursor();

    // Use XPath and cursor movement to position the cursor at
    // the <stringlist> element.
    String namespaceDecl = "declare namespace any='" + namespaceUri + "'; ";
    selectionCursor.selectPath(namespaceDecl + 
        "$this//any:someelement/any:stringlist");
    selectionCursor.toNextSelection();

    // Create a new cursor at the same location and move it to 
    // <stringelement>'s <someelement> parent.
    XmlCursor editCursor = selectionCursor.newCursor();
    editCursor.toParent();

    // Move the <stringelement> element to this position, displacing 
    // the <someelement> downward. Remove the <someelement> XML, 
    // effectively replacing <someelement> with <stringlist>.
    selectionCursor.moveXml(editCursor);
    editCursor.removeXml();
    editCursor.dispose();

    return rootDoc.validate();
}

Using the selectChildren Method to Find XML

The XmlObject.selectChildren method you can retrieve an array of the child elements of a specified name. The method is overloaded to take java.xml.namespace.QName instances or strings as parameters. The following code (based on the instance used in the preceding example) simply finds the <anyfoo> element, an xs:any, and replaces it with an <anybar> element.

public boolean editExistingDocWithSelectChildren(RootDocument rootDoc)
{
    String namespaceUri = "http://xmlbeans.apache.org/samples/any";
    RootDocument.Root root = rootDoc.getRoot();
        
    // Select the <anyfoo> children of <root>.
    XmlObject[] stringElements =
        root.selectChildren(new QName(m_namespaceUri, "anyfoo"));

    // If the element is there, replace it with another element.
    if (stringElements.length > 0)
    {
        XmlCursor editCursor = stringElements[0].newCursor();
        editCursor.removeXml();
        editCursor.beginElement(new QName(namespaceUri, "anybar"));
        editCursor.insertChars("some other text");                
        editCursor.dispose();
    }
    return rootDoc.validate();
}

Using the DOM API to Find XML

Through the getDomNode method (exposed by XmlObject and types generated from schema), you can get a live DOM node representing your XML. For example, calling myElement.getDomNode() will return a org.w3c.dom.Node instance representing the XML bound to myElement. If you're already familiar with DOM-style access to XML, this can be a familiar alternative for handling xs:any instances.

Using the instance introduced earlier in this topic, the following example adds a new <bar> element between the first and second children of the <arrayofany> element. The code also ensures that the first and second children are <stringelement> and <someelement>, respectively.

public boolean editExistingDocWithDOM(RootDocument rootDoc)
{
    RootDocument.Root root = rootDoc.getRoot();
        
    // Get the DOM nodes for the <arrayofany> element's children.
    Node arrayOfAnyNode = root.getArrayofany().getDomNode();

    // You don't have get* accessors for any of the <arrayofany> 
    // element's children, so use DOM to identify the first
    // and second elements while looping through the child list.
    NodeList childList = arrayOfAnyNode.getChildNodes();
    Element firstElementChild = null;
    Element secondElementChild = null;

    // Find the first child element and make sure it's
    // <stringelement>.
    for (int i = 0; i < childList.getLength(); i++)
    {
        Node node = childList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            if (node.getLocalName().equals("stringelement"))
            {
                firstElementChild = (Element)node;                
                break;
            }
        }
    }
    if (firstElementChild == null) {return false;}

    // Find the second child element and make sure it's
    // <someelement>.
    Node node = firstElementChild.getNextSibling();
    do 
	{
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            if (node.getLocalName().equals("someelement"))
            {
                secondElementChild = (Element)node;
                break;
            }
        }
        node = node.getNextSibling();
    } while (node != null);
    if (secondElementChild == null) {return false;}
    
    // Create and insert a new <bar> element.
    Element fooElement = 
        secondElementChild.getOwnerDocument().createElementNS("http://openuri.org","bar");
    Node valueNode = 
        fooElement.getOwnerDocument().createTextNode("some text");
    fooElement.appendChild(valueNode);
    arrayOfAnyNode.insertBefore(fooElement, secondElementChild);
    
    return rootDoc.validate();
}

Related Topics

Getting Started with XMLBeans