[UPDATED] Derived XMLDocument w/ Barebones XPath

Last post 01-22-2005 5:56 by Anonymous. 3 replies.
Page 1 of 1 (4 items)
Sort Posts: Previous Next
  • 12-10-2002 12:19

    [UPDATED] Derived XMLDocument w/ Barebones XPath

    (Note: Please see my posting below for an important bug fix.)


    Hi everybody,

    In an effort to provide my application with some XPath-style querying ability, I wrote a derived XMLDocument class that implements barebones versions of the selectNodes and selectSingleNode functions. They only allow for querying against node attributes, not node values; another important distinction is that attribute values in query strings are not enclosed by quotes. For example, you would use

    selectSingleNode("tables/table[@name=Customers]")

    instead of

    selectSingleNode("tables/table[@name='Customers']")

    I did this mainly for convenience, but there's no reason the code can't be modified to implement this behavior. Anyway, it hasn't been extensively tested, but perhaps some of you might find it useful - at the very least it might be helpful in getting started on your own XMLDocument class.

    Luke




    Imports System.Xml

    Public Class XmlDoc : Inherits XmlDocument
    Public Function selectSingleNode(ByVal cXPath As String) As XmlNode
    Dim aQuery() As String = cXPath.Split("/")
    Dim oNodeList As XmlNodeList = Me.ChildNodes
    Dim nI As Integer

    For nI = 0 To aQuery.Length - 2
    GetFirstMatchedNodes(oNodeList, aQuery(nI))
    Next
    Return GetNodeFromList(oNodeList, aQuery(aQuery.Length - 1))
    End Function

    Public Function selectNodes(ByVal cXPath As String) As XmlNodeList
    Dim aQuery() As String = cXPath.Split("/")
    Dim oNodeList As XmlNodeList = Me.ChildNodes
    Dim nI As Integer

    For nI = 0 To aQuery.Length - 2
    GetFirstMatchedNodes(oNodeList, aQuery(nI))
    Next
    GetAllMatchedNodes(oNodeList, aQuery(aQuery.Length - 1))

    Return oNodeList
    End Function

    Private Sub GetAllMatchedNodes(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String)
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    nI = 0
    Do While nI <= oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If Not MatchXPathParams(aParams, oNodeList.Item(nI)) Then
    oNodeList.Item(nI).ParentNode.RemoveChild(oNodeList.Item(nI))
    Else
    nI += 1
    End If
    Else
    oNodeList.Item(nI).ParentNode.RemoveChild(oNodeList.Item(nI))
    End If
    Loop
    End Sub

    Private Sub GetFirstMatchedNodes(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String)
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    For nI = 0 To oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If MatchXPathParams(aParams, oNodeList.Item(nI)) Then
    oNodeList = oNodeList.Item(nI).ChildNodes
    Exit For
    End If
    End If
    Next
    End Sub

    Private Function GetNodeFromList(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String) As XmlNode
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    For nI = 0 To oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If MatchXPathParams(aParams, oNodeList.Item(nI)) Then
    Return oNodeList.Item(nI)
    End If
    End If
    Next
    End Function

    Private Sub ParseXPath(ByVal cXPath As String, _
    ByRef cQuery As String, _
    ByRef aParams() As String)
    Dim nIndex As Integer

    nIndex = cXPath.IndexOf("[")
    If nIndex <> -1 Then
    cQuery = cXPath.Substring(0, nIndex)
    cXPath = cXPath.Substring(nIndex + 1)
    cXPath = cXPath.Replace("]", "")
    aParams = cXPath.Split("[")
    Else
    cQuery = cXPath
    ReDim aParams(-1)
    End If
    End Sub

    Private Function MatchXPathParams(ByRef aParams() As String, _
    ByRef oNode As XmlNode) As Boolean
    Dim nI, nJ, nIndex As Integer
    Dim cNodeName, cNodeVal As String
    Dim bMatches As Boolean

    If aParams.Length = 0 Then
    Return True
    End If

    bMatches = True
    For nI = 0 To aParams.Length - 1
    nIndex = aParams(nI).IndexOf("=")
    cNodeVal = aParams(nI).Substring(nIndex + 1)

    ' Assume all parameters are attributes (and so start with '@')
    cNodeName = aParams(nI).Substring(1, nIndex - 1)
    For nJ = 0 To oNode.Attributes.Count - 1
    If oNode.Attributes(nJ).Name = cNodeName Then
    If oNode.Attributes(nJ).InnerText <> cNodeVal Then
    bMatches = False
    End If
    Exit For
    ElseIf nJ = oNode.Attributes.Count - 1 Then
    bMatches = False
    End If
    Next
    Next

    Return bMatches
    End Function
    End Class
  • 01-29-2003 13:19 In reply to

    Re: [UPDATED] Derived XMLDocument w/ Barebones XPath

    I've implemented this into an application I'm using. However--I've run into a bit of a problem when using the selectNodes method.

    If you look at that function, you'll notice that there's a "removechild" call to filter out the nodes that don't match.

    HOWEVER, it appears as though that removechild call is actually removing children from the underlying xmldocument--instead of just from the nodelist that we get in selectedNodes:
    Dim oNodeList as XmlNodeList = Me.ChildNodes


    Anyone run into this problem?
  • 01-30-2003 11:08 In reply to

    Re: [UPDATED] Derived XMLDocument w/ Barebones XPath

    You're right, good catch - I hadn't noticed this bug because in each case in my app where I use selectNodes, the XmlDoc object is only used for one call to this method.

    Replacing the line
    Dim oNodeList As XmlNodeList = Me.ChildNodes
    with
    Dim oNodeList As XmlNodeList = Me.Clone.ChildNodes
    should fix the problem.

    Thanks for pointing that out...

    Luke
  • 01-22-2005 5:56 In reply to

    Re: [UPDATED] Derived XMLDocument w/ Barebones XPath

    Hello

    I have found that Me.ChildNodes does not seem to remove children from the underlying XmlDocument, even if I called multiple SelectNodes on the same XmlDocument object? Maybe this is becuase of the newer CF?

    Anyway I found that Me.ChildNodes was much much faster than Me.Clone.ChildNodes. I also extended the original a little to support a few more Xpath style queries namely:

    Filter by child element:
    selectSingleNode("tables/table[name=Customers]")


    Filter by node match index:
    selectSingleNode("tables/table[n]")


    Its not extensively tested but works so far for me.

    Brian Norman
    www.bbpsoftware.co.uk


    Imports System.Xml

    Public Class XmlDoc : Inherits XmlDocument
    Public Function selectSingleNode(ByVal cXPath As String) As XmlNode
    Dim aQuery() As String = cXPath.Split("/")
    Dim oNodeList As XmlNodeList = Me.ChildNodes()
    Dim nI As Integer

    For nI = 0 To aQuery.Length - 2
    GetFirstMatchedNodes(oNodeList, aQuery(nI))
    Next
    Return GetNodeFromList(oNodeList, aQuery(aQuery.Length - 1))
    End Function

    Public Function selectNodes(ByVal cXPath As String) As XmlNodeList
    Dim aQuery() As String = cXPath.Split("/")
    Dim oNodeList As XmlNodeList = Me.ChildNodes()
    Dim nI As Integer

    For nI = 0 To aQuery.Length - 2
    GetFirstMatchedNodes(oNodeList, aQuery(nI))
    Next
    GetAllMatchedNodes(oNodeList, aQuery(aQuery.Length - 1))

    Return oNodeList
    End Function

    Private Sub GetAllMatchedNodes(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String)
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    nI = 0
    Do While nI <= oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If Not MatchXPathParams(aParams, oNodeList.Item(nI), nI) Then
    oNodeList.Item(nI).ParentNode.RemoveChild(oNodeList.Item(nI))
    Else
    nI += 1
    End If
    Else
    oNodeList.Item(nI).ParentNode.RemoveChild(oNodeList.Item(nI))
    End If
    Loop
    End Sub

    Private Sub GetFirstMatchedNodes(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String)
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    For nI = 0 To oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If MatchXPathParams(aParams, oNodeList.Item(nI), nI) Then
    oNodeList = oNodeList.Item(nI).ChildNodes
    Exit For
    End If
    End If
    Next
    End Sub

    Private Function GetNodeFromList(ByRef oNodeList As XmlNodeList, _
    ByVal cXPath As String) As XmlNode
    Dim cQuery, aParams() As String
    Dim nI As Integer

    ParseXPath(cXPath, cQuery, aParams)

    For nI = 0 To oNodeList.Count - 1
    If oNodeList.Item(nI).Name = cQuery Then
    If MatchXPathParams(aParams, oNodeList.Item(nI), nI) Then
    Return oNodeList.Item(nI)
    End If
    End If
    Next
    End Function

    Private Sub ParseXPath(ByVal cXPath As String, _
    ByRef cQuery As String, _
    ByRef aParams() As String)
    Dim nIndex As Integer

    nIndex = cXPath.IndexOf("[")
    If nIndex <> -1 Then
    cQuery = cXPath.Substring(0, nIndex)
    cXPath = cXPath.Substring(nIndex + 1)
    cXPath = cXPath.Replace("]", "")
    aParams = cXPath.Split("[")
    Else
    cQuery = cXPath
    ReDim aParams(-1)
    End If
    End Sub

    Private Function MatchXPathParams(ByRef aParams() As String, _
    ByRef oNode As XmlNode, ByVal nNodeIndex As Integer) As Boolean
    Dim nI, nJ, nIndex As Integer
    Dim cNodeName, cNodeVal As String
    Dim bMatches As Boolean

    If aParams.Length = 0 Then
    Return True
    End If

    bMatches = True
    For nI = 0 To aParams.Length - 1
    nIndex = aParams(nI).IndexOf("=")
    cNodeVal = aParams(nI).Substring(nIndex + 1)

    ' check if parameter is attributes (and so start with '@')
    If aParams(nI).Substring(0, 1) = "@" Then
    'is attribute
    cNodeName = aParams(nI).Substring(1, nIndex - 1)
    For nJ = 0 To oNode.Attributes.Count - 1
    If oNode.Attributes(nJ).Name = cNodeName Then
    If oNode.Attributes(nJ).InnerText <> cNodeVal Then
    bMatches = False
    End If
    Exit For
    ElseIf nJ = oNode.Attributes.Count - 1 Then
    bMatches = False
    End If
    Next
    Else
    'check if param is nodelist index
    If IsNumeric(aParams(nI)) Then
    'is index
    If Not nNodeIndex.ToString = aParams(nI) Then
    bMatches = False
    End If
    Else
    'is element
    cNodeName = aParams(nI).Substring(0, nIndex)
    For nJ = 0 To oNode.Attributes.Count - 1
    If oNode.ChildNodes(nJ).Name = cNodeName Then
    If oNode.ChildNodes(nJ).InnerText <> cNodeVal Then
    bMatches = False
    End If
    Exit For
    ElseIf nJ = oNode.ChildNodes.Count - 1 Then
    bMatches = False
    End If
    Next
    End If
    End If

    Next

    Return bMatches
    End Function
    End Class
Page 1 of 1 (4 items)