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

XML Explorer

, 11 Sep 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
An extremely fast, lightweight XML file viewer.

Go to the CodePlex project site for the latest releases and source code.

XmlExplorer Window with the XPath Expression Results Window

Contents

Introduction

At work, I find myself constantly having to view XML files. Some of the files I work with are extremely large (150MB+). Internet Explorer and Firefox take forever to open these files. I like the way Visual Studio 2005 provides the ability to expand/collapse individual elements, collapse or expand the entire document, etc., but it doesn't handle large files well either. Even the extremely expensive Stylus Studio chokes on large files, and doesn't offer the features of VS2005.

I explored several Open-Source solutions, including the ones from CodeProject, but most of them were not fast enough. Most loaded the entire document into a tree, all at once.

This article discusses my solution to the problem. I ended up with a nice little utility that gets used extensively around the office. It is an extremely fast, lightweight XML file viewer. It supports multiple document tabs, with support for middle-clicking to close tabs. It can be handle extremely large XML files. It has been tested on files as big as 150MB. It allows fast viewing and exploration, copying of formatted XML data, and evaluation of XPath expressions.

Background

My original implementation was based on the .NET XmlDocument. It was fast enough, but I didn't need any of the edit capabilities it offers. A colleague of mine, Mark (Code6) Belles, was kind enough to point me in the direction of the XPathDocument. XPathDocuments are even faster, and use much less memory than the XmlDocument, at the expense of the modification functionality.

According to MSDN, XPathDocument "provides a fast, read-only, in-memory representation of an XML document using the XPath data model". Sounded good to me.

Using the code

I've included several different ways to use the TreeView.

The XPathNavigatorTreeView can be added to your own forms via the designer in VS. Then, in code, just set the Navigator property of the XPathNavigatorTreeView to a XPathNavigator you've already loaded, for example:

// load the XPathDocument
XPathDocument document = new XPathDocument(_filename);

// set the Navigator property of the XPathNavigatorTreeView
this.xmlTreeView.Navigator = document.CreateNavigator();

The XmlExplorerTabPage can be programmatically added to any standard TabControl:

// create a tab page
XmlExplorerTabPage tabPage = new XmlExplorerTabPage();

// add the tabpage to the tab control
this.tabControl.TabPages.Add(tabPage);

// instruct the tab to open the specified file.
tabPage.Open(filename);

The TabbedXmlExplorerWindow can be shown, in process, from your own applications.

// create a new application window
TabbedXmlExplorerWindow window = new TabbedXmlExplorerWindow();

// instruct the window to open a specified file
window.Open(filename);

// show the window
window.Show();

The XmlExplorer sample project is a fully-functional, standalone Windows application you can use to quickly view even extremely large XML files.

Points of interest

XPathNavigatorTreeView

One point of interest for the XPathNavigatorTreeView is the way TreeNodes are loaded on-demand, instead of loading nodes for every element in the document at once. Each node gets added with an empty 'dummy' node, so it can be expanded. I then override the OnBeforeExpand method, remove the dummy node, and add nodes for any child XML elements. Each node has a property to track whether it's been expanded and loaded, so it only gets loaded once.

I then use the XPathNavigator of an expanded tree node to select the child XML elements, returning an XPathNodeIterator. I use the iterator to add TreeNodes for the child XML elements, cloning the navigator for each node to maintain a cursor for future expansion.

// select the child nodes of the specified xml tree node
XPathNodeIterator iterator = 
  treeNode.Navigator.SelectChildren(XPathNodeType.All);

// create and add a node for each navigator
foreach (XPathNavigator navigator in iterator)
{
    XPathNavigatorTreeNode node = 
        new XPathNavigatorTreeNode(navigator.Clone());
    treeNodeCollection.Add(node);
}

XPathNavigatorTreeNode

The implementation of XPathNavigatorTreeNode is pretty straightforward. Here is how I construct the text for the node:

/// <summary>
/// Returns the text used to display this XPathNavigatorTreeNode,
/// formatted using the XPathNavigator it represents.
/// </summary>
/// <returns />
public string GetDisplayText()
{
    if (_navigator == null)
        return string.Empty;

    StringBuilder builder = new StringBuilder();
    switch (_navigator.NodeType)
    {
        case XPathNodeType.Comment:
            // comments are easy, just append the value inside  tags
            builder.Append("");
            break;

        case XPathNodeType.Root:
        case XPathNodeType.Element:
            // append the start of the element
            builder.AppendFormat("<{0} ", _navigator.Name);

            // append any attributes
            if (_navigator.HasAttributes)
            {
                // clone the node's navigator (cursor),
                // so it doesn't lose it's position
                XPathNavigator attributeNavigator = _navigator.Clone();
                if (attributeNavigator.MoveToFirstAttribute())
                {
                    do
                    {
                        builder.AppendFormat("{0}=\"{1}\" ", 
                                attributeNavigator.Name, attributeNavigator.Value);
                    }
                    while (attributeNavigator.MoveToNextAttribute());
                }
            }

            // if the element has no children, close the node immediately
            if (!_navigator.HasChildren)
            {
                builder.Append("/>");
            }
            else
            {
                // otherwise, an end tag node will be appended
                // by the XPathNavigatorTreeView after it's expanded
                builder.Append(">");
            }
            break;

        default:
            // all other node types are easy, just append the value
            // strings, whitespace, etc.
            builder.Append(this.StripNonPrintableChars(_navigator.Value));
            break;
    }
    return builder.ToString();
}

Loading XML files asynchronously

As fast as the XPathDocument can load large XML files, I still wanted the UI to remain responsive, and even allow the user to cancel the loading of a file. I implemented the methods used to load the files in an asynchronous manner, using the .NET Thread class.

/// <summary>
/// Begins loading an XML file on a background thread.
/// </summary>
private void BeginLoadFile()
{
    _loadFileThread = new Thread(new ThreadStart(this.LoadFile));
    _loadFileThread.IsBackground = true;
    _loadFileThread.Start();
}

/// <summary>
/// The background worker method used to load an XML file in the background.
/// </summary>
private void LoadFile()
{
    try
    {
        if (this.LoadingFileStarted != null)
            this.LoadingFileStarted(this, EventArgs.Empty);

        Debug.WriteLine(string.Format("Peak RAM Before......{0}", 
              Process.GetCurrentProcess().PeakWorkingSet64.ToString()));
        Debug.Write("Loading XPathDocument.");
        DateTime start = DateTime.Now;

        // load the document
        XPathDocument document = new XPathDocument(_filename);

        Debug.WriteLine(string.Format("Done. Elapsed: {0}ms.", 
                        DateTime.Now.Subtract(start).TotalMilliseconds));

        // the UI has to be updated on the thread that created it,
        // so invoke back to the main UI thread.
        MethodInvoker del = delegate()
        {
            this.LoadDocument(document);
        };

        this.Invoke(del);

        if (this.LoadingFileCompleted != null)
            this.LoadingFileCompleted(this, EventArgs.Empty);
    }
    catch (ThreadAbortException ex)
    {
        // do not display the exception to the user,
        // as they most likely aborted the thread by
        // closing the tab or application themselves
        Debug.WriteLine(ex);
        if (this.LoadingFileFailed != null)
            this.LoadingFileFailed(this, EventArgs.Empty);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);

        MethodInvoker del = delegate()
        {
            MessageBox.Show(this, ex.ToString());
        };

        this.Invoke(del);

        if (this.LoadingFileFailed != null)
            this.LoadingFileFailed(this, EventArgs.Empty);
    }
}

/// <summary>
/// Loads an XPathDocument into the tree.
/// </summary>
private void LoadDocument(XPathDocument document)
{
    try
    {
        if (document == null)
            throw new ArgumentNullException("document");

        Debug.Write("Loading UI.");
        DateTime start = DateTime.Now;
        this.xmlTreeView.Navigator = document.CreateNavigator();
        Debug.WriteLine(string.Format("Done. Elapsed: {0}ms.", 
                        DateTime.Now.Subtract(start).TotalMilliseconds));
        Debug.WriteLine(string.Format("Peak RAM After......{0}", 
                        Process.GetCurrentProcess().PeakWorkingSet64.ToString()));
    }
    catch (ThreadAbortException)
    {
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
        MessageBox.Show(this, ex.ToString());
    }
}

Settings

The sample application also saves and restores user settings (such as window position, size, font, etc.) using the standard .NET Settings class. I used the excellent Visual Settings Designer in Visual Studio 2005 (just double-click the Properties node under your project, then go to Settings). I made all of the settings user-specific, so different users of the same machine can use different settings. The settings can be accessed and saved with the following code:

// load the WindowState setting
window.WindowState = Properties.Settings.Default.WindowState;

...

// save the settings
Properties.Settings.Default.Save();

Performance

I've compared the times required to load XML files of varying sizes. XML Notepad was used for comparison, since it's nice enough to provide load times in the status bar. I realize the comparison isn't exactly fair, as XML Notepad is an XML editor. I trust you will find a place in your XML toolbox for both applications, as I have.

File Size XML Notepad XML Explorer
47.4 MB 48.68 seconds 4.94 seconds
31.3 MB 18.46 seconds 3.63 seconds
30.96 MB 16.47 seconds 2.89 seconds
3.69 MB 1.55 seconds .32 seconds
281 KB .35 seconds .04 seconds

As you can see, the difference is greatest on extremely large files. On the more normal sized files, the difference is barely noticeable.

Memory

XML Explorer also makes much more efficient use of RAM, by utilizing XPathDocument (instead of XmlDocument), and by loading the XML document into the display on-demand.

File Size XML Notepad XML Explorer
47.4 MB 563.3 MB RAM 126.1 MB RAM
31.3 MB 322.98 MB RAM 73.92 MB RAM
3.69 MB 51.87 MB RAM 14.04 MB RAM
281 KB 12.87 MB RAM 6.85 MB RAM

Future enhancements

I would like to add the ability to edit, but it would likely come at the expense of speed and memory usage. I'm pretty happy using Visual Studio to edit XML files, and using XML Explorer for what it's best at. I may dedicate some time to adding edit capabilities in the future.

I will likely be adding XSL transformation in the near future.

Syntax highlighting and the ability to launch the XML editor of your choice were added in the 1.1 release.

The 2.0 release is now available, containing the following changes:

  • Visual Studio .NET style docking panes.
  • Expressions Library to save commonly-used XPaths.
  • Validation pane with support for XSD schema validation.
  • Document tabs are now full-featured panes, with support for tiling horizontally, vertically, etc.
  • Automatic update checking (only notifies when a new release is available).
  • About dialog (Help->About).
  • 'Recently Used Files' list under the File menu.

You can get it, and the code, over at the CodePlex project page. Still haven't had the time to update the article (or write a part 2).

License

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

Share

About the Author

Jason Coon
Web Developer
United States United States
Jason Coon is an Application Developer with ten years experience designing and developing applications.
 
Professional Experience
- Senior Application Developer
- Project Manager
- Microsoft Certified Professional
 
Favorite Pastimes
- Spending time with family and friends
- Snowboarding, wakeboarding, watching movies
 
Projects
- XML Explorer, an extremely fast, lightweight XML file viewer, XPath expression tester, XSD schema validator, and much more. http://www.codeplex.com/xmlexplorer

Comments and Discussions

 
GeneralMy vote of 5 PinmemberKendoTM11-May-11 3:55 
GeneralTags highlight [modified] PinmemberVictor Lapin1-Oct-09 10:56 
GeneralRe: Tags highlight PinmemberJason Coon12-Oct-09 4:53 
GeneralRe: Tags highlight PinmemberVictor Lapin12-Oct-09 5:37 
Generaledit function PinmemberJian123456717-Dec-08 9:17 
QuestionHow to use namespace manager PinmemberGuillaume Hanique15-Sep-08 9:51 
GeneralFails on Namespace even on 1.1.2 Release Pinmembervkanna31-Oct-07 9:54 
GeneralNice app - Added REST Support Pinmemberxph!10-Aug-07 8:24 
GeneralFxCop PinmemberKoen Vingerhoets31-Jul-07 0:00 
GeneralInterface enabled state confusing! PinmemberKoen Vingerhoets24-Jul-07 3:28 
GeneralRe: Interface enabled state confusing! PinmemberJason Coon24-Jul-07 4:08 
GeneralLicense - integration PinmemberKoen Vingerhoets23-Jul-07 23:18 
AnswerRe: License - integration PinmemberJason Coon24-Jul-07 4:01 
GeneralSuggestion Pinmemberyavor nenov19-Jul-07 5:33 
AnswerRe: Suggestion [modified] PinmemberJason Coon19-Jul-07 6:20 
GeneralRe: Suggestion Pinmemberyavor nenov19-Jul-07 6:45 
GeneralSecurity Exception while accessing commandline arguments on startup PinmemberMartin081516-Jul-07 5:08 
GeneralRe: Security Exception while accessing commandline arguments on startup PinmemberMark (Code6) Belles16-Jul-07 7:32 
GeneralRe: Security Exception while accessing commandline arguments on startup PinmemberMartin081516-Jul-07 7:56 
GeneralRe: Security Exception while accessing commandline arguments on startup PinmemberMark (Code6) Belles16-Jul-07 9:44 
GeneralRe: Security Exception while accessing commandline arguments on startup PinmemberMartin081516-Jul-07 9:47 
GeneralRe: Security Exception while accessing commandline arguments on startup PinmemberMark (Code6) Belles16-Jul-07 10:16 
AnswerRe: Security Exception while accessing commandline arguments on startup PinmemberJason Coon16-Jul-07 18:49 
QuestionQuick question PinmemberEd.Poore11-Jul-07 5:52 
AnswerRe: Quick question Pinmemberjcoon11-Jul-07 8:26 
GeneralRe: Quick question PinmemberEd.Poore11-Jul-07 9:27 
GeneralRe: Quick question PinmemberMark (Code6) Belles11-Jul-07 9:34 
GeneralRe: Quick question PinmemberEd.Poore11-Jul-07 9:38 
AnswerRe: Quick question Pinmemberfjurek17-Jul-07 0:20 
GeneralRe: Quick question PinmemberJason Coon19-Jul-07 5:36 
GeneralDownloads Fixed :) Pinmemberjcoon11-Jul-07 2:42 
GeneralTesting PinmemberCliffStanford11-Jul-07 0:50 
AnswerRe: Testing Pinmemberjcoon11-Jul-07 2:45 
QuestionWhere is source code? PinmemberRavikanth Gore10-Jul-07 21:09 
GeneralSource code missing PinmemberTony Bermudez10-Jul-07 20:23 
GeneralBest of luck Pinmemberreji.sukesan10-Jul-07 20:16 
GeneralRe: Best of luck PinmemberAnandChavali11-Jul-07 0:59 
GeneralNeed help in downloading demo and source code... PinmemberAnandChavali10-Jul-07 20:12 
GeneralXPath Query Execution PinmemberMark (Code6) Belles10-Jul-07 19:58 
GeneralRe: XPath Query Execution Pinmemberreji.sukesan10-Jul-07 20:08 
GeneralRe: XPath Query Execution PinmemberMark (Code6) Belles10-Jul-07 20:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 11 Sep 2008
Article Copyright 2007 by Jason Coon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid