- 示例工程
【免费下载链接】Windows-universal-samples
API samples for the Universal Windows Platform.
本文基于 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 | 对应文件 |
|---|---|---|---|
| 1 | Build New RSS | XmlDocument.LoadFromFileAsync、CreateCDataSection | Scenario1_BuildNewRss.xaml.cs |
| 2 | DOM Load/Save | SelectNodes(XPath)、SaveToFileAsync | Scenario2_MarkHotProducts.xaml.cs |
| 3 | Set Load Settings | XmlLoadSettings.ProhibitDtd/ResolveExternals | Scenario3_XmlLoading.xaml.cs |
| 4 | XPath Query | SelectNodes、SelectSingleNode | Scenario4_GiftDispatch.xaml.cs |
| 5 | XSLT Transformation | XsltProcessor.TransformToString/TransformToDocument | Scenario5_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);这段代码展示了一个常见的“读取—修改—序列化”流程:
LoadXml(xml)把字符串解析为 DOM 文档(与异步的LoadFromFileAsync相对,这里是内存加载);CreateCDataSection(rss)生成<![CDATA[...]]>节点,保证插入的内容不会被当作 XML 标记解析——对于嵌入脚本、正则表达式等含<、&字符的文本这是正确做法;GetElementsByTagName("content").Item(0)定位目标元素,AppendChild将 CDATA 节点作为子节点挂入;- 最后
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 中的三种设置组合:
| 选项 | ProhibitDtd | ResolveExternals | 预期行为 |
|---|---|---|---|
| RB1 | true | false | 解析抛异常(样例 XML 含 DTD),界面显示 “Error: DTD is prohibited” |
| RB2 | false | false | DTD 声明保留但不解析外部引用 |
| RB3 | false | true | 解析并解析外部 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 中的步骤:
- 如果下载的是样例集合 ZIP,务必解压整个归档(本样例依赖根目录下的 LICENSE 等共享内容),而不仅解压本样例文件夹;
- 打开 Visual Studio,选择文件 > 打开 > 项目/解决方案;
- 进入解压目录的
Samples/XmlDocument/cs子目录,双击 XmlDocument.sln 加载解决方案(本样例仅提供 C# 版本,工程文件为 XmlDocument.csproj); - 按
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.
相关推荐
UWP 中 ListView 与 GridView 的实战指南:基于 Windows-universal-samples XamlListView 示例的源码解析
UWP 中 ListView 与 GridView 的实战指南:基于 Windows universal samples XamlListView 示例的源码解
示例工程SleeperX:macOS系统级电源管理框架的技术实现与应用
SleeperX:macOS系统级电源管理框架的技术实现与应用 在macOS生态系统中,电源管理一直是一个复杂而关键的技术领域。对于需要在特定场景下保持系统活跃
示例工程UWP 多窗口实战:基于 AppWindow 的 Windows-universal-samples 二次窗口开发指南
UWP 多窗口实战:基于 AppWindow 的 Windows universal samples 二次窗口开发指南 导读 本指南以 Windows univ
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考