XML 字符串浏览器(就像 Internet Explorer)使用 WebBrowser 控件






4.48/5 (13投票s)
本文将向您展示如何在不使用 XML 文件的情况下,在浏览器中(类似于 Internet Explorer)以彩色可折叠树视图显示 XML 字符串/XmlDocument。
引言
本文将向您展示如何像 Internet Explorer 渲染 XML 文件一样渲染 **XML 字符串** 或 `System.Xml.XmlDocument`。表面上看,这似乎是一项简单的任务。**错了!**如果您认为这是一项简单的任务,那么您会惊讶于自己错了(我也是)。本示例中的用户控件很有用,因为许多现代技术都依赖于 XML。因此,缺少一种简单的方法来以美观的格式显示*中间*或*生成的* XML 是不令人满意的。这就是我创建此控件的原因——因为我找不到像这样的控件!是否曾想过如何在不使用 XML 文件的情况下显示格式化的 XML 字符串或 XML 文档?您是否像我一样绝望,因为 `WebBrowser` 控件没有提供简单的方法来实现这一点?如果是这样,请不要再犹豫了——本文将为您提供所需的答案。
下面的屏幕截图描绘了示例 XML 文件的渲染。在渲染**之前**,该文件已加载到 `string` 中。
背景
多年来,我一直在架构和开发解决方案,并从互联网上获益良多。我无法告诉您搜索某物节省了我多少时间。有这么多很棒的文章和很棒的作者。这就是为什么我决定写我的第一篇文章——回馈给我很多东西的社区。此外,我还花了几个小时寻找一种方法来以类似于 Internet Explorer 的方式显示 XML `string`。
我感到惊讶的是,尽管许多现代技术都依赖于 XML,但 `WebBrowser` 控件本身并不像 Internet Explorer 那样允许您查看 XML(除非您正在查看 XML 文件)。依赖 XML 文件存在来查看格式化的 XML 是完全不可取的。
与许多开发项目一样,这一个也经历了数小时(数天)的研究。
我的旅程始于发现一个 XSL 文件(称为*DEFAULTSS.XSL*),该文件使用了旧的不受支持的 XSL 命名空间“*http://www.w3.org/TR/WD-xsl*”。最初,将此现代化为 XSLT 1.0 的努力并不完全成功,因为缺乏一种简单的方法来选择和渲染 `namespace` 和 `CData` 部分。
幸运的是,我记得旧的做事方式(`FreeThreadedDOMDocument` 和老式的 `XSLTemplate`)。旧的 XSL 处理器仍然可以在 .NET 中使用。这就是我所需要的突破。在那之后,我对完成 *DEFAULTSS.XSL* 文件的 XSLT 1.0 版本(Internet Explorer 使用它来渲染 XML 文件)进行了重要(且富有挑战性)的更改。该版本最初由 Steve Muench 转换,您可以在此处找到。
自写这篇文章以来,我尝试了几种新的 XML 渲染方法。一种是使用 SAXON 解析器(*XSLTHtml20.xslt*);另一种(我认为是最好的方法)是使用命名空间轴并在将 `CData` 节点传递给 XSLT 转换之前对其进行转义(*XmlToHtml10Basic.xslt*)。
Using the Code
本文的源代码包含两个项目:`XmlRender` 和 `XmlRenderTestApp`。
名称 | 描述 |
XmlRender | 此项目包含几个 `static` 类方法
它还包含 `XmlBrowser` 控件,该控件扩展了 `WebBrowser`,并带有三个设置:
所有这些属性都用于向浏览器指示我们希望显示格式化的 XML(以及它应该如何渲染)。 |
XmlRenderTestApp | 此项目包含一个 Web 窗体,其中有一个下拉列表用于选择 XML 文件,单选按钮用于指示我们是否要使用 XSL 或 XSLT 转换,以及 `XmlBrowser` 控件。每个文件都会加载到一个 `string` 中,并设置 `XmlBrowser` 的 `XmlDocument` 属性。 |
XmlRender 代码
/// <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 代码
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 代码
// 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
}
关注点
我曾考虑在此列出 *XmlToHtml10.xslt*/*XmlToHtml10Plus.xslt* 文件,但它们很大。但我会告诉您其中的亮点:
- *XmlToHtml10.xslt* 在 JavaScript 中使用了一些 `MSXML2.FreeThreadedDOMDocument`(我最初曾考虑使用正则表达式,但一开始没有这样做,因为我不想花太多时间来写这篇文章)。
- *XmlToHtmlPlus.xslt* 只使用正则表达式。它**不**使用 `MSXML2.FreeThreadedDOMDocument`。这尤其好,因为我认为它可能更有效。
- XSLT 中有一些巧妙的 XPath 构建,用于将当前上下文节点标识给 JavaScript 函数。(请参阅关于 `CDATA` 处理的部分。)
- 有关 XSLT 递归的有趣示例,请查看 `xmlnsSelector` 模板或 `getXPath` 模板。XSLT 递归总是很有趣的。
- 最后但并非最不重要的,如果您在 VS2005 中调试/显示 *XmlToHtml10.xslt*/*XmlToHtml10Plus.xslt* 的输出,它将无法利用我添加的有趣部分。您只能在运行时看到这一点。
我实际上正在写另一篇文章,演示如何通过在运行时向通用 `DataSet` 提供 XSD,轻松地用从任何数据库检索到的表数据来塑造 `DataSet`。缺乏 XML 可视性导致我分心并先写了这篇文章!您可以此处找到我提到的文章。
希望您喜欢这篇文章。我真的很享受思考它带来的挑战。
以下是此项目中包含的转换文件的简要概述:
名称 | XSLT 处理器 | 使用扩展 | 描述 |
XMLToHTML.xsl | XSL | 否 | 原始 Microsoft XSL |
XMLToHTML10.xslt | XSLT1.0 | 是 | 使用 `FreeThreadDomDocument`/正则表达式和 XSLT 来访问命名空间节点和 `CDATA` |
XMLToHTML10Plus.xslt | XSLT1.0 | 是 | 仅使用正则表达式来访问 `CDATA` 和命名空间节点 |
XMLToHTML10Cdata.xslt | XSLT1.0 | 是 | 使用正则表达式来访问 `CDATA`,并使用命名空间轴来访问命名空间 |
XMLToHTML10Basic.xslt | XSLT1.0 | 否 | 使用 `CDATA` 的转义和命名空间轴 |
XMLToHtml20.xslt | XSLT2.0 | 否 | 不渲染命名空间或 `CDATA` |
一个普遍的观点是:不使用 JavaScript 扩展的转换比使用 JavaScript 扩展的转换性能更好。
历史
- 2008 年 3 月 12 日:首次发布
- 2008 年 3 月 13 日:修复了渲染问题,现在 XML `string` 的显示与 Internet Explorer 查看 XML 文档完全相同。添加了 `XMLBrowser` 控件,具有两个属性:`XmlDocument` 和 `XmlText`。这是为了将 XML 格式化封装到 `XmlRender` 类库中可重用的用户控件。
- 2008 年 3 月 17 日:添加了 *DEFAULTSS.XSL* 的 XSLT 等效版本。另外,在测试应用程序中添加了一些复选框,允许在 XSL/XSLT 转换之间切换。
- 2008 年 3 月 18 日:修改窗体以自动填充 *$(TargetDir)\XML Files* 目录中的文件。文件在构建项目时(通过构建事件)自动复制到此目录。无需为每个文件设置构建选项(只需将其复制到 `XmlRenderTestApp` 项目中的 XML 文件目录)。
- 2008 年 3 月 24 日:添加了 XSLT 文件的纯正则表达式版本(称为 *XmlToHtml10Plus.xslt*)。另外,对 *XmlToHtml10.xslt* 进行了轻微的更正,以保持一致性。
- 2008 年 3 月 26 日:对 *XmlToHtml10Plus.xslt* 和 *XmlToHtml10.xslt* 中与 `xmlns` 类属性相关的部分进行了小修复。
- 2008 年 4 月 4 日:添加了 SAXON 实现的 XML 渲染——仍在进行中,但具有非常酷的功能。
- 2008 年 4 月 7 日:使用 Microsoft XSLT 解析器的大型 XML 选项不会添加命名空间和 `CDATA` 节点。速度非常快。
- 2008 年 4 月 14 日:使用 Microsoft XSLT 解析器的大型 XML 选项将添加命名空间和 `CDATA` 节点。速度非常快。使用命名空间轴和 `CDATA` 的转义,以便可以在 XSLT 中使用它。