Modifying XML Documents

BDB XML provides a mechanism with which you can modify documents stored in your containers, without using the document update procedure described in the previous section. You do this using XmlModify objects.

By using XmlModify, you can avoid the need to hold the entire replacement document in memory.

Essentially, you use XmlModify methods to identify a series of modification steps to be taken against a document. These steps allow you to add, delete, rename, and replace document nodes. You can also manipulate comments and processing instructions.

Once you have finished identifying the modification steps that you want to perform, you use XmlModify::execute() to apply the modifications to either a single document (by passing it an XmlValue object), or a set of documents (by passing it an XmlResults object).

Modification Parameters

There are a common set of parameters to the XmlModify modification methods that are worth examining before proceeding. These arguments have roughly the same meaning, regardless of the modification action being requested. They appear on the modification methods in the following order:

  • XmlQueryExpression selectionExpr

    This parameter contains an XQuery expression that selects the portion of the document to be modified. For example, if you want to rename a node, then this expression would select the node that you want to rename.

  • XmlObject type

    This parameter identifies the type of information you are inserting into the document. That is, you use this parameter to indicate whether you are inserting an element node, an attribute node, text node, a processing instruction, or a comment. See the API Reference documentation for information on how to specify these types.

  • std::string name

    Identifies the name of the information you are inserting. For example, if you are inserting an element or attribute node, then this provides the name of that node. The value of this parameter is ignored if you are inserting a text or comment node.

  • std::string content

    Identifies the content that you are inserting. If you are inserting an element node, then this must contain either a text node, or a valid child content for the node. For attribute nodes, this contains the value to which the parameter is equal. For processing instructions, this contains all of the information that appears in the processing instruction other than the processing instruction's name.

Modification Methods

XmlModify provides a series of methods that you use to identify how a document is to be modified. To define your document modification, you call these methods as many times as is required. When XmlModify::execute() is called, the documents are modified according to the instructions provided in the order that they were provided.

The XmlModify modification methods are:

XmlModify::addAppendStep()

Appends the provided content to the targeted node's content.

If you are appending an element node, then the new node is by default appended immediately after the targeted node's last child node. Note, however, that this method provides a location parameter that identifies the index of the child node at which the append operation is to be performed. Note also that if the location parameter is specified, then the new node is inserted immediately prior to the identified child node.

For example, consider the following document:

<a>
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
</a> 

For this document, if you:

  • Provide an XQuery selection expression of:

    /a
  • Indicate you are inserting an element node.

  • Provide a name of "b4".

  • Provide "my inserted child".

  • Leave the location parameter blank.

Then when the modification is executed against the document, the resulting document is:

<a>
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
   <b4>my inserted child</b4>
</a> 

However, if you give the location parameter a value of "0" (modify at the first child node), then the resulting document is:

<a>
   <b4>my inserted child</b4>
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
</a> 

If you indicate that the type of information to be inserted is an attribute node, then the location parameter is always ignored and the new attribute is inserted at the node selected by the selection expression. So for a selection expression of

/a

The resulting document is:

<a  b4="my inserted child">
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
</a> 

If you indicate that the type of information to be inserted is a comment node, and you leave the location parameter blank, then the resulting document is:

<a>
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
<!-- my inserted child -->
</a> 

If you indicate a location of 0, then the resulting document is:

<a>
<!-- my inserted child -->
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third child</b3>
</a> 

And finally, if you are inserting a text node with no location parameter, the resulting document is:

<a>
   <b1>first child</b1>
   <b2>second child</b2>
   <b3>third childmy inserted child</b3>
</a> 

Note that the selection expression you provide here must not select an attribute node or an exception is thrown when the modification is executed.

XmlModify::addInsertAfterStep()

Inserts the identified content after the selected node. Note that the node that you target for this operation cannot select the document root node or an attribute node, or an exception is thrown.

If you are inserting an element node, then the new node is inserted after the closing tag of the targeted node.

For example, consider the following document:

<a>
   <b>
     text node
   </b>
</a> 

For this document, if you:

  • Provide an XQuery selection expression of:

    /a/b
  • Indicate you are inserting an element node.

  • Provide a name of "b2".

  • Provide "my inserted node".

Then when the modification is executed against the document, the resulting document is:

<a>
   <b>
     text node
   </b>
<b2>my inserted node</b2>
</a> 

If you are inserting an attribute, then the new attribute is placed on the selected node's parent node. So for this example, the resulting document would be:

<a b2="my inserted node">
   <b>
     text node
   </b>
</a> 

XmlModify::addInsertBeforeStep()

Identical to XmlModify::addInsertAfterStep() , except that element nodes, text, comments, and processing instructions are inserted prior to the node selected by the selection expression.

Again, you cannot select the root node or an attribute node or an exception is thrown when this instruction is executed.

For example, consider the following document:

<a>
   <b>
     text node
   </b>
</a> 

For this document, if you:

  • Provide an XQuery selection expression of:

    /a/b
  • Indicate you are inserting an element node.

  • Provide a name of "b2".

  • Provide "my inserted node".

Then when the modification is executed against the document, the resulting document is:

<a>
<b2>my inserted node</b2>
   <b>
     text node
   </b>
</a> 

Attribute insertion is handled identically to XmlModify::addInsertAfterStep(). If you are inserting an attribute, then the new attribute is placed on the selected node's parent node. So for this example, the resulting document would be:

<a b2="my inserted node">
   <b>
     text node
   </b>
</a> 

XmlModify::addRemoveStep()

Removes the node targeted by the selection expression. For example, if you have the following document:

<a>
   <b>
     <c>
        text node
     </c>
   </b>
</a> 

and you provide a selection expression of:

/a/b/c

then the resulting document is:

<a>
   </b>
</a> 

Similarly, if you have the following document:

<a>
   <b>
     <c attr1="foo">
        text node
     </c>
   </b>
</a> 

and you provide a selection expression of:

/a/b/c/@attr1

then the resulting document is:

<a>
   <b>
     <c>
        text node
     </c>
   </b>
</a> 

Again, it is an error to target the document's root node with this method.

XmlModify::addRenameStep()

This method renames the selected node. For example, if you have the following document:

<a>
   <b>
     <c attr1="foo">
        text node
     </c>
   </b>
</a> 

and you provide a selection expression of:

/a

and you provide a new name of 'z', then the resulting document is:

<z>
   <b>
     <c attr1="foo">
        text node
     </c>
   </b>
</z> 

Similarly, a selection expression of:

/a/b/c/@attr1

and a new name of 'z' leaves you with:

<a>
   <b>
     <c z="foo">
        text node
     </c>
   </b>
</a> 

XmlModify::addUpdateStep()

This method updates (replaces) the contents of the targeted node with new content. If an element node is targeted, the content here is expected to be a text node. For example, given the following document:

<a>
   <b>
     <c attr1="foo">
        text node
     </c>
   </b>
</a> 

providing a selection expression of:

/a

and replacement content:

Update content

Then the resulting document is:

<a>
Update content
</a> 

If, however, you provide replacement content of:

<z>Update content</z>

(which includes the reserved characters '<' and '>'), then the method translates this into content that is appropriate for a text node. In this case, the resulting document is:

<a>
&lt;z>Update content&lt;/z>
</a> 

Similarly, providing a selection expression of:

/a/b/c/@attr1

and replacement content:

Update content

results in the following document:

<a>
   <b>
     <c attr1="Update content">
        text node
     </c>
   </b>
</a> 

Modification Example

To illustrate document modification, we will:

  1. Retrieve a document named "doc1.xml" from a container.

  2. Rename an attribute node called 'attr1' to 'myAttribute'.

  3. Add a child node called "newChild" to node "node2".

  4. Remove a node called "removeNode".

  5. Update the contents of attribute node 'myAttribute' with the string "replacement content".

The document that we will update is as follows:

<sampleDocument>
    <node1 attr1="an attribute node" />
    <removeNode>Some content to remove</removeNode>
    <node2 />
</sampleDocument>

Notice that in performing the modification, we are not required to explicitly save the modified document back into the container; that is done for us under the covers.

#include "DbXml.hpp"
...

using namespace DbXml;

...

// Get a manager object.
XmlManager myManager;

// Open a container
XmlContainer myContainer = 
    myManager.openContainer("exampleData.dbxml");

XmlQueryContext qc = myManager.createQueryContext();
XmlUpdateContext uc = myManager.createUpdateContext();
XmlModify mod = myManager.createModify();

// Build the modification object.
// Rename the attribute node from 'attr1' to 'myAttribute'.
XmlQueryExpression select = 
    myManager.prepare("/sampleDocument/node1/@attr1", qc);
mod.addRenameStep(select, "myAttribute");

// Add '<newChild>' node to '<node2>'
std::string newChildContent = "<c1>some content</c1>";
select = myManager.prepare("/sampleDocument/node2", qc);
mod.addAppendStep(select, 
                  XmlModify::Element, 
                  "newChild", 
                  newChildContent);

// Remove <removeNode> from the document
select = myManager.prepare("/sampleDocument/removeNode", qc);
mod.addRemoveStep(select);

// Replace the contents of /sampleDocument/node1/@myAttribute. Notice
// the attribute was renamed from attr1 in the first step of this
// modification. Modifications are performed in the specified order.
std::string attrContent = "replacement content";
select = myManager.prepare("/sampleDocument/node1/@myAttribute", qc);
mod.addUpdateStep(select, attrContent);

// Now retrieve the document we want to modify from the container. 
// Notice that we could have performed a query against the container,
// and then handed the entire result set to this method. In that case,
// every document contained in the result set is modified.
XmlDocument retDoc = myContainer.getDocument("doc1.xml");
XmlValue docValue(retDoc);
mod.execute(docValue, qc, uc);

// Show that the modification was performed
// and written to the container.
XmlDocument retDoc2 = myContainer.getDocument("doc1.xml");
std::string doc1String;
std::cout << retDoc2.getName() << ":\n" 
          << retDoc2.getContent(doc1String) 
          << "\n\n" << std::endl; 

When we run this code, the program displays the modified document which is now:

doc1.xml:
<sampleDocument>
    <node1 myAttribute="replacement content" />
    
    <node2><newChild><c1>some content</c1></newChild></node2>
</sampleDocument>