Click here to Skip to main content
Click here to Skip to main content

XML Pathfinder: a Visual Basic Utility

, 27 Feb 2008
Rate this:
Please Sign up or sign in to vote.
Constructing a simple utility that may be used to locate and evaluate paths within an XML document and testing queries against those paths
ScreenShot_small.GIF

Introduction

This article discusses the construction of a simple utility that may be used to locate and evaluate paths within an XML document, and to test queries against those paths. The application allows the user to open an XML document into a TreeView control for easy viewing of the structure. The user may right click on any node within the TreeView to expose a context menu that will allow the user to copy the current path to that node. The user may then open a test window up and paste the copied path into an expression text box. Once the path has been pasted into the text box, the user may test the path directly or may edit the path and test the edit. The project includes a collection of sample XML documents which may be used to try out the application.

Using the Application

To use the application, open the main form and use the file menu to open an XML document. The document will be displayed in the main form using a TreeView control. The user may click on any node in the TreeView and the path to that node -- as well as the node type -- will be displayed in the status bar at the bottom of the window. The user may right click on any selected node and select from one of three options:

  1. Copy only the text contained in the current node.
  2. Copy the path to the node formatted as a query for that attribute value.
  3. Copy the full path exactly as it is.
f1.jpg

Figure 1: Context Menu

f2.jpg

Figure 2: Main Form

f3.jpg

Figure 3: Test Form
(example shows use of the “Copy Full Path to Query an Attribute” context menu option)

f4.jpg

Figure 4: Editing a Path Manually to Test Alternative Paths
("/description" added to path shown in Figure 3)

The intent of the application was to provide a simple tool that may be used to test paths for XPath based queries. It is intended to simplify viewing the XML and to make it easier to identify specific paths within an XML document.

Getting Started

In order to get started, unzip the included project and open the solution in the Visual Studio 2005 environment. In the solution explorer, you should note these files:

f5.jpg

Figure 5: Solution Explorer

The Main Form (frmXmlPathfinder.vb)

The main form is used to open XML documents and to display them in TreeView format. The form also provides the interface necessary to copy node paths and to open a test window that may be used to test XPath-based queries against the XML document’s content. The code is annotated and should be easy enough to follow from the descriptions provided:

If you’d care to open the code view up in the IDE, you will see that the code file begins as follows:

Imports System.Xml
Imports System.Xml.XPath

Note that the additions of the System.Xml and System.Xml.XPath libraries are the only departure from the default. Following the imports, the class is defined and a constructor added. A local string variable is declared and used to hold the path to the XML document.

Public Class frmXmlPathfinder

    ' Member variables
    Private mFilePath As String


    ''' <span class="code-SummaryComment"><summary></span>
    ''' Constructor
    ''' <span class="code-SummaryComment"></summary></span>
    ''' <span class="code-SummaryComment"><remarks></remarks></span>
    Public Sub New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        ' init the file path as empty
        mFilePath = String.Empty

    End Sub

Next up is the method used to exit the application.

''' <span class="code-SummaryComment"><summary></span>
''' Exit the Application
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub tspExit_Click(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles tspExit.Click

    Application.Exit()

End Sub

The next section of the code is used to load the XML document into the TreeView control.

''' <span class="code-SummaryComment"><summary></span>
''' Write the xml document into a treeview to make 
''' easier to read and navigate, starting with the top
''' of the document, the method uses recursion to
''' populate the treeview with all the child nodes
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="currentNode"></param></span>
''' <span class="code-SummaryComment"><param name="nodCollection"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub PushToTreeView(ByVal currentNode As XmlNode, _
    ByVal nodCollection As TreeNodeCollection)

    Try


        Dim insertNode As TreeNode = nodCollection.Add(currentNode.Name)

        Select Case (currentNode.NodeType)

            Case XmlNodeType.Element
                insertNode.Text = currentNode.Name
                insertNode.Tag = "Element"
                insertNode.ImageIndex = 1
            Case XmlNodeType.Attribute
                insertNode.Text = "@" + currentNode.Name
                insertNode.Tag = "Attribute"
                insertNode.ImageIndex = 2
            Case XmlNodeType.Text
                insertNode.Text = currentNode.Value
                insertNode.Tag = "Text"
                insertNode.ImageIndex = 3
            Case XmlNodeType.CDATA
                insertNode.Text = currentNode.Value
                insertNode.Tag = "CDATA"
                insertNode.ImageIndex = 4
            Case XmlNodeType.Comment
                insertNode.Text = currentNode.Value
                insertNode.Tag = "Comment"
                insertNode.ImageIndex = 5
            Case XmlNodeType.Entity
                insertNode.Text = currentNode.Value
                insertNode.Tag = "Entity"
                insertNode.ImageIndex = 6
            Case XmlNodeType.Notation
                insertNode.Text = currentNode.Value
                insertNode.Tag = "Notation"
                insertNode.ImageIndex = 7
            Case Else
                ' do nothing

        End Select

        ' Recursive stuff

        ' check the current node for attributes
        If Not currentNode.Attributes Is Nothing Then

            ' write out the attributes to the treeview
            Dim attribute As XmlAttribute
            For Each attribute In currentNode.Attributes
            PushToTreeView(attribute, insertNode.Nodes)
            Next

        End If

        ' check the current node for child nodes
        If Not currentNode Is Nothing And currentNode.HasChildNodes Then

            ' write out the child nodes to the treeview
            Dim childNode As XmlNode
            For Each childNode In currentNode.ChildNodes
                PushToTreeView(childNode, insertNode.Nodes)
            Next

        End If

    Catch ex As Exception

        MessageBox.Show(ex.Message, "Error Reading XML Document")

    End Try

End Sub

The next method is used to load up a new XML document into the form.

''' <span class="code-SummaryComment"><summary></span>
''' Open an xml document into the treeview
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub tspOpen_Click(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles tspOpen.Click

    Try

        openFileDialog1.Title = "Open XML File"
        openFileDialog1.Filter = "XML Files|*.xml"
        openFileDialog1.DefaultExt = "XML"
        openFileDialog1.FileName = ""

        openFileDialog1.ShowDialog()

        If openFileDialog1.FileName = "" Then
            Return
        End If

        ' set the file path member var
        mFilePath = openFileDialog1.FileName

        ' Clear the treeview.
        treeXml.Nodes.Clear()

        ' set the wait cursor
        Me.Cursor = Cursors.WaitCursor

        ' create a new xml doc
        Dim doc As New XmlDocument()

        Try

            ' load the xml doc
            doc.Load(mFilePath)

            ' set the form text to include
            ' the file name
            Me.Text = "XML Pathfinder - " + mFilePath

            ' return the cursor
            Me.Cursor = Cursors.Default

        Catch ex1 As Exception

            ' return the cursor
            Me.Cursor = Cursors.Default

            ' tell a story
            MessageBox.Show(ex1.Message, "Error Opening XML File")
            Return

        End Try


        ' open the doc into the treeview for 
        ' inspection
        PushToTreeView(doc, treeXml.Nodes)

        ' restore the cursor
        Me.Cursor = Cursors.Default


    Catch ex2 As Exception

        ' snitch
        MessageBox.Show(ex2.Message, "Unable to Open Document")

    End Try

End Sub

The next bit of code is used display the selected node’s path within the context of the XML document. The path shown is a cleaned up version of what appears in the TreeView. This code will display both the path as well as the selected node’s type, e.g. Element or Attribute.

''' <span class="code-SummaryComment"><summary></span>
''' Update the status bar when a new node is selected
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub treeXml_AfterSelect(ByVal sender As System.Object, ByVal e As 
System.Windows.Forms.TreeViewEventArgs) Handles treeXml.AfterSelect

    Try

        Dim tmp As String = treeXml.SelectedNode.FullPath
        tmp = tmp.Replace("#document", "/")

        ' show the path in the status bar
        statusPath.Text = "Selected Path: " + tmp

        ' show the selected node type in the status bar
        statusType.Text = " Selected Type: " + 
        treeXml.SelectedNode.Tag

    Catch

        ' skip this update

    End Try

End Sub

The next three methods are used to format the path and to copy it into the clipboard, making it possible to then paste the selected path directly into the expression test text box on the test form.

''' <span class="code-SummaryComment"><summary></span>
''' Copy only the selected node's text to the clipboard
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub copyTextToClipboardToolStripMenuItem_Click(ByVal sender As 
System.Object, ByVal e As System.EventArgs) Handles 
copyTextToClipboardToolStripMenuItem.Click

    Dim tmp As String = treeXml.SelectedNode.Text
    Clipboard.SetDataObject(tmp, True)

End Sub



''' <span class="code-SummaryComment"><summary></span>
''' Copy the full path of the node and try to convert the attribute
''' portion into a formatted query for that value; if the user
''' performs this action on an invalid selection (not an
''' attribute, it will give a useless result)
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub copyFullPathToolStripMenuItem_Click(ByVal sender As 
System.Object, ByVal e As System.EventArgs) Handles 
copyFullPathToolStripMenuItem.Click

    Try

        ' clean #document off the path
        Dim tmp As String = treeXml.SelectedNode.FullPath
        tmp = tmp.Replace("#document", "/")

        ' check to see if there is an attribute present
        ' you'd likely query the attribute this can
        ' get hosed if the user selects this option and
        ' has not clicked on an attribute's value
        Dim pos As Integer = 0
        pos = tmp.LastIndexOf("@")
        pos = pos - 1

        If pos <> 0 Then

            tmp = tmp.Remove(pos, 1)
            tmp = tmp.Insert(pos, "[")

            Dim posSlash As Integer = 
            treeXml.SelectedNode.FullPath.LastIndexOf("/")

            If posSlash < pos Then

                tmp += "='KeyValueHere')"
                End

            Else

                tmp = tmp.Remove(posSlash - 8, 1)
                tmp = tmp.Insert(posSlash - 8, "='")
                tmp += "']"

            End If

        End If

            Clipboard.SetDataObject(tmp, True)

        catch 

        ' if it fails, just select the selected node's
        ' full path
        Dim tmp As String = treeXml.SelectedNode.FullPath
        tmp = tmp.Replace("#document", "/")

        ' put it in the clip board
        Clipboard.SetDataObject(tmp, True)

    End Try

End Sub



''' <span class="code-SummaryComment"><summary></span>
''' Copy the exact path to the node without
''' making any modifications to it
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub copyFullPathAsIsToolStripMenuItem_Click(ByVal sender As 
System.Object, ByVal e As System.EventArgs) Handles 
copyFullPathAsIsToolStripMenuItem.Click

    Try

        ' clean #document off the path
        Dim tmp As String = treeXml.SelectedNode.FullPath
        tmp = tmp.Replace("#document", "/")

        Clipboard.SetDataObject(tmp, True)

    Catch

        ' do nothing

    End Try

End Sub

The last two methods in the main form are used to either open a help window or to open a test window.

    ''' <span class="code-SummaryComment"><summary></span>
    ''' Open the test file if there is an active 
    ''' xml file open in the main form
    ''' <span class="code-SummaryComment"></summary></span>
    ''' <span class="code-SummaryComment"><param name="sender"></param></span>
    ''' <span class="code-SummaryComment"><param name="e"></param></span>
    ''' <span class="code-SummaryComment"><remarks></remarks></span>
    Private Sub tspOpenTest_Click(ByVal sender As System.Object, ByVal e As 
    System.EventArgs) Handles tspOpenTest.Click

        If mFilePath <> String.Empty Then
    
            Dim f As New frmTest(mFilePath)
            f.Show()

        Else

            MessageBox.Show("Open an xml document prior to starting a test.", 
            "Invalid File")

        End If

    End Sub



    ''' <span class="code-SummaryComment"><summary></span>
    ''' Open the help file
    ''' <span class="code-SummaryComment"></summary></span>
    ''' <span class="code-SummaryComment"><param name="sender"></param></span>
    ''' <span class="code-SummaryComment"><param name="e"></param></span>
    ''' <span class="code-SummaryComment"><remarks></remarks></span>
    Private Sub tspOpenHelp_Click(ByVal sender As System.Object, ByVal e As 
    System.EventArgs) Handles tspOpenHelp.Click

        Dim f As New frmHelp()
        f.Show()

    End Sub


End Class

The Test Form (frmTest.vb)

The test form is used to test paths within open XML documents and to display any XPath type queries executed against the XML document. If you’d care to open the code view, up in the IDE you will see that the code file begins as follows:

Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.XPath

The imports are primarily per the default configuration for a Windows application. The System.Xml and System.Xml.XPath library imports are the only departure from the default. Following the imports, the class is declared and a constructor added. A local string variable is declared and used to hold the path to the XML document.

Public Class frmTest

    ' the document opened for examination
    Dim doc As New XmlDocument()

    ''' <span class="code-SummaryComment"><summary></span>
    ''' Constructor
    ''' 
    ''' Uses the same xml document as is opened
    ''' in the main form of the application; this
    ''' is passed upon creating a new instance 
    ''' of this test form
    ''' 
    ''' <span class="code-SummaryComment"></summary></span>
    ''' <span class="code-SummaryComment"><param name="filePath"></param></span>
    ''' <span class="code-SummaryComment"><remarks></remarks></span>
    Public Sub New(ByVal filePath As String)

        InitializeComponent()

        ' Load the xml document for use in
        ' this form
        Try

             doc.Load(filePath)
             Me.Text = "Testing - " & filePath

        Catch ex As Exception

             MessageBox.Show(ex.Message, "Error Loading XML document")

        End Try

    End Sub

The next bit of code is the button click event handler for the test itself. This code uses the expression text box content as a path; whatever is captured from the resulting query is displayed in the result section of the form. This is accomplished by creating an XPathNavigator and using the XML document’s CreateNavigator method. Once the navigator is declared, we can send the expression search term directly to the navigator’s select method to test it.

''' <span class="code-SummaryComment"><summary></span>
''' Tests the statement entered into the expression
''' test box and display the results in the 
''' results panel
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles btnTest.Click

    rtbResults.Text = String.Empty

    ' get an xpath navigator 
    Dim navigator As XPathNavigator
    navigator = doc.CreateNavigator

    ' contain the results in a stringbuilder
    Dim sb As New StringBuilder()


    Try

        ' look for the path and use an iterator to capture the results
        Dim nodes As XPathNodeIterator = 
        navigator.Select(txtTestExpression.Text)

        While (nodes.MoveNext())

            Dim node As XPathNavigator = nodes.Current

            ' depending upon which radio button is checked,
            ' write the results to the string builder
            If optInnerXml.Checked = True Then

                sb.Append(node.InnerXml & Environment.NewLine)

            ElseIf optOuterXml.Checked = True Then

                sb.Append(node.OuterXml & Environment.NewLine)

            Else

                sb.Append(node.Value & Environment.NewLine)

            End If

        End While


    Catch ex As Exception

        MessageBox.Show(ex.Message, "XPath Error")

    End Try


    ' post any results to the results box
    rtbResults.Text = sb.ToString()

End Sub

The last bit of the class is used to close the form.

''' <span class="code-SummaryComment"><summary></span>
''' Close this form
''' <span class="code-SummaryComment"></summary></span>
''' <span class="code-SummaryComment"><param name="sender"></param></span>
''' <span class="code-SummaryComment"><param name="e"></param></span>
''' <span class="code-SummaryComment"><remarks></remarks></span>
Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles btnClose.Click

    Me.Dispose()

End Sub

The only remaining forms are used to display the help. The help file contains access to an RTF document with instructions and samples showing a few ways in which it is possible to use the utility.

Summary

This application was provided as a starter utility application that may be used to evaluate paths when writing XPath based queries against an existing XML document. There are lots of other things that one could add to the application to make it more useful. A better interface for defining and testing more elaborate queries would be a good enhancement.

History

  • 27 February, 2008 -- Original version posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

salysle
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 27 Feb 2008
Article Copyright 2008 by salysle
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid