☰
UWP 中 Windows Runtime XML API 实战指南:基于 Windows-universal-samples 的 XmlDocument 样例
2026/9/25 2:24:07 网站建设 项目流程
  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

本文基于 Windows-universal-samples 仓库中的 XmlDocument 样例,系统讲解 Windows Runtime 中Windows.Data.Xml.Dom与Windows.Data.Xml.Xsl两个命名空间的常见使用场景:从异步加载 XML、构建 CDATA 节、XPath 查询与 DOM 树修改,到通过XmlLoadSettings配置 DTD 安全策略,再到使用 XSLT 将 XML 转换为 HTML 或新文档。读完本文,你将掌握一套完整的、可直接复制到 C# UWP 项目中的 WinRT XML 处理方案,并了解每个关键 API 在样例源码中的真实调用方式。

样例总览与工程结构

该样例面向Windows 10的 UWP 应用,用 C# 演示了五个典型场景(源码见 Samples/XmlDocument/cs 目录):

场景标题(导航栏)核心 API对应文件
1Build New RSSXmlDocument.LoadFromFileAsync、CreateCDataSectionScenario1_BuildNewRss.xaml.cs
2DOM Load/SaveSelectNodes(XPath)、SaveToFileAsyncScenario2_MarkHotProducts.xaml.cs
3Set Load SettingsXmlLoadSettings.ProhibitDtd/ResolveExternalsScenario3_XmlLoading.xaml.cs
4XPath QuerySelectNodes、SelectSingleNodeScenario4_GiftDispatch.xaml.cs
5XSLT TransformationXsltProcessor.TransformToString/TransformToDocumentScenario5_XSLTTransform.xaml.cs

这五个场景在 SampleConfiguration.cs 中注册为导航项:

List<Scenario> scenarios = new List<Scenario> { new Scenario() { Title = "Build New RSS", ClassType = typeof(Xml.BuildNewRss) }, new Scenario() { Title = "DOM Load/Save", ClassType = typeof(Xml.MarkHotProducts) }, new Scenario() { Title = "Set Load Settings", ClassType = typeof(Xml.XmlLoading) }, new Scenario() { Title = "XPath Query", ClassType = typeof(Xml.GiftDispatch) }, new Scenario() { Title = "XSLT Transformation", ClassType = typeof(Xml.XSLTTransform) } };

所有场景共用一个静态辅助方法Scenario.LoadXmlFile(定义于 SampleConfiguration.cs),它从 UWP 包的安装目录(Package.Current.InstalledLocation)中定位数据文件,并以固定安全设置加载:

public static async Task<XmlDocument> LoadXmlFile(String folder, String file) { StorageFolder storageFolder = await Package.Current.InstalledLocation.GetFolderAsync(folder); StorageFile storageFile = await storageFolder.GetFileAsync(file); XmlLoadSettings loadSettings = new XmlLoadSettings(); loadSettings.ProhibitDtd = false; loadSettings.ResolveExternals = false; return await XmlDocument.LoadFromFileAsync(storageFile, loadSettings); }

从源码结构看,每个场景的初始数据都是打包进应用的静态资源,分别位于 buildRss/rssTemplate.xml、markHotProducts/products.xml、loadExternaldtd/xmlWithExternaldtd.xml、giftDispatch/employees.xml 和 xsltTransform/ 目录。这一点对理解 UWP 的文件访问模型很关键:包内文件通过InstalledLocation只读访问,而用户可写文件则写入ApplicationData.Current.LocalFolder。

场景 1:异步加载 RSS 模板并插入 CDATA 节

场景 1 演示了两个基础能力:XmlDocument.LoadFromFileAsync的异步文件加载,以及用CreateCDataSection向 DOM 树中写入一段不被解析的原始文本。

页面初始化时加载 RSS 模板 rssTemplate.xml:

<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0"> <channel> <title>Pay Less For More</title> <link>http://paylessformore</link> <description>Pay Less For More Homepage</description> <content> </content> </channel> </rss>

注意模板中预留了空的<content>元素。用户在输入框中填写 RSS 内容并点击按钮后,核心处理逻辑如下(摘自 Scenario1_BuildNewRss.xaml.cs):

var doc = new XmlDocument(); scenario1OriginalData.Document.GetText(TextGetOptions.None, out xml); doc.LoadXml(xml); // 从字符串构建 DOM 树 // 创建 CDATA 节并挂到 <content> 元素下 var cdata = doc.CreateCDataSection(rss); var element = doc.GetElementsByTagName("content").Item(0); element.AppendChild(cdata); Scenario.RichEditBoxSetMsg(scenario1Result, doc.GetXml(), true);

这段代码展示了一个常见的“读取—修改—序列化”流程:

  1. LoadXml(xml)把字符串解析为 DOM 文档(与异步的LoadFromFileAsync相对,这里是内存加载);
  2. CreateCDataSection(rss)生成<![CDATA[...]]>节点,保证插入的内容不会被当作 XML 标记解析——对于嵌入脚本、正则表达式等含<、&字符的文本这是正确做法;
  3. GetElementsByTagName("content").Item(0)定位目标元素,AppendChild将 CDATA 节点作为子节点挂入;
  4. 最后GetXml()把修改后的整棵树序列化回字符串显示。

如果输入为空,样例会以红色文本提示 “Please type in RSS content in the [RSS Content] box firstly.”,这是样例中统一的错误反馈方式(由 SampleConfiguration.cs 中的RichEditBoxSetText/RichEditBoxSetError辅助方法实现)。

场景 2:XPath 定位节点、修改属性并保存文件

场景 2 的业务是“标记热销商品”:当商品的十天内销量(Sell10day)超过在店库存(InStore)时,将其hot属性置为 1,并可把结果保存到磁盘。

初始数据 products.xml 结构如下:

<products> <product id="DVD-001" title="Toy story 3" hot="0"> <price>12.99</price> <InStore>35</InStore> <Sell10day>33</Sell10day> </product> <product id="DVD-002" title="The Blind Side" hot="0"> <price>15.99</price> <InStore>12</InStore> <Sell10day>48</Sell10day> </product> </products>

“Mark Hot Products” 按钮的处理函数(Scenario2_MarkHotProducts.xaml.cs)用一条 XPath 谓词完成筛选与修改:

var xpath = "/products/product[Sell10day>InStore]/@hot"; var hotAttributes = doc.SelectNodes(xpath); for (uint index = 0; index < hotAttributes.Length; index++) { hotAttributes.Item(index).NodeValue = "1"; }

几个值得注意的细节:

  • XPath 谓词product[Sell10day>InStore]直接对同元素下的两个子节点做数值比较,只有 DVD-002(48 > 12)满足条件;
  • 表达式以/@hot结尾,SelectNodes返回的是XmlAttribute节点集合,因此通过NodeValue = "1"修改属性值,而不是SetAttribute;
  • 修改完成后doc.GetXml()输出整个更新后的文档,同时把 “Save” 按钮从禁用变为可用。

“Save” 按钮则演示了SaveToFileAsync的用法(Scenario2_MarkHotProducts.xaml.cs):

var file = await ApplicationData.Current.LocalFolder .CreateFileAsync("HotProducts.xml", CreationCollisionOption.GenerateUniqueName); await doc.SaveToFileAsync(file);

这里体现了 UWP 的存储模型:写入目标是应用本地文件夹ApplicationData.Current.LocalFolder,冲突时通过CreationCollisionOption.GenerateUniqueName自动改名,避免覆盖上一次的结果。

场景 3:XmlLoadSettings 的 DTD 安全设置

这是样例中安全性价值最高的场景。加载来自外部、不可信来源的 XML 时,prohibitDTD与resolveExternals两项设置决定了解析器的安全边界:

  • ProhibitDtd(prohibitDTD):为true时禁止 XML 文件使用 DTD。README 明确指出了动机——大量 DTD 实体引用可能导致应用在加载和解析 XML 文件时长时间无响应(即常见的 “billion laughs” 类攻击);
  • ResolveExternals(resolveExternals):决定解析过程中是否解析外部引用(例如外部 DTD 定义)。

样例数据 xmlWithExternaldtd.xml 故意包含一个 DOCTYPE 声明,指向同目录下的外部 DTD 文件 dtd.txt:

<?xml version="1.0" standalone="no"?> <!DOCTYPE dtds SYSTEM "dtd.txt"> <dtds> <dtd> <name>XML DTD sample1</name> <url>http://www.microsoft.com</url> </dtd> ... </dtds>
<!ELEMENT dtds (dtd)+> <!ELEMENT dtd (name,url)> <!ELEMENT name (#PCDATA)> <!ELEMENT url (#PCDATA)>

界面上提供三个单选按钮,对应 Scenario3_XmlLoading.xaml.cs 中的三种设置组合:

选项ProhibitDtdResolveExternals预期行为
RB1truefalse解析抛异常(样例 XML 含 DTD),界面显示 “Error: DTD is prohibited”
RB2falsefalseDTD 声明保留但不解析外部引用
RB3falsetrue解析并解析外部 DTD(见下方路径替换技巧)

该场景同时演示了两种加载入口,二者共用同一套XmlLoadSettings:

按文件加载(LoadFromFileAsync):

var loadSettings = new XmlLoadSettings(); // ... 按单选项设置 ProhibitDtd / ResolveExternals ... var folder = await Package.Current.InstalledLocation.GetFolderAsync("loadExternaldtd"); var file = await folder.GetFileAsync("xmlWithExternaldtd.xml"); var doc = await XmlDocument.LoadFromFileAsync(file, loadSettings);

按缓冲区加载(LoadXmlFromBuffer):先把 XML 字符串写入DataWriter,用DetachBuffer取出IBuffer,再调用doc.LoadXmlFromBuffer(ibuffer, loadSettings)。

从源码中可以看到一个针对 UWP 沙箱的实用技巧(Scenario3_XmlLoading.xaml.cs):当ResolveExternals == true且ProhibitDtd == false时,外部 DTD 的相对路径dtd.txt会被替换为包安装目录下的完整路径:

if (loadSettings.ResolveExternals == true && loadSettings.ProhibitDtd == false) { StorageFolder storageFolder = await Package.Current.InstalledLocation.GetFolderAsync("loadExternaldtd"); String dtdPath = storageFolder.Path + "\\dtd.txt"; xml = xml.Replace("dtd.txt", dtdPath); }

原因在于缓冲方式加载时解析器没有默认的文档位置上下文,必须提供可解析的绝对路径。另外,样例在ProhibitDtd == true时对异常做了“预期内”处理——捕获后仅提示 “Error: DTD is prohibited”,而不是当作程序故障,这为调用方提供了一个处理已知安全拒绝的良好示范。

场景 4:用 XPath 谓词批量筛选节点

场景 4(“Show Anniversary Gift”)演示如何根据工龄区间为员工匹配年礼,本质是三次带数值谓词的SelectNodes查询 + 两次SelectSingleNode取值。

员工数据 employees.xml:

<employees> <employee> <name>Micheal Jackson</name> <department>Customer Support</department> <startyear>2004</startyear> </employee> <employee> <name>Anni Wang</name> <department>Business Partner</department> <startyear>2009</startyear> </employee> <employee> <name>Sail Young</name> <department>Consulting</department> <startyear>2000</startyear> </employee> </employees>

筛选逻辑(Scenario4_GiftDispatch.xaml.cs):

var thisYear = 2012; // 固定为 2012,保证三个礼品档位都能被演示覆盖 var previousOneYear = thisYear - 1; // 2011 var previousFiveYear = thisYear - 5; // 2007 var previousTenYear = thisYear - 10;// 2002 var xpathArray = new String[3]; // 1~4 年:入职年份在 (2007, 2011] 内 xpathArray[0] = "descendant::employee[startyear <= " + previousOneYear + " and startyear > " + previousFiveYear + "]"; // 5~9 年:入职年份在 (2002, 2007] 内 xpathArray[1] = "descendant::employee[startyear <= " + previousFiveYear + " and startyear > " + previousTenYear + "]"; // 10 年及以上:入职年份 <= 2002 xpathArray[2] = "descendant::employee[startyear <= " + previousTenYear + "]"; var Gifts = new String[3] { "Gift Card", "XBOX", "Windows Phone" };

对每条 XPath 执行doc.SelectNodes(xpath)得到节点集合,再对每个员工节点用SelectSingleNode("descendant::name")与SelectSingleNode("descendant::department")取出姓名和部门,通过FirstChild.NodeValue读取文本内容,最终按[姓名]/[部门]/[礼品]的格式输出。

从源码可以读出两处细节:其一,年份基准固定为 2012 而非DateTime.Now.Year,注释解释原因是“保证所有礼品档位都能被发放(演示数据恰好覆盖)”;其二,以固定基准 2012 计算,样例数据中的三位员工分别命中 Gift Card(2004 年、2009 年入职)与 Windows Phone(2000 年入职)两档,XBOX 档在该数据下不会命中。

场景 5:用 XSLT 将 XML 转换为 HTML 或新文档

场景 5 使用Windows.Data.Xml.Xsl命名空间中的XsltProcessor类,演示把源 XML 按 XSL 样式表转换为 HTML 页面(字符串)或转换为新的XmlDocument两个方向。

初始化时(Scenario5_XSLTTransform.xaml.cs)分别从 xsltTransform/ 目录异步加载源 XML xmlContent.xml 与样式表 xslContent.xml 到两个可编辑输入区,因此运行时可以直接修改两者来观察转换结果的差异。

源 XML(带命名空间):

<library xmlns='http://www.microsoft.com'> <book> <chapter></chapter> <chapter> <section> <paragraph a="b">1</paragraph> <paragraph a="b">2</paragraph> </section> </chapter> </book> </library>

XSL 样式表核心是一个匹配根节点的模板,通过xsl:for-each遍历深路径m:library/m:book/m:chapter/m:section/m:paragraph(其中m前缀绑定到http://www.microsoft.com命名空间),把每个段落及其属性a输出为 HTML 表格行。注意样式表中的模板路径必须与源文档的命名空间前缀声明配套,否则for-each选不中节点——这也是 XSLT 处理带命名空间文档时的典型坑。

“TransformToString” 按钮的处理(Scenario5_XSLTTransform.xaml.cs):

doc = new XmlDocument(); doc.LoadXml(xml); xsltDoc = new XmlDocument(); xsltDoc.LoadXml(xslt); // 按样式表声明把 XML 变换为 HTML 字符串 var xsltProcessor = new XsltProcessor(xsltDoc); String transformedStr = xsltProcessor.TransformToString(doc);

“TransformToDocument” 按钮则把结果作为XmlDocument拿到手并落盘(Scenario5_XSLTTransform.xaml.cs):

var xsltProcessor = new XsltProcessor(xsltDoc); XmlDocument transformedDocument = xsltProcessor.TransformToDocument(doc); StorageFolder storageFolder = ApplicationData.Current.LocalFolder; StorageFile xmlFile = await storageFolder.CreateFileAsync("transformed.xml", CreationCollisionOption.ReplaceExisting); await transformedDocument.SaveToFileAsync(xmlFile);

对比场景 2 的GenerateUniqueName,这里选择ReplaceExisting,即每次运行都覆盖同一个transformed.xml,两种CreationCollisionOption的差异正好在这两个场景中得到了对照。此外该场景把“源 XML 无效”“XSL 无效”“变换失败”分成三段try/catch,分别把对应的输入框文字标红并给出具体错误消息,是处理多输入依赖型操作时值得借鉴的异常组织方式。

构建与运行

前提条件为Windows 10与 Microsoft Visual Studio(UWP 开发工作负载)。按照 Samples/XmlDocument/README.md 中的步骤:

  1. 如果下载的是样例集合 ZIP,务必解压整个归档(本样例依赖根目录下的 LICENSE 等共享内容),而不仅解压本样例文件夹;
  2. 打开 Visual Studio,选择文件 > 打开 > 项目/解决方案;
  3. 进入解压目录的Samples/XmlDocument/cs子目录,双击 XmlDocument.sln 加载解决方案(本样例仅提供 C# 版本,工程文件为 XmlDocument.csproj);
  4. 按Ctrl+Shift+B,或选择生成 > 生成解决方案完成构建。

部署与运行:

  • 仅部署:选择生成 > 部署解决方案;
  • 调试并运行:按F5,或选择调试 > 开始调试;不调试直接运行按Ctrl+F5(调试 > 开始执行但不调试)。

运行后从左侧导航栏选择五个场景之一即可分别验证上述各 API 的行为。

进一步参考

  • WinRT 类型参考:Windows.Data.Xml.Dom.XmlDocument与Windows.Data.Xml.Xsl.XsltProcessor(样例 README 的 “Related topics” 指向官方文档);
  • 仓库中另有已归档的 JavaScript 版本实现,可对比不同语言下的 API 形态:archived/XmlDocument/README.md;
  • 本样例同仓库的 Samples/XmlLite/README.md 演示了更底层的XmlReader/XmlWriter解析器,适合处理大型 XML 时与本文的 DOM 方案作选型对比。

从整体结构看,这五个场景覆盖了 UWP 中处理 XML 的完整链路——加载(文件/字符串/缓冲区)→ 查询(XPath/SelectNodes)→ 修改(节点/属性)→ 安全控制(XmlLoadSettings)→ 输出(字符串/文档/文件/HTML),每个场景的代码量都很小且相互独立,是阅读 WinRT XML API 的合适入口。

  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载
上一篇:Windows右键菜单管理终极指南:让右键菜单焕然一新的ContextMenuManager
下一篇:NVIDIA Profile Inspector DLSS功能异常修复指南:3步快速解决未知设置问题

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询