Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

XML String Browser (just like Internet Explorer) using the WebBrowser Control

4.48/5 (15 votes)
14 Apr 2008CPOL6 min read 1   3.2K  
This article will show you how to view a colourful collapsible treeview for XML Strings/XmlDocument in a browser (just like Internet Explorer) without the need for XML files.

Introduction

This article will show you how to render an XML string or System.Xml.XmlDocument in the same way Internet Explorer renders an XML file. On the surface, this seems like a simple task. Wrong! If you believe that this is a simple task, you would be surprised how wrong you would be (I was). The user control in this example is useful because a lot of modern technologies depend on XML. As such, the absence of a simple way to display intermediate or generated XML in a presentable format is not satisfactory. That's why I created this control - because I couldn't find one like this one anywhere! Ever wondered how to display a formatted XML string or XML document without the need for an XML file? Do you despair, like I did, because the WebBrowser control doesn't present a simple way to do this? If so, look no further - this article presents the answer you seek.

The following screenshot depicts the rendering of a sample XML file. The file is loaded into a string before rendering.

XMLBrowser/xmlrender_20.jpg

Background

I have been architecting and developing solutions for many years, and have benefited a lot from the internet. I can't tell you the number of times searching for something has saved me countless hours. There are so many cool articles and cool people writing them. This is why I decided to write my first article - to give back to a community that has given me a lot. Also, because, I spent a few hours searching for a way to display an XML string in a way similar to how Internet Explorer does it.

I am surprised that although a lot of modern technologies depend on XML, the WebBrowser control does not natively allow you to view XML the way Internet Explorer does (unless you are viewing an XML file). Having to rely on the presence of an XML file to view formatted XML is not at all desirable.

As with many development endeavors, hours (several days) of research went into this one.

My journey began by discovering an XSL file (referred to as DEFAULTSS.XSL) that was using the old unsupported XSL namespace "http://www.w3.org/TR/WD-xsl". Efforts to modernize this to XSLT 1.0 were, initially, not completely fruitful due to the lack of a simple way to select and render namespaces and CData sections.

Luckily, I remembered the old way of doing things (FreeThreadedDOMDocument and the good old XSLTemplate). The old XSL processor can still be used in .NET. That was the break I needed. After that, I made important (and challenging) changes to complete an XSLT 1.0 version of the DEFAULTSS.XSL file (used by Internet Explorer to render XML files). This version was originally converted by Steve Muench, and can be found here.

Since I first wrote this article, I've tried a couple of new ways to render XML. One includes using the SAXON parser (XSLTHtml20.xslt); the other way (which I believe is the best way) includes using the namespace axis and escaping CData nodes before passing to XSLT transformation (XmlToHtml10Basic.xslt).

Using the Code

The code for this article consists of two projects, XmlRender and XmlRenderTestApp.

Name Description
XmlRender This project contains a few static class methods:
  • RenderXmlToHtml.Render(XmlDocument xmlToRender,XmlRender.XmlBrowser.XslTransformType xslTransformType)
  • RenderXmlToHtml.Render(System.Xml.XmlDocument xmlToRender)
  • RenderXmlToHtml.Render(string xmlToRender)

It also contains the XmlBrowser control which extends the WebBrowser with three settings:

  • System.Xml.XmlDocument XmlDocument
  • string XmlText
  • XmlRender.XmlBrowser.XslTransformType

All of these properties are used to indicate to the browser that we would like to display formatted XML (and how it should be rendered).

XmlRenderTestApp This project contains a web form with a dropdown to select XML files, radio buttons to indicate whether or not we want to use an XSL or XSLT transformation, and the XmlBrowser control. Each file is loaded into a string, and sets the XmlDocument property of the XmlBrowser.

XmlRender Code

C#
/// <summary />
/// This class has the methods required to render XML as pretty HTML.
/// </summary />
internal static class RenderXmlToHtml
{
    #region Render(XmlDocument xmlToRender, 
            XmlBrowser.XslTransformType xslTransformType)
    /// <summary>
    /// This method determines which output type to render
    /// </summary>
    internal static string Render(XmlDocument xmlToRender, 
             XmlBrowser.XslTransformType xslTransformType)
    {
        if (xslTransformType == XmlBrowser.XslTransformType.XSL)
            return Render(xmlToRender.OuterXml);
        else if (xslTransformType == XmlBrowser.XslTransformType.XSLT10)
            return Render(xmlToRender,XmlRender.Properties.Resources.XmlToHtml10);
        else if (xslTransformType == XmlBrowser.XslTransformType.XSLT10RegExp)
            return Render(xmlToRender,
                   XmlRender.Properties.Resources.XmlToHtml10Plus);
        return string.Empty;
    }
    #endregion

    #region Render(string xmlToRender)
    /// <summary>
    /// This method has to use the old XSL Processor because of the fact that the
    /// http://www.w3.org/TR/WD-xsl namespace is unsupported.
    ///
    /// The simplest way was to use the old xsl instead of re-inventing the wheel.
    /// I could not find a way to easily fully convert this to XSLT 1.0 due to 
    /// the lack of namespace and cdata 'selectability'.
    ///
    /// This brings back memories :)
    /// </summary>
    internal static string Render(string xmlToRender)
    {
        XSLTemplate oXSLT;
        FreeThreadedDOMDocument oStyleSheet;
        IXSLProcessor oXSLTProc;
        DOMDocument oXMLSource;

        try
        {
            oXSLT = new XSLTemplate();
            oStyleSheet = new FreeThreadedDOMDocument();
            oXMLSource = new DOMDocument();

            oStyleSheet.async = false;
            oStyleSheet.loadXML(XmlRender.Properties.Resources.XMLToHTML);

            oXSLT.stylesheet = oStyleSheet;

            oXSLTProc = oXSLT.createProcessor();

            oXMLSource.async = false;
            oXMLSource.loadXML(xmlToRender);
            oXSLTProc.input = oXMLSource;

            oXSLTProc.transform();

            return oXSLTProc.output.ToString();
        }
        catch (Exception e)
        {
            return e.Message;
        }
        finally
        {
            oXSLT = null;
            oStyleSheet = null;
            oXMLSource = null;
            oXSLTProc = null;
        }
    }
    #endregion

    #region Render(XmlDocument xmlToRender)
    /// <summary>
    /// This method uses the XsltCompiledTransform to transform XML to pretty HTML.
    /// Unfortunately, this method does require
    /// using EXSL/MSXML but it is XSLT 1.0 compliant.
    /// </summary>
    internal static string Render(XmlDocument xmlToRender,string xsltDocument)
    {
        XslCompiledTransform xslCompiledTransform;
        XmlReader xmlReader;
        System.IO.StringReader stringReader;
        StringBuilder stringBuilder;
        XmlWriter xmlWriter;
        XsltSettings xsltSettings;
        try
        {
            xslCompiledTransform = new XslCompiledTransform(true);
            stringReader = new System.IO.StringReader(xsltDocument);
            xmlReader = XmlReader.Create(stringReader);
            xsltSettings = new XsltSettings(true, true);
            xslCompiledTransform.Load(xmlReader,xsltSettings,new XmlUrlResolver());

            stringBuilder = new StringBuilder();
            xmlWriter = XmlWriter.Create(stringBuilder);
            XsltArgumentList a = new XsltArgumentList();
            // Need to pass the xml string as an input parameter so
            // we can do some parsing for extra bits that XSLT won't do.
            a.AddParam("xmlinput", string.Empty, xmlToRender.OuterXml);
            xslCompiledTransform.Transform(xmlToRender, a, xmlWriter);
            return stringBuilder.ToString();
        }
        catch (Exception e) { return e.Message; }
        finally
        {
            xslCompiledTransform = null;
            xmlReader = null;
            stringReader = null;
            stringBuilder = null;
            xmlWriter = null;
            xsltSettings = null;
        }
    }
    #endregion
}

XmlRenderTestApp Code

C#
public partial class Form1 : Form
{
    // used to store location of xml files
    string _xmlDirectory;

    #region Constructor
    /// <summary>
    /// Constructor
    /// </summary>
    public Form1()
    {
        InitializeComponent();
        // Set up xmlDirectory and load combo box. 
        //
        // when you build the project all files are copied to $(TargetDir)\XML Files
        // just add any additional files you want to the XML files directory of this project
        FileInfo fi = new FileInfo(Assembly.GetExecutingAssembly().FullName);
        _xmlDirectory = fi.DirectoryName + @"\XML Files\";
        foreach(FileInfo file in new DirectoryInfo(_xmlDirectory).GetFiles("*.*"))
        {
            comboBox1.Items.Add(file.Name);
        }
    }
    #endregion

    #region comboBox select
    /// <summary>
    /// Xml file to load into a string for rendering.
    /// </summary>
    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        XmlDocument _xd;
        try
        {
            _xd = new XmlDocument();
            string fileName = _xmlDirectory + comboBox1.SelectedItem.ToString();
            System.Uri _uri = new Uri(fileName);

            _xd.Load(fileName);

            // the following lines are of particular interest. 
            // I'm rendering an XML string to HTML (using good old XSL).
            // This gives us the flexibility to build XML and 
            // present it in the browser in a nice way.
            //
            // I have to do this because we can't send an XML string to the browser 
            // and expect it to render like IE does

            // Send an XmlDocument to the XmlBrowser
            xmlBrowser1.XmlDocument = _xd;

            // Uncomment the following to send Xml Text to the XmlBrowser
            //xmlBrowser1.XmlText = _xd.OuterXml;

            // Uncomment the following to see how
            // the webbrowser control renders XML text
            //xmlBrowser1.DocumentText = _xd.OuterXml;

            // Uncomment the following to compare
            // to WebBrowser native rendering of Xml Documents
            //xmlBrowser1.Url = _uri;
        }
        finally
        {
            _xd = null;
        }
    }
    #endregion

    #region RadioButtons
    private void radioButton1_CheckedChanged(object sender, EventArgs e)
    {
        xmlBrowser1.XmlDocumentTransformType = 
           XmlRender.XmlBrowser.XslTransformType.XSL;
    }

    private void radioButton2_CheckedChanged(object sender, EventArgs e)
    {
        xmlBrowser1.XmlDocumentTransformType = 
           XmlRender.XmlBrowser.XslTransformType.XSLT10;
    }
    private void radioButton3_CheckedChanged(object sender, EventArgs e)
    {
        xmlBrowser1.XmlDocumentTransformType = 
           XmlRender.XmlBrowser.XslTransformType.XSLT10RegExp;
    }
    #endregion

}

XmlBrowser Code

C#
// Use this control if you want a webBrowser which has been extended 
// to allow for Xml Rendering
public partial class XmlBrowser : WebBrowser
{
    #region Types/Private Variables
    /// <summary>
    /// Type to determine which XSL/XSLT to use in transformation
    /// </summary>
    public enum XslTransformType
    {
        XSL,
        XSLT10,
        XSLT10RegExp
    }
    // XmlDocument to store Xml data for rendering
    private XmlDocument _xmlDocument;
    // XslDocument to use when rendering Xml
    private XslTransformType _xslTransformType;
    #endregion

    #region Constructor
    public XmlBrowser()
    {
        InitializeComponent();
    }
    #endregion

    #region Properties

    #region XmlText
    /// <summary>
    /// Property to set Xml Text for webbrowser
    /// </summary>
    [Category("XmlData")]
    [Description("Use this property to set the XmlText 
                  for rendering in the webbrowser.")]
    public string XmlText
    {
        set
        {
            if (value != string.Empty)
            {
                _xmlDocument = new XmlDocument();
                _xmlDocument.LoadXml(value);
                this.DocumentText = 
                  RenderXmlToHtml.Render(_xmlDocument,_xslTransformType);
            }
        }
        get
        {
            if (_xmlDocument == null)
                return string.Empty;
            else
                return _xmlDocument.OuterXml;
        }
    }
    #endregion

    #region XmlDocument
    /// <summary>
    /// Property to set XmlDocument for webbrowser
    /// </summary>
    [Category("XmlData")]
    [Description("Use this property in your code to set the 
          System.Xml.XmlDocument for rendering in the webbrowser.")]
    public XmlDocument XmlDocument
    {
        get
        {
            return _xmlDocument;
        }
        set
        {
            if (value != null)
            {
                _xmlDocument = value;
                this.DocumentText = 
                  RenderXmlToHtml.Render(_xmlDocument,_xslTransformType);
            }
        }
    }
    #endregion

    #region XmlDocumentTransformType
    /// <summary>
    /// Change this to determine which transform type to use
    /// </summary>
    [Category("XmlData")]
    [Description("Use this property to specify to use either 
       the XSL or XSLT 1.0 compliant stylesheet for rendering.")] 
    public XslTransformType XmlDocumentTransformType
    {
        get
        {
            return _xslTransformType;
        }
        set
        {
            _xslTransformType = value;
        }
    }
    #endregion

    #endregion
}

Points of Interest

I thought about listing the XmlToHtml10.xslt/XmlToHtml10Plus.xslt file(s) here, but they are pretty big. I will tell you about the highlights though:

  • XmlToHtml10.xslt does use MSXML2.FreeThreadedDOMDocument a little bit in the JavaScript (I initially thought about using Regular Expressions, but I didn't do this at first because I didn't want to spend a lot of time writing this article).
  • XmlToHtmlPlus.xslt only uses Regular Expressions. It does not use MSXML2.FreeThreadedDOMDocument. This is particularly good because I'm thinking that it might be more efficient.
  • There is some clever use of XPath building in the XSLT to identify the current context node to the JavaScript function. (See the section on CDATA handling.)
  • For some interesting examples of XSLT recursion, take a look at the xmlnsSelector template or the getXPath template. XSLT recursion is always fun.
  • Last but not least, if you debug/show the output of XmlToHtml10.xslt/XmlToHtml10Plus.xslt in VS2005, it won't take advantage of the interesting bits I've added. You can only see this at runtime.

I actually was writing another article to demonstrate how easy it is to shape a DataSet with table data retrieved from any database by supplying an XSD at run-time to a generic DataSet. The lack of XML viewability caused me to get side-tracked and write this article first! You can find the article I am referring to here.

I hope you like this article. I really enjoyed thinking about the challenges it presented.

Here is a brief overview of the transformation files included in this project:

Name XSLT Processor Uses Extensions Description
XMLToHTML.xsl XSL No Original Microsoft XSL
XMLToHTML10.xslt XSLT1.0 Yes Uses FreeThreadDomDocument/Regular Expressions with XSLT to get at namespace nodes and CDATA
XMLToHTML10Plus.xslt XSLT1.0 Yes Uses only Regular Expressions to get at CDATA and namespace nodes
XMLToHTML10Cdata.xslt XSLT1.0 Yes Uses Regular Expressions to get at CDATA, and namespace axis to get at namespaces
XMLToHTML10Basic.xslt XSLT1.0 No Uses escaping of CDATA and namespace axis
XMLToHtml20.xslt XSLT2.0 No No rendering of namespaces or CDATA

A general point - the transformations that don't use JavaScript extensions perform better than the ones that do.

History

  • 12-Mar-2008: Initial release
  • 13-Mar-2008: Fixed a rendering issue so that an XML string now displays exactly like Internet Explorer viewing an XML document. Added the XMLBrowser control with two properties: XmlDocument and XmlText. This is to encapsulate XML formatting to a reusable user control within the XmlRender class library.
  • 17-Mar-2008: Added the XSLT equivalent for DEFAULTSS.XSL. Also, added some checkboxes to the test app to allow toggling between XSL/XSLT transformation.
  • 18-Mar-2008: Modified form to automatically populate based on files in the $(TargetDir)\XML Files directory. Files are automatically copied to this directory when building the project (via build events). No need to set build options on each file (just copy them to the XML files directory in the XmlRenderTestApp project.
  • 24-Mar-2008: Added the Regular Expression only version of the XSLT file (called XmlToHtml10Plus.xslt). Also, made a slight correction to the XmlToHtml10.xslt, for consistency.
  • 26-Mar-2008: Minor fix to XmlToHtml10Plus.xslt and XmlToHtml10.xslt with regards to xmlns-like attributes.
  • 04-Apr-2008: Added SAXON implementation of XML rendering - still a work in progress, but has really cool functionality.
  • 07-Apr-2008: Large XML option using the Microsoft XSLT parser will not add namespaces and CDATA nodes. Very quick.
  • 14-Apr-2008: Large XML option using the Microsoft XSLT parser will add namespaces and CDATA nodes. Very quick. Uses the namespace axis and escaping of CDATA so that it can be used in XSLT.

References

License

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