Download





The XPathEditor and XPathChangeNavigator Classes

The XPathEditor and XPathChangeNavigator classes, both derived from the XPathNavigator2 class, add their own particular functionality based on the cursor navigation model of traversing the document. By accepting XPath queries via the Select method, all these XPathNavigator2 classes allow you to quickly find nodes in the tree and then perform specific operations on the nodes depending on the type of navigator created.

In the Technology Preview release you will notice that there is an XPathNavigator2 class. This is due to be combined with the existing System.Xml version 1.x XPathNavigator class in the next release. Hence this chapter does not cover the functionality of the XPathNavigator2 class that is already exposed by the version 1.x XPathNavigator.

The XPathEditor Class

The XPathEditor class is designed to leverage the XPathNavigator2 and the XmlWriter functionality. The XPathEditor provides a cursor-based API for editing an XML tree and provides additional helpers to allow you to perform edit operations with a single method call. The following list shows the features provided by the XPathEditor in relation to the XPathDocument2 class.

  • Multiple XPathNavigator2/XPathEditor instances: The XPath Document2 allows multiple XPathNavigator2 and/or XPathEditor instances to be in use at the same time. These can be positioned on the same node or on different nodes in the same tree.

  • Not multithread write safe: As mentioned above, the XPath Document2 can have multiple XPathEditors in use at the same time. The XPathEditor allows users to obtain an XmlWriter on any node and to start inserting a subtree. However, the XPathEditor is a single-user XML store and does no locking of the XPathDocument2 while editing. If multiple XPathEditors are created on separate threads, the application is responsible for ensuring that locks are taken out and released appropriately.

  • XmlWriter implementation: The XPathEditor uses the XmlWriter for inserting new node trees. The CreateFirstChild, CreateNext Sibling, and CreateAttributes methods return an XmlWriter that can be used to write new nodes at different locations in the XPathDocument2 instance. The XmlWriter exposed by the XPath Document2 allows writing of well-formed XML, otherwise the write operation fails and an XmlException is thrown. For example, it is invalid to write attributes at the root node of the document.

  • Namespace nodes: The XPathDocument2 does not allow creation of namespace nodes nor the updating of namespace nodes on existing elements. New namespace nodes can be created while inserting new elements using the XmlWriter. The scope for these new namespace nodes is the scope of the newly inserted elements.

  • Text nodes: The XPathEditor provides the helpers CreateFirst ChildElement and CreateNextSiblingElement, which take a string value parameter. These methods create new element nodes with appropriate text values. This text value can be updated by calling the SetValue method after positioning the XPathEditor on this text node. Calling SetValue on an element is not legal and results in an XmlException.

  • Delete behavior: The XPathEditor API allows users to navigate to a node in the tree and call DeleteCurrent to delete the node. This node and its subtree are removed from the main tree and are no longer reachable from its parent. DeleteCurrent is not a valid operation when the XPathEditor is positioned on the document root. As a result of a delete operation there are further clarifications on the behavior of the XML store for existing navigators and change tracking.

    • Existing navigators: The delete operation doesn't affect the position of any other navigators. There are potentially other XPath Navigator2 or XPathEditor instances that are positioned on a deleted node or somewhere in the node's subtree. These navigators are valid in the sense that they can move around in the subtree. The XPathNavigator2.MoveTo(XPathNavigator2) method can be used to move these navigators back into the main tree if necessary. Although no further edit operations are allowed on a node marked as deleted, the user can continue to make changes to other nodes in the subtree, and these changes are visible through the XPathChangeNavigator.

    • Change tracking: Even though a node is deleted, edit operations are allowed in the subtree, and these changes are visible through the XPathChangeNavigator. This behavior gets further refined once AcceptChanges is called on a deleted node. Calling AcceptChanges on a deleted node marks the node and its subtree as permanently removed from the XPathDocument2 (i.e., these nodes are no longer considered part of the document). Any further changes to these nodes, even though they are allowed, are not tracked by the XPathChangeNavigator.

  • Select method: The XPathEditor extends the XPathNavigator2 class, with the Select method returning an IEnumerator that can be used to enumerate over the results of the Select operation. The XPathEditor implementation of Select is slightly different in the sense that Enumerator.Current is an XPathEditor pointing to the node. This allows you to edit the results of a Select statement when called on the XPathEditor. For example, the code in Listing 6.4 selects all the price elements and then deletes each one by iterating over the set of nodes that are returned.

Selecting and Deleting Nodes with an XPathEditor
Dim doc As New XPathDocument2()
doc.Load("books.xml")

Dim editor As XPathEditor = doc.CreateXPathEditor()

Dim iter As IEnumerable = editor.Select("//price")

For Each editor As XPathEditor In iter
  ' delete this price node
  editor.DeleteCurrent()
Next

The methods described in Figure are specific to the XPathEditor class and detail the editing support for this navigator.

The Methods of the XPathEditor Class

Method

Description

CloneAsNavigator()

Gets a read-only XPathNavigator2 from an XPathEditor. This is useful for handling read-only XPathNavigator2 classes from the XPathEditor.

CreateFirstChild()

Inserts new elements before the child element of the current element. It returns an XmlWriter instance that can be used to construct the tree of nodes at the specified location. The actual insert operations are performed only when the XmlWriter.Close method is called, and until then the nodes are not visible on any XPathNavigator2. This method is valid only when the XPathEditor is positioned on the document root node or an element node; otherwise an XmlException is thrown. The XPathEditor remains positioned on the current element after this call.

A second overload of this method accepts a parameter that is an XML string representing well-formed XML:

CreateFirstChild(xml-content)

This XML content can be a fragment or sequence of fragments that need to be inserted or just text content. The method's implementation is identical to calling CreateFirstChild to get back an XmlWriter, breaking down the XML string, and outputting it through the writer. The xml-content can be a fragment that represents a subtree; in this case the subtree is created and inserted in one shot. This method is valid only when the XPathEditor is positioned on the root node or an element node; otherwise an XmlException is thrown. The XPathEditor remains positioned on the current element after this call. The in-scope namespaces are automatically derived from the insertion context.

CreateNextSibling()

Inserts new elements as the next sibling(s) of the current element. It returns an XmlWriter that can be used to construct the tree of nodes at the specified location. The actual insert operations are performed only when the XmlWriter.Close method is called, and until then the nodes are not visible on any XPathNavigator2. This method is valid only when the XPathEditor is positioned on an Element, Comment, Text, or PI node. Otherwise an XmlException is thrown. Calling this method when the XPathEditor is positioned on the document root node also causes an XmlException to be thrown. The XPathEditor remains positioned on the current element after this call.

A second overload of this method accepts a parameter that is an XML string representing well-formed XML:

CreateNextSibling(xml-content)

This XML content can be a fragment or sequence of fragments that need to be inserted or just text content. The method's implementation is identical to calling CreateNextSibling to get back an XmlWriter, breaking down the XML string, and outputting it through the writer. The xml-content can be a fragment that represents a subtree; in this case the subtree is created and inserted in one shot. This method is valid only when the XPathEditor is positioned on an Element, Comment, Text, or PI node. Otherwise an XmlException is thrown. The XPathEditor remains positioned on the current element after this call. The in-scope namespaces are automatically derived from the insertion context.

CreateFirstChildElement (prefix, local-name, namespace-uri, schema-type, value)

Inserts a new element as a child of the current element. The parameters are:

  • prefix: The prefix of the element to add. If this parameter is Nothing (null in C#) or String.Empty, no prefix is set.

  • local-name: The local name of the element to add. If this parameter is Nothing (null in C#) or String.Empty, an error occurs.

  • namespace-uri: The namespace URI of the element to add. If this parameter is Nothing (null in C#) or String.Empty, no namespace URI is set.

  • schema-type: The XSD type of the element to add. If this parameter is Nothing (null in C#), the element is assumed to be untyped. In the Technology Preview this property is ignored; i.e., there is no schema type support.

  • value: The text content of the element to add. If this parameter is Nothing (null in C#) or String.Empty, it is assumed there is no text content.

CreateFirstSiblingElement (prefix, local-name, namespace-uri, schema-type, value)

Inserts a new element as a sibling of the current element. See CreateFirstChildElement for the parameter descriptions.

CreateAttributeString (prefix, local-name, namespace-uri, schema-type, value)

Creates an attribute node with the specified prefix, name, namespace, schema type, and value. If the XPathEditor is positioned on an element, the attribute is inserted as the first attribute of the element. If the XPathEditor is positioned on an attribute, the new attribute is inserted after the current attribute. This is equivalent to using an XmlWriter obtained through calling the CreateAttributes method and then calling the StartElement and EndElement methods, followed by Close. The XPathEditor remains positioned on the current node afterward. If you try to create an attribute that is actually a namespace node, an XmlException is thrown. In some cases adding new attributes causes new namespace nodes to be added because the attribute belongs to a namespace undeclared in the XML document. In this case an error is thrown only if inserting the namespace node changes the namespace name of other elements or attributes in the document.

CreateAttributes()

Inserts a new attribute into the current element. If the XPathEditor is positioned on an element, the attribute is inserted as the first attribute of the element. If the XPathEditor is positioned on an attribute, the new attribute is inserted after this attribute. The XPathEditor remains positioned on the current node after this call. If you try to create an attribute that is actually a namespace node, an XmlException is thrown. In some cases adding new attributes causes new namespace nodes to be added because the attribute belongs to a namespace undeclared in the XML document. In this case an error is thrown only if inserting the namespace node changes the namespace name of other elements or attributes in the document.

DeleteCurrent()

Removes the current node from the tree. The node is no longer reachable using an XPathNavigator2. The XPathEditor remains positioned on the current node. DeleteCurrent is not valid when the XPathEditor is positioned on the root node; in this case an XmlException is thrown. Deleting a namespace node throws an XmlException if there are other nodes referencing this namespace.

If the node being deleted is a namespace node, a check is performed to see if any elements or attributes would have their namespace name altered by the disappearance of the node. If the deletion of the node changes the namespace name of an element or attribute within the scope of the namespace node, an exception is thrown. For example, attempting to delete the namespace node that represents xmlns:xsl="http://www.w3.org/1999/XSL/Transform" from the document element of an XSLT stylesheet would be an error. On the other hand, deleting an unreferenced namespace node from the same document element would not.

Deleted nodes are still accessible to navigators positioned over them prior to deletion. However, methods that attempt to move from the deleted subtree back to the document will fail. For example, MoveToParent and MoveToPreviousSibling always fail (return False) when positioned over the top-most deleted node because the node is no longer connected to the document. Similarly, MoveToNextSibling always fails if positioned on the bottom-most deleted node.

SetValue(string)

Updates the value in the current node with the supplied String parameter value. Namespace nodes cannot be updated, and calling SetValue when positioned on a namespace node results in an XmlException being thrown. Similarly, calling SetValue on the document root node results in an XmlException being thrown. Otherwise, calling SetValue replaces the value of the node. The description of what constitutes the value of an XPath node is given in the description of the XPathNavigator2.Value property.

The XPathChangeNavigator Class

The properties and methods detailed in Figure and 6.11 are specific to the XPathChangeNavigator class and describe the change navigation support available for this XPathNavigator2. The XPathChangeNavigator class is used to identify the changes made to an XPathDocument2 class and is created via a call to the CreateXPathChangeNavigator method on the XPathDocument2 class.

The Properties of the XPathChangeNavigator Class

Property

Description

NodeChangeType

Returns a value from the XmlNodeChangeType enumeration (Updated, Inserted, Deleted, or Unchanged) that indicates the status of the node. It will be Updated if the value of the node has been changed, Inserted if this node has been inserted explicitly, and Deleted if this node has been deleted explicitly. Otherwise the value is Unchanged.

OriginalValue

Returns a String that is the original value of the node currently pointed to by the XPathChangeNavigator. If the current value of NodeChangeType is Inserted, the property returns Nothing (null in C#). It also returns Nothing (null) for all nodes except text and attribute nodes (e.g., there is no original value for an element or a PI).

The Methods of the XPathChangeNavigator Class

Method

Description

AcceptChange()

Commits any changes made to this node. The AcceptChange method has the following effects:

  • Inserted nodes: The node is no longer marked as Inserted; i.e., the XmlNodeChangeType value for this node is set to Unchanged, and the current value of the node is propagated to the original value of the node.

  • Deleted nodes: The node and its subtree are no longer accessible from the main tree using an XPathNavigator2, XPathEditor, or XPathChangeNavigator.

  • Updated nodes: The node is no longer marked as Updated; i.e., the XmlNodeChangeType value for this node is set to Unchanged, and the current value of the node is propagated to the original value for the node.

RejectChange()

Rejects any changes made to this node. The RejectChange method has the following effects:

  • Inserted nodes: The node and its subtree are no longer accessible from the main tree using the XPathNavigator2, XPathEditor, or XPathChangeNavigator.

  • Deleted nodes: The node is no longer marked as Deleted; i.e., the XmlNodeChangeType value for this node is set to Unchanged, and the current value of the node is propagated to the original value of the node.

  • Updated nodes: The node is no longer marked as Updated; i.e., the XmlNodeChangeType value for this node is set to Unchanged, and the original value of the node is propagated to the current value for the node.

SelectChanges()

Returns an enumerator over all changed nodes within the subtree of this node. This is the set of nodes where the XmlNodeChangeType property has the value (Inserted Or Deleted Or Updated). This is a static set of nodes (a snapshot), so subsequent changes to the tree do not affect the enumerator. The MoveNext method is used to iterate through the nodes that match the filter criteria, or a For Each statement can be used to iterate through the results.

A second overload of this method accepts a parameter that is a value or combination of values from the XmlChange Filters enumeration, which specifies the types of changed nodes that are selected with the XPath statement:

SelectChanges(XmlChangeFilters)

CreateXmlEditor()

Returns an XPathEditor positioned on the current node. This XPathEditor could potentially be positioned on a deleted node (i.e., a node of type XmlNodeChange Type.Deleted), in which case any changes made through the XPathEditor are disconnected from the original document.

Using the XPathEditor Class with the XPathDocument2

At the start of this chapter we saw how to update an XPathDocument2 instance with an XPathEditor. This section provides more examples of using an XPathEditor with the XPathDocument2 to manipulate the XML. Typically the most common pattern is to select a set of nodes using the Select method, enumerate these nodes, and work with them.

Updating Text Node Values with the SetValue Method

The example in Listing 6.5 shows how you could increase the prices of all the books in the books.xml document. The Select method selects the text values of all the price nodes using the XPath "//price/text()", and then the SetValue method of the XPathEditor is used to increase the price of each book by 50% after it has been converted to decimal value.

Updating Text Node Values with the SetValue Method
Dim doc As New XPathDocument2()
doc.Load("books.xml")

Dim xmledit As XPathEditor = doc.CreateXPathEditor()

Dim iter As IEnumerable = xmledit.Select("//price/text()")

For Each editor As XPathEditor In iter

  ' convert the price to a decimal value
  Dim price As Double = XmlConvert.ToDouble(editor.ReadStringValue())
  price = price * 1.5

  ' set the new value onto the selected price text node
  editor.SetValue(price.ToString())

Next

The screenshot in Figure shows the results.

4. Updating price nodes in a document with the XPathEditor

graphics/06fig04.gif

Deleting Nodes with the DeleteCurrent Method

The next example, in Listing 6.6, shows how you can delete all the book prices. Again the Select method selects all the price nodes, this time with the XPath "//price", and then each one is deleted using the DeleteCurrent method.

Deleting Nodes with the DeleteCurrent Method
Dim doc As New XPathDocument2()
doc.Load("books.xml")

Dim editor As XPathEditor = doc.CreateXPathEditor()

Dim iter As IEnumerable = editor.Select("//price")

' delete all the price nodes
For Each editor As XPathEditor In iter
  editor.DeleteCurrent()
Next editor

The screenshot in Figure shows the results of this example.

5. Deleting price nodes in a document with the XPathEditor

graphics/06fig05.gif

Inserting Nodes with the CreateNextSibling Method

The next example, in Listing 6.7, shows how you can add a new book to the document, after the last book. This time, the Select method selects the last book node with the XPath "//book[position()=last()]" (i.e., select the book element that is at the last position of the document). The Create NextSibling method is then used to create the new book element.

However, instead of using an XmlWriter to create the node tree, a well-formed XML string is passed to the CreateNextSibling method. Although it is temping to provide strings to build the node tree, this tends to be error prone for anything other than small sections of XML or XML copied from other sources. It is usually preferable to use an XmlWriter. However, since the XmlWriter is used to write the node tree anyway, exceptions are still thrown for any invalid XML.

Inserting Nodes with the CreateNextSibling Method
Dim doc As New XPathDocument2()
doc.Load("books.xml")

Dim xmledit As XPathEditor = doc.CreateXPathEditor()

Dim iter As IEnumerable = xmledit.Select("//book[position()=last()]")

For Each editor As XPathEditor In iter
  editor.CreateNextSibling("<book genre='technology' " _
                         & "publicationdate='10-27-2003' " _
                         & "ISBN='1-861003-11-9'>" _
                         & "<title>ASP.NET V2</title>" _
                         & "<author><first-name>Alex</first-name>" _
                         & "<last-name>Homer</last-name></author>" _
                         & "<author><first-name>Dave</first-name>" _
                         & "<last-name>Sussman</last-name></author>" _
                         & "<price>29.22</price></book>")
Next

The screenshot in Figure on the next page shows the newly inserted book node.

6. Inserting a sibling node in a document with the XPathEditor

graphics/06fig06.jpg

Inserting Nodes with the CreateFirstChildElement Method

The next example, in Listing 6.8, shows how you can add a new child element. This is useful for creating elements with simple content, for example, an element with just a text value and no child elements. The Select method selects all the <book> elements with a price greater than 9.99 using the XPath "/bookstore/book[price>9.99]". Then the SelectSingleValue method is used to find the single price element that we know exists somewhere in the subtree for the selected book elements.

In this case it is the last child element for the book, but this does not always have to be the case. As long as we stick to using XPath to find elements, the structure of the XML can vary without causing errors. The price is converted to a numeric Double type using the XmlConvert method, and the CreateFirstChildElement method is called to create a <discount price> element that is 80% of the original price.

In all the examples so far we are using String values when the values are either read or written, since we have no schema to tell us what the types are for the XML nodes in the document. This means that to get these into CLR values such as a Double, we have to use the XmlConvert class.

Inserting a Node with the CreateFirstChildElement Method
Dim doc As New XPathDocument2()
doc.Load("books.xml")

Dim xmledit As XPathEditor = doc.CreateXPathEditor()
Dim iter As IEnumerable _
    = xmledit.Select("/bookstore/book[price>9.99]")

For Each editor As XPathEditor In iter
  Dim price As Double = XmlConvert.ToDouble(CStr( _
                        editor.SelectSingleValue("price")))
  editor.CreateFirstChildElement("", "discountprice", "", Nothing, _
                                         (price * 0.8).ToString())
Next

The screenshot in Figure shows the new discountprice node on the second book element.

7. Inserting child nodes into a document with the XPathEditor

graphics/06fig07.gif

Using the XPathChangeNavigator Class with the XPathDocument2

We can now see how the XPathChangeNavigator can be used to examine the changes made to the XPathDocument2 class. The important aspect of this class to consider is that the XPathChangeNavigator provides a "merged view" onto the XPathDocument2, allowing you to see all the nodes in the tree (including deleted nodes), along with their original values. The XPathEditor class, on the other hand, provides only a "current view" of the nodes—meaning that deleted nodes cannot be moved to, nor the original values of the nodes retrieved. They are hidden from the XPathEditor class. The "merged view" enables the following functionality with the XPathChangeNavigator.

  • It is possible to accept and reject changes at a node level. We will see later that, when using events on the XPathDocument2, this can be used to enforce business rules on the document.

  • It provides an API for navigating through changes made to the document. This API can be used by the XmlAdapter class to generate the appropriate SQL statements to update SQL Server when combined with a single XML view.

The XPathChangeNavigator exposes a value from the XmlNodeChange Type enumeration as the NodeChangeType property, which indicates the status of each node since the last call to the AcceptChanges or Reject Changes methods. A combination of values from the XmlChangeFilters enumeration can be passed to the SelectChanges method to retrieve all the nodes that have a corresponding value for their NodeChangeType property. This enumeration allows a bitwise combination of its member values; hence expressions such as Inserted Or Deleted (Inserted | Deleted in C#) can be used to get both the inserted and deleted nodes.

The next example performs the same actions as the XPathEditor examples shown earlier, applying the changes to the books.xml document. Once these changes have been made, an XPathChangeNavigator is created to iterate over them using the SelectChanges method, and each different type of change made to the book.xml document is reported. The code in Listing 6.9 shows the XPathChangeNavigator code—the code shown in earlier examples is omitted for clarity and to avoid repetition.

Iterating over the Changes to the book.xml Document
...
' code here to change the XML document in the XPathDocument2
' as demonstrated in previous code, Listings 6.5 through 6.8
...

' use the XPathChangeNavigator to iterate over the changes
Dim changes As XPathChangeNavigator = doc.CreateXPathChangeNavigator()
Dim iter4 As IEnumerable = changes.SelectChanges( _
                                   XmlChangeFilters.AllChanges)

Dim sType As String
For Each item As XPathChangeNavigator In iter4

  ' get the node type as a String
  Select Case item.ItemType
    Case 0: sType = "Element"
    Case 3: sType = "Text"
    Case Else: sType = "Other"
  End Select

  If item.NodeChangeType = XmlNodeChangeType.Inserted Then
    text1.Items.Add("inserted item - " & sType _
                  & " node:" & item.Name _
                  & " new value:" & item.ReadStringValue())

  ElseIf item.NodeChangeType = XmlNodeChangeType.Deleted Then
    text1.Items.Add("deleted item - " & sType _
                  & " node:" & item.Name _
                  & " value:" & item.ReadStringValue())

  ElseIf item.NodeChangeType = XmlNodeChangeType.Updated Then
    text1.Items.Add("updated item - " & sType _
                  & " node:" & item.Name _
                  & " new value:" & item.ReadStringValue() _
                  & " original value:" & item.OriginalValue)
  End If

Next

The screenshots in Figures 6.8 and 6.9 on the next two pages show the books.xml document before and after the changes made by the XPathEditor. Figure is the result of navigating over the changes with an XPathChange Navigator.

8. The books.xml document before changes with the XPathEditor

graphics/06fig08.gif

9. Navigating the changes to the books.xml document with the XPathChangeNavigator

graphics/06fig09.jpg

We will see in the next section how the XPathChangeNavigator can be used to accept or reject changes on individual nodes, depending on business rules applied to the document via events, when updates are made through the XPathEditor. And in Chapter 7 we will see how the XPathChangeNavigator interacts with the XmlAdapter to push changes made to a document into SQL Server through an XML view.


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