Go to the CodePlex project site for the latest releases and source code.
Contents
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.
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
. XPathDocument
s 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.
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:
XPathDocument document = new XPathDocument(_filename);
this.xmlTreeView.Navigator = document.CreateNavigator();
The XmlExplorerTabPage
can be programmatically added to any standard TabControl
:
XmlExplorerTabPage tabPage = new XmlExplorerTabPage();
this.tabControl.TabPages.Add(tabPage);
tabPage.Open(filename);
The TabbedXmlExplorerWindow
can be shown, in process, from your own applications.
TabbedXmlExplorerWindow window = new TabbedXmlExplorerWindow();
window.Open(filename);
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 TreeNode
s 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 TreeNode
s for the child XML elements, cloning the navigator for each node to maintain a cursor for future expansion.
XPathNodeIterator iterator =
treeNode.Navigator.SelectChildren(XPathNodeType.All);
foreach (XPathNavigator navigator in iterator)
{
XPathNavigatorTreeNode node =
new XPathNavigatorTreeNode(navigator.Clone());
treeNodeCollection.Add(node);
}
The implementation of XPathNavigatorTreeNode
is pretty straightforward. Here is how I construct the text for the node:
public string GetDisplayText()
{
if (_navigator == null)
return string.Empty;
StringBuilder builder = new StringBuilder();
switch (_navigator.NodeType)
{
case XPathNodeType.Comment:
builder.Append("");
break;
case XPathNodeType.Root:
case XPathNodeType.Element:
builder.AppendFormat("<{0} ", _navigator.Name);
if (_navigator.HasAttributes)
{
XPathNavigator attributeNavigator = _navigator.Clone();
if (attributeNavigator.MoveToFirstAttribute())
{
do
{
builder.AppendFormat("{0}=\"{1}\" ",
attributeNavigator.Name, attributeNavigator.Value);
}
while (attributeNavigator.MoveToNextAttribute());
}
}
if (!_navigator.HasChildren)
{
builder.Append("/>");
}
else
{
builder.Append(">");
}
break;
default:
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.
private void BeginLoadFile()
{
_loadFileThread = new Thread(new ThreadStart(this.LoadFile));
_loadFileThread.IsBackground = true;
_loadFileThread.Start();
}
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;
XPathDocument document = new XPathDocument(_filename);
Debug.WriteLine(string.Format("Done. Elapsed: {0}ms.",
DateTime.Now.Subtract(start).TotalMilliseconds));
MethodInvoker del = delegate()
{
this.LoadDocument(document);
};
this.Invoke(del);
if (this.LoadingFileCompleted != null)
this.LoadingFileCompleted(this, EventArgs.Empty);
}
catch (ThreadAbortException ex)
{
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);
}
}
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:
window.WindowState = Properties.Settings.Default.WindowState;
...
Properties.Settings.Default.Save();
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 |
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).