![]() |
Desktop Development »
Miscellaneous »
General
Intermediate
License: The Code Project Open License (CPOL)
XML ExplorerBy Jason CoonAn extremely fast, lightweight XML file viewer. |
C# 2.0.NET 2.0, WinXP, VistaVS2005, Dev
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Go to the CodePlex Project Site for the latest releases and source code.

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 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 the large files, and doesn't offer the features of VS2005.
I explored several open-source solutions, including ones from Code Project, 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's supports multiple document tabs, with support for middle-clicking to close tabs. It can 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.
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, the XPathDocument "provides a fast, read-only, in-memory representation of an XML document using the XPath data model". Sounded good to me.
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.
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);
}
The implementation of the 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();
}
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());
}
}
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();
I've compared the times required to load XML files of varying size. 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 |
| 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 |
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:
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).
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 11 Sep 2008 Editor: Sean Ewington |
Copyright 2007 by Jason Coon Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |