第11章 其他 XML 技术

概述

System.Xml 命名空间由以下命名空间和核心类型构成:

  • System.Xml.*
  • XmlReader​ 和 XmlWriter​:高性能、前向读写的 XML 流
  • XmlDocument​:基于 W3C 标准 DOM(已过时)的 XML 文档
  • System.Xml.Linq​:全新的以 LINQ 为中心的 XML DOM(请参见第10章 LINQ to XML
  • System.Xml.XmlSchema​:为 W3C 的 XSD 大纲提供的基础类型和 API
  • System.Xml.Xsl​:使用 W3C 的 XSLT 对 XML 进行转换的基础类型和 API(XslCompiled Transform)
  • System.Xml.Serialization​:提供了类和 XML 之间的序列化功能(请参见第17章 序列化)

其中,W3C 是 World Wide Web Consortium(万维网联盟)的缩写,该组织定义了 XML 标准。

11.1 XmlReader

XmlReader​ 是一个高性能的类,它能够以 层次、前向的方式读取 XML 流。​XmlReader​ 可能会从一些较慢的数据源(例如 Stream​ ​和 URI)读取数据,因此它的大多数方法都提供了异步版本。

XmlReader​ 是一个抽象类(abstract),它通过工厂方法 Create() ​ 创建实例,用法如下:

using XmlReader reader = XmlReader.Create (new System.IO.StringReader (myString));

此外,XmlReader​ 还有一个因子类型 XmlReaderSettings​,用于设置读取的一些参数:

11.1.0 XmlReaderSettings

XmlReaderSettings​ 有若干属性。

跳过无关内容的有:

  • IgnoreComments​ 属性:是否忽略 注释节点
  • IgnoreProcessingInstructions​ 属性:是否忽略 处理指令
  • IgnoreWhitespace​ 属性:是否忽略 空白字符

读取片段的有:

  • ConformanceLevel​ 属性:ConformanceLevel​ 枚举类型,用于指示 所读取的内容是部分节点还是完整文档

关于流的有:

  • CloseInput​ 属性:用于指示关闭 XmlReader​ 时是否 关闭底层流 。默认为 true。

    XmlWriterSettings​ 有类似属性 CloseOutput​,默认为 true

11.1.1 读取节点

XmlReader​ 的主要成员有:

  • Read() ​ 方法:读取 XML 流的下一个节点,它类似于 IEnumerator.MoveNext()​ 方法。当该方法返回 false ,说明流已读完。
  • NodeType ​ 属性:为 XmlNodeType​ 类型,用于指明节点类型;
  • Name ​ 和 Value ​ 属性:用于提供节点名称和内容,开发者可以根据 NodeType ​ 属性确定这两个属性是否包含内容

以下是 XmlReader​ 的简单使用:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader reader = XmlReader.Create("customer.xml", settings);
while (reader.Read())
{
Console.Write(new string(' ', reader.Depth * 2)); // Write indentation
Console.Write(reader.NodeType.ToString()); if (reader.NodeType == XmlNodeType.Element ||
reader.NodeType == XmlNodeType.EndElement)
{
Console.Write(" Name=" + reader.Name);
}
else if (reader.NodeType == XmlNodeType.Text)
{
Console.Write(" Value=" + reader.Value);
}
Console.WriteLine();
}
输出内容如下:
XmlDeclaration
Element Name=customer
Element Name=firstname
Text Value=Jim
EndElement Name=firstname
Element Name=lastname
Text Value=Bo
EndElement Name=lastname
EndElement Name=customer

XmlNodeType​ 包含的成员有:

None Comment Document
XmlDeclaration Entity DocumentType
Element EndEntity DocumentFragment
EndElement EntityReference Notation
Text ProcessingInstruction Whitespace
Attribute CDATA SignificantWhitespace

11.1.2 读取元素

通常我们已知要读取的 XML 的结构,XmlReader​ 为此提供了一系列方法用于验证节点类型、读取节点数据,简化我们的开发。

常用的有:

  • ReadStartElement()​ 方法:用于 验证并读取 当前游标的 NodeType​ 是否为 Element
  • ReadEndElement()​ 方法:用于 验证并读取 当前游标的 NodeType​ 是否为 EndElement

它们需要搭配 Read() ​ 方法使用:

reader.ReadStartElement ("firstname");
Console.WriteLine (reader.Value);
reader.Read();
reader.ReadEndElement();

Notice

如果验证失败,它们会抛出 XmlException​ 异常。该异常具有 LineNumber​ 和 LinePosition​ 属性用于指示失败的具体位置。

  • ReadElementContentAsString()​ 方法:一次性完成上述操作

    该方法有两个重载方法,一个无需参数,直接读取下一个 Element​ 内容;一个需要两个参数,其中第二个参数为 命名空间

    该方法也有类型化版本,如 ReadElementContentAsInt()​,该方法会自动解析为整数。

  • MoveToContent() ​ 方法:用于跳过不必要的 XML 声明、空白节点、注释、处理指令

    当然,我们也可以通过 XmlReaderSettings​ 忽略上述内容

这两个方法的用法如下:

/* 读取的 xml 内容如下
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<customer id="123" status="archived">
<firstname>Jim</firstname>
<lastname>Bo</lastname>
<creditlimit>500.00</creditlimit> <!-- OK, we sneaked this in! -->
</customer>
*/
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader r = XmlReader.Create ("customer.xml", settings);
r.MoveToContent(); // 跳过 XML 声明
r.ReadStartElement("customer");
string firstName = r.ReadElementContentAsString("firstname", "");
string lastName = r.ReadElementContentAsString("lastname", "");
decimal creditLimit = r.ReadElementContentAsDecimal("creditlimit", "");
r.MoveToContent(); // 跳过注释
r.ReadEndElement(); // 读取尾 tag

11.1.2.1 可选元素

在前一个例子中,若希望将 设为可选元素,一个简单的方法是:

r.ReadStartElement ("customer");
string firstName = r. ReadElementContentAsString ("firstname", "");
string lastName = r.Name == "lastname"
? r.ReadElementContentAsString() : null;
decimal creditLimit = r.ReadElementContentAsDecimal ("creditlimit", "");

这里利用了 ReadElementContentAsString()​ 重载方法直接读取下 一个 Element 的特性。

Eureka

从上述方法我们也可以窥探一点:ReadElementContentAsXXX()​ 方法调用后,它的游标已经在下一个元素那里了,相当于已经调用了 Read() ​ 方法,才可以通过 r.Name​ 获知下一个节点的名称。

11.1.2.3 空元素

XML 的空元素有两种形式(如下)

<customerList></customerList>
<customerList/>

以如下代码为例,XmlReader​ 的 ReadEndElement()​ 方法对第 2 种 XML 数据无法正常验证,会抛出异常(这是因为 XmlReader​ 无法找到指定的结束节点):

reader.ReadStartElement ("customerList");
reader.ReadEndElement();

我们可以通过 XmlReader​ 的 IsEmptyElement ​ 属性验证光标处是否为空节点,再决定是否调用 ReadEndElement()​ 方法:

bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement ("customerList");
if (!isEmpty) reader.ReadEndElement();

上述代码很繁琐,更常见的是直接调用 ReadElementContentAsXXX() ​ 方法读取,它可以正确处理这两种空元素。

11.1.2.4 其他 ReadXXX()​ 方法

下表(该表格在 C#12 中移除了不少方法)汇总了 XmlReader​ 中的所有 ReadXXX​ 方法,其中有颜色的部分是该方法的关注点:

方法 支持的 NodeType XML 示例 输入参数 返回的数据 说明
ReadContentAsXXX Text x x 将文本(Text)解析为 XXX 类型,内部使用 XmlConvert​ 类执行转换。
ReadString Text x x 类似于 ReadContentAsString()​,如果元素包含多余一个文本节点,则抛出异常。尽量避免使用该方法。
ReadElementString Element <a>x</a> x 类似于 ReadElementContentAsString()​,如果元素包含多余一个文本节点,则抛出异常。尽量避免使用该方法。
ReadElementContentAsXXX Element <a>x</a> x 包装了 ReadContentAsXXX()​ 方法读取元素节点(Element),而非元素的文本节点(Text)。
ReadInnerXml Element <a>x</a> x 通常用于读取 Element,返回该元素及其后代所有节点。
当它应用在 Attribute 上时,将返回该 Attribute 值。
ReadOuterXml Element <a>x</a> x ReadInnerXml()​ 方法相似,但返回内容包含外部节点。
ReadStartElement Element <a>x
ReadEndElement Element x</a>
ReadSubtree Element <a>x</a> x 返回 XmlReader​ 实例作为代理读取器,该实例金提供了当前节点和其后节点的视图。
读取完毕后该代理需要关闭,关闭后读取器的游标会移动至树尾。
ReadToDescendant Element <a>x</b> "b" 用于在后代节点中移动游标,至(第一个匹配到的)指定节点的起始位置。
ReadToFollowing Element <a>x</b> "b" 用于移动游标至(第一个匹配到的)指定节点的起始位置。(不论深度大小)
ReadToNextSibling Element <a>x</a></b> "b" 用于移动游标至(第一个匹配到的)指定节点的下一个节点起始位置。
ReadAttributeValue Attribute 11.1.3 读取属性(Attribute)

11.1.3 读取属性(Attribute)

XmlReader​ 提供了两种方式获取 Attribute 值,以及一个属性获取 Attribute 数量:

  • GetAttribute() ​ 方法:有三种重载,可以传入 Attribute 名、索引、Attribute 名 + 命名空间,以获取值
  • 索引 器:与 GetAttribute()​ 方法等价,有三种重载
  • AttributeCount ​ 属性:获取当前节点的 Attribute 数目

以如下 XML 片段为例,该代码演示了通过索引器获取 Attribute 值:

<customer id="123" status="archived"/>
Console.WriteLine (reader ["id"]);             // 123
Console.WriteLine (reader [1]); // archived
Console.WriteLine (reader ["bogus"] == null); // True

Notice

XmlReader​ 必须在 起始元素上 才能获得 Attribute 值。若调用了 ReadStartElement()​ 方法,将无法获得这些 Attribute。

11.1.3.1 属性节点

上一节获取 Attribute 的方式适用面并不广:开发者需要知道 XML 的整个结构,知道何时要读取 Attribute。

为了使处理更加简单,Attribute 的遍历不考虑前向规则。XmlReader​ 提供了若干方法用于在 Attribute 间跳转:

  • MoveToAttribute() ​ 方法:可接受的参数和 GetAttribute()​ 方法一样,有三种重载,可以传入 Attribute 名、索引、Attribute 名 + 命名空间,以跳转至指定 Attribute。
  • MoveToFirstAttribute() ​ 方法:跳转至首 Attribute
  • MoveToNextAttribute() ​ 方法:跳转至下一个 Attribute

通过 MoveToAttribute()​ 可以读取指定 Attribute:

reader.MoveToAttribute ("status");
string status = reader.ReadContentAsString(); reader.MoveToAttribute ("id");
int id = reader.ReadContentAsInt();

此外,两个方法则可以遍历每个 Attribute:

if (reader.MoveToFirstAttribute())
do
{
Console.WriteLine (reader.Name + "=" + reader.Value);
}
while (reader.MoveToNextAttribute());

Tips

XmlReader​ 还有一个 MoveToElement()​ 方法,不接受参数,可以在任意位置回到元素起点。

11.1.4 命名空间和前缀

Info

另请参考 10.8 名称(Name)和 命名空间(namespace)

XmlReader​ 提供的方法支持两种方式与 Element/Attribute 交互:

  • Name
  • NamespaceURI + LocalName

ReadStartElement()​ 方法为例,对于第一种方式,则有:

// 可以读取形如
// <customer ...> 和 <customer xmlns='blah' ...> 的数据
reader.ReadStartElement("customer");
// 可以读取形如 <x:customer ...> 的数据
reader.ReadStartElement ("x:customer");

对于第二种方式,则有:

/* 可以读取形如
* <customer xmlns="DefaultNamespace" xmlns:other="OtherNamespace">
* <address>
* <other:city>
* ...
* 的数据
*/
reader.ReadStartElement ("customer", "DefaultNamespace");
reader.ReadStartElement ("address", "DefaultNamespace");
reader.ReadStartElement ("city", "OtherNamespace");

可以看到,第二种方式,其 LocalName 无需 添加前缀。

使用第二种方式时,我们往往需要将前缀单独剥离出来,得到相应的命名空间,再进行数据处理。

11.2 XmlWriter

XmlWriter​ 是一个 XML 流的前向写入器。XmlWriter​ 的设计和 XmlReader ​ 是对称的。它也通过 Create() ​ 方法创建实例,创建时可以传入 XmlWriterSettings ​ 对象,配置写入方式:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; using XmlWriter writer = XmlWriter.Create("foo.xml", settings); writer.WriteStartElement("customer");
writer.WriteElementString("firstname", "Jim");
writer.WriteElementString("lastname", "Bo");
writer.WriteEndElement();
<!--写入的内容如下-->
<?xml version="1.0" encoding="utf-8"?>
<customer>
<firstname>Jim</firstname>
<lastname>Bo</lastname>
</customer>

11.2.0 XmlWriterSettings

XmlWriterSettings​ 有若干属性设置写入 XML 的方式:

  • OmitXmlDeclaration​ 属性:是否忽略写入 XML 的 声明

  • ConfirmanceLevel​ 属性:ConformanceLevel​ 枚举类型,用于指示 所写入的内容是部分节点还是完整文档

    该属性也可以用于忽略写入 XML 声明

11.2.1 写入元素

XmlWriter​ 有以下几个方法用于写入元素:

  • WriteStartElement()​ 方法:用于写入 首节点
  • WriteElementString()​ 方法:用于写入 一个完整的节点
  • WriteEndElement()​ 方法:用于写入 尾节点 (无需传参)

它们的用法如下:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; using XmlWriter writer = XmlWriter.Create("foo.xml", settings); writer.WriteStartElement("customer");
writer.WriteElementString("firstname", "Jim");
writer.WriteElementString("lastname", "Bo");
writer.WriteEndElement();
<!--写入的内容如下-->
<?xml version="1.0" encoding="utf-8"?>
<customer>
<firstname>Jim</firstname>
<lastname>Bo</lastname>
</customer>

此外还有:

  • WriteValue()​ 方法:用于写入 文本(Text) 节点,可接受非字符串类型的参数,内部通过 XmlConvert ​ 类进行转换

  • WriteString()​ 方法:和传入字符串的 WriteValue() ​ 方法等价,也会自动转义非法字符(如 &、<、> 及扩展 Unicode 字符)

    WriteElementString()​ 也会自动转义非法字符。

Warn

向 XML 写入字符串时,务必要使用 XmlConvert ​ 进行数据转化。以 DateTime​ 为例,如下代码得到结果可能是 XML 不兼容的:

writer.WriteElementString("birthdate", DateTime.Now.ToString());

11.2.2 写入属性(Attribute)

起始 节点之后可以立刻写入 Attribute。XmlWriter​ 提供了若干方法用于写入 Attribute:

  • 字符串 类型的值:

  • WriteAttributeString()​ 方法

  • 非字符串 类型的值:

  • WriteStartAttribute()​ 方法

  • WriteValue()​ 方法

  • WriteEndAttribute()​ 方法

下面是字符串类型值的简单应用:

writer.WriteStartElement ("customer");
writer.WriteAttributeString ("id", "1");
writer.WriteAttributeString ("status", "archived");

11.2.3 写入其他类型节点

XmlWriter​ 定义了一系列方法写入各类型节点:

  • WriteBase64()​ 方法:用于写入 二进制 数据
  • WriteBinHex()​ 方法:用于写入 二进制 数据
  • WriteCData()​ 方法
  • WriteComment()​ 方法
  • WriteDocType()​ ​方法
  • WriteEntityRef()​ ​方法
  • WriteProcessingInstruction()​ ​方法
  • WriteRaw()​ 方法:用于将字符串写入到 输出流
  • WriteWhitespace()​ ​方法
  • WriteNode()​ 方法:可以接受 XmlReader ​ 实例,并输出其中所有内容

11.2.4 命名空间和前缀

Write*()​ 方法的重载允许将元素或属性和命名空间关联起来。以如下代码为例,我们将所有元素和 http://oreilly.com 命名空间关联,并为元素添加前缀“o”:

writer.WriteStartElement ("o", "customer", "http://oreilly.com");
writer.WriteElementString ("o", "firstname", "http://oreilly.com", "Jim");
writer.WriteElementString ("o", "lastname", "http://oreilly.com", "Bo");
writer.WriteEndElement();
<?xml version="1.0" encoding="utf-8"?>
<o:customer xmlns:o='http://oreilly.com'>
<o:firstname>Jim</o:firstname>
<o:lastname>Bo</o:lastname>
</o:customer>

如果父元素未添加前缀(子元素默认使用父元素的命名空间),XmlWriter​ 会 自动忽略子元素的命名空间声明 ,使 XML 更为简洁:

writer.WriteStartElement("customer", "http://oreilly.com");
writer.WriteElementString("firstname", "Jim");
writer.WriteElementString("lastname", "http://oreilly.com", "Bo");
writer.WriteEndElement();
<?xml version="1.0" encoding="utf-16"?>
<customer xmlns="http://oreilly.com">
<firstname>Jim</firstname>
<lastname>Bo</lastname>
</customer>

11.3 XmlReader​/XmlWriter​ 的使用模式

假设我们有如下类型,我们想对其进行序列化/反序列化:

public class Contacts
{
public IList<Customer> Customers { get; set; } = new List<Customer>();
public IList<Supplier> Suppliers { get; set; } = new List<Supplier>();
} public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
} public class Supplier
{
public string Name { get; set; }
}
<?xml version="1.0" encoding="utf-8"?>
<contacts>
<customer id="1">
<firstname>Jay</firstname>
<lastname>Dee</lastname>
</customer>
<customer> <!-- we'll assume id is optional -->
<firstname>Kay</firstname>
<lastname>Gee</lastname>
</customer>
<supplier>
<name>X Technologies Ltd</name>
</supplier>
</contacts>

最好的选择是让 每个类各自 实现自己的序列化(ReadXml()​ 方法)/反序列化(WriteXml()​ 方法)逻辑,它有如下好处:

  • ReadXml()​ 和 WriteXml()​ 保证了它们退出时读取器、写入器保持同样的 深度

在实现时要注意一点:

  • ReadXml()​ 读取 层元素;WriteXml()​ 写入 层内容

利用这一点,可以做到:

  • 调用者决定外层元素命名方式;
  • 调用者可以为子元素添加附加的 XML Attribute,例如元素的子类型(在读到该元素后再决定实例化哪种类)
  • 实现和 IXmlSerializable​ 接口的兼容(见 17.8.4 IXmlSerializable 接口

整个实现如下:

public class Contacts
{
public IList<Customer> Customers { get; set; } = new List<Customer>();
public IList<Supplier> Suppliers { get; set; } = new List<Supplier>(); public void ReadXml(XmlReader r)
{
bool isEmpty = r.IsEmptyElement; // This ensures we don't get
r.ReadStartElement(); // snookered by an empty
if (isEmpty) return; // <contacts/> element!
while (r.NodeType == XmlNodeType.Element)
{
if (r.Name == Customer.XmlName) Customers.Add(new Customer(r));
else if (r.Name == Supplier.XmlName) Suppliers.Add(new Supplier(r));
else
throw new XmlException("Unexpected node: " + r.Name);
}
r.ReadEndElement();
} public void WriteXml(XmlWriter w)
{
foreach (Customer c in Customers)
{
w.WriteStartElement(Customer.XmlName);
c.WriteXml(w);
w.WriteEndElement();
}
foreach (Supplier s in Suppliers)
{
w.WriteStartElement(Supplier.XmlName);
s.WriteXml(w);
w.WriteEndElement();
}
}
}
public class Customer
{
public const string XmlName = "customer";
public int? ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; } public Customer() { } public Customer(XmlReader r) { ReadXml(r); } public void ReadXml(XmlReader r)
{
if (r.MoveToAttribute("id")) ID = r.ReadContentAsInt();
r.ReadStartElement();
FirstName = r.ReadElementContentAsString("firstname", "");
LastName = r.ReadElementContentAsString("lastname", "");
r.ReadEndElement();
} public void WriteXml(XmlWriter w)
{
if (ID.HasValue) w.WriteAttributeString("id", "", ID.ToString());
w.WriteElementString("firstname", FirstName);
w.WriteElementString("lastname", LastName);
}
}
public class Supplier
{
public const string XmlName = "supplier";
public string Name { get; set; } public Supplier() { } public Supplier(XmlReader r) { ReadXml(r); } public void ReadXml(XmlReader r)
{
r.ReadStartElement();
Name = r.ReadElementContentAsString("name", "");
r.ReadEndElement();
} public void WriteXml(XmlWriter w) =>
w.WriteElementString("name", Name);
}

11.3.2 混合使用 XmlReader​/XmlWriter​ 和 X-DOM

混合使用二者可以兼顾 X-DOM 的易用性和 XmlReader​/XmlWriter​ 低内存消耗的优点。

11.3.2.1 混合使用 XmlReader​ 和 XElement

混合使用二者时,通常使用 XNode.ReadFrom() ​ 方法读取到当前子树尾,以节省内存。

例如我们有如下 XML 日志文件,它包含一百万个 logentry 元素:

<log>
<logentry id="1">
<date>...</date>
<source>...</source>
...
</logentry>
...
</log>

我们可以这样读取其中的值:

XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true; using XmlReader r = XmlReader.Create ("logfile.xml", settings);
r.ReadStartElement ("log");
while (r.Name == "logentry")
{
XElement logEntry = (XElement) XNode.ReadFrom (r);
int id = (int) logEntry.Attribute ("id");
DateTime date = (DateTime) logEntry.Element ("date");
string source = (string) logEntry.Element ("source");
...
}
r.ReadEndElement();

相应的,上一节的 Customer​ 代码可以改造成这样:

public void ReadXml (XmlReader r)
{
XElement x = (XElement) XNode.ReadFrom (r);
ID = (int) x.Attribute ("id");
FirstName = (string) x.Element ("firstname");
LastName = (string) x.Element ("lastname");
}

此外,XElement​ 和 XmlReader​ 共同保证命名空间会正确得到保留且前缀会恰当展开,即使它们的定义在当前级别之外。以如下 XML 为例,在 logentry 一级构建的 XElement​ 元素 也会正确 继承外部的命名空间:

<log xmlns="http://loggingspace">
<logentry id="1">
...

11.3.2.2 混合使用 XmlWriter​ 和 XElement

XElement​ 支持将 层元素写入 XmlWriter​。以下代码使用 XElement​ 将一百万个 logentry 元素输出到 XML 文件中,并且不会将所有内容保存在 内存 中:

using XmlWriter w = XmlWriter.Create("logfile.xml");

w.WriteStartElement("log");
for (int i = 0; i < 1000000; i++)
{
XElement e = new XElement ("logentry",
new XAttribute("id", i),
new XElement("date", DateTime.Today.AddDays(-1)),
new XElement("source", "test"));
e.WriteTo(w);
}
w.WriteEndElement();

11.4 XSD 和大纲的验证

特定的 XML 文档适用于特定的领域,这种不兼容演化成了若干标准,每个领域的 XML 要遵守相应标准。这些标准描述了 XML 大纲的模式,用于将 XML 文档的解释、验证 标准化自动化 。其中接受程度最高的标准是 XSD(XML Schema Definition,XML 大纲定义)。System.Xml 也支持在此之前的 DTD 和 XDR 标准。

下面两段分别是 XML 文档和对应的 XSD 文档:

<?xml version="1.0"?>
<customers>
<customer id="1" status="active">
<firstname>Jim</firstname>
<lastname>Bo</lastname>
</customer>
<customer id="1" status="archived">
<firstname>Thomas</firstname>
<lastname>Jefferson</lastname>
</customer>
</customers>
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="customers">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="customer">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:int" use="required"/>
<xs:attribute name="status" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

可以看到,XSD 本身是 XML 文档添加了若干附加信息。

11.4.1 大纲验证

在读取/处理信息之前验证大纲有如下几点好处:

  • 可以避免编写大量的错误检查或异常处理语句。
  • 大纲验证可以查出意想不到的错误。
  • 错误的信息更加详细有效。

进行验证并不复杂,直接使用 XmlReader​、XmlDocument​ 或 X-DOM 读取 XML 即可,在读取过程中会自动进行大纲验证。

11.4.1.1 使用 XmlReader​ 验证大纲

使用 XmlReader​ 验证大纲只需正常调用 Read() 方法,如果大纲验证在某一个位置失败了,会抛出 XmlSchemaValidationException ​ 异常:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add (null, "customers.xsd");
using (XmlReader r = XmlReader.Create("customers.xml",settings))

如果大纲是内联的,则应当设置如下标志:

settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;

什么是内联?

内联 XSD(XML Schema Definition)是指在 XML 文档中直接包含 XML Schema 定义的做法。这种做法允许 XML 文档在不依赖外部 Schema 文件的情况下,提供数据的结构和类型定义。内联 XSD 通常用于以下场景:

  1. 自包含的文档:当需要一个自包含的 XML 文档,即所有必要的信息都包含在单个文件中时,内联 XSD 非常有用。
  2. 简化分发和部署:内联 XSD 可以简化 XML 文档的分发和部署,因为不需要额外的 Schema 文件。
  3. 即时验证:内联 XSD 允许 XML 处理器在解析文档时立即验证数据结构,而不需要先下载或访问外部 Schema。
  4. 特定于应用的 Schema:对于特定应用或一次性使用的 XML 文档,内联 XSD 可以提供快速且方便的解决方案。

在技术实现上,内联 XSD 通过在 XML 文档中直接包含 <xsd:schema> 元素来实现,该元素定义了 XML 文档的结构和数据类型。例如,一个内联 XSD 可能看起来像这样:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="root">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="child" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="#schema">
<child>Example</child>
</root>

在这个例子中,<xsd:schema> 定义了一个名为 root 的元素,它包含一个名为 child 的子元素,该子元素的类型为 xsd:string。然后,XML 文档部分引用了这个内联 Schema,使用 xsi:noNamespaceSchemaLocation 属性指向内联 Schema 的位置。

内联 XSD 是 XML Schema 技术的一个强大特性,它提供了一种灵活且高效的方式来定义和验证 XML 文档的结构。

如果只想验证文档,也可以按照如下方式进行:

using (XmlReader r = XmlReader.Create("customers.xml", settings))
try
{
while(r.Read());
}
catch (XmlschemaValidationException ex)
{
...
}
XmlSchemaValidaionException

XmlSchemaValidaionException​ 异常主要的成员有:

  • Message​ 属性: 异常 信息
  • LineNumber​ 属性:发生异常的 行数
  • LinePosition​ 属性:发生异常的 列数
获取全部大纲异常

在上述例子中,遇到第一个大纲错误就会抛出异常,无法一次性获得文档中所有错误。若想得到文档中的全部错误,可以通过 XmlReaderSettings ​ 的 ValidationEventHandler​ 事件添加错误处理程序:

一旦处理了该事件,在大纲出现错误时就不会 再抛出异常 ,而是会 触发事件处理器

用法如下:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add (null, "customers.xsd");
settings.ValidationEventHandler += ValidationHandler;
using XmlReader r = XmlReader.Create("customers.xml",settings);
while (r.Read()) ; static void ValidationHandler (object sender, ValidationEventArgs e)
{
Console.WriteLine ("Error:"+ e.Exception.Message);
}

其中 ValidationEventArgs​ 的 Exception​ 属性包含了 XmlSchemaValidationException​ 的信息。

11.4.1.2 验证 X-DOM

X-DOM 验证 XML 大纲有两种方式:

  • 借助 XmlReader ​ 进行加载、验证

  • 通过 System.Xml.Schema​ 类中的扩展方法进行验证

    适用于 XDocument​ 和 XElement​ 都已加载到内存中的情况

两种方式的示例代码如下:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add (null,"customers.xsd");
XDocument doc;
using (XmlReader r = XmlReader.Create("customers.xml", settings))
try { doc = XDocument.Load(r); }
catch (XmlSchemaValidationException ex) { ... }
XDocument doc =XDocument.Load(@"customers.xml");
XmlSchemaSet set = new XmlSchemaSet();
set.Add (null, @"customers.xsd");
StringBuilder errors = new StringBuilder ();
doc.Validate(set, (sender,args) => { errors.AppendLine
(args.Exception.Message); }
);
Console.WriteLine(errors.ToString());

11.5 XSLT

XSLT(Extensible Stylesheet Language Transformations,扩展样式表转换语言)是一种 XML 语言。它描述了如何将一种 XML 语言 转换为另外一种语言 。比较典型例子是将描述数据的 XML 文档转换为(描述格式的)XHTML 文档。

Eureka

即通过 XML 定义了一种转化规则,按照这个规则可将 XML A 文档转化为对应的 XML B 文档。

以如下 XSLT 文档为例:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xs1="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:templatematch="/">
<html>
<p><xsl:value-of select="//firstname"/></p>
<p><xsl:value-of select="//lastname"/></p>
</html>
</xsl:template>
</xsl:stylesheet>

通过上述规则,可将如下左侧内容转化为右侧内容:

<customer>
<firstname>Jim</firstname>
<lastname>Bo</lastname>
</customer>
<html>
<p>Jim</p>
<p>Bo</p>
</html>

System.Xml.Xsl.XslCompiledTransform​ 类可以高效地进行 XSLT 转换,它舍弃了过时的 XmlTransform ​类。XslCompiledTransform ​的用法非常简单:

XslCompiledTransform transform = new XslCompiledTransform();
transform.Load("test.xslt");
transform.Transform("input.xml", "output.xml");

通常,更倾向于使用接受 XmlWriter​ 的 Transform​ 重载,而非输出至文件,以便更容易地控制内容格式。

第11章 其他 XML 技术的更多相关文章

  1. 高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群

    高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群 libnet软件包<-依赖-heartbeat(包含ldirectord插件(需要perl-MailTools的rpm包)) l ...

  2. Linux就这个范儿 第11章 独霸网络的蜘蛛神功

    Linux就这个范儿 第11章  独霸网络的蜘蛛神功  第11章 应用层 (Application):网络服务与最终用户的一个接口.协议有:HTTP FTP TFTP SMTP SNMP DNS表示层 ...

  3. 第11章 享元模式(Flyweight Pattern)

    原文 第11章 享元模式(Flyweight Pattern) 概述:   面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题.但是在某些情况下,对象的数量可能会太多,从而导致了运行时 ...

  4. Java核心技术卷一基础知识-第11章-异常、断言、日志和调试-读书笔记

    第11章 异常.断言.日志和调试 本章内容: * 处理错误 * 捕获异常 * 使用异常机制的技巧 * 使用断言 * 日志 * 调试技巧 * GUI程序排错技巧 * 使用调试器 11.1 处理错误 如果 ...

  5. 《C#从现象到本质》读书笔记(九)第11章C#的数据结构

    <C#从现象到本质>读书笔记(九)第11章C#的数据结构 C#中的数据结构可以分为两类:非泛型数据结构和泛型数据结构. 通常迭代器接口需要实现的方法有:1)hasNext,是否还有下一个元 ...

  6. MySQL性能调优与架构设计——第11章 常用存储引擎优化

    第11章 常用存储引擎优化 前言: MySQL 提供的非常丰富的存储引擎种类供大家选择,有多种选择固然是好事,但是需要我们理解掌握的知识也会增加很多.每一种存储引擎都有各自的特长,也都存在一定的短处. ...

  7. Java Persistence with MyBatis 3(中文版) 第三章 使用XML配置SQL映射器

    关系型数据库和SQL是经受时间考验和验证的数据存储机制.和其他的ORM 框架如Hibernate不同,MyBatis鼓励开发者可以直接使用数据库,而不是将其对开发者隐藏,因为这样可以充分发挥数据库服务 ...

  8. 20191105 《Spring5高级编程》笔记-第11章

    第11章 任务调度 任务调度由三部分组成: 任务:需要在特定时间运行或定期运行的业务逻辑块: 触发器:指定任务应该执行的条件: 调度程序:根据来自触发器的信息执行任务: 11.2 Spring中的任务 ...

  9. 深入刨析tomcat 之---第8篇 how tomcat works 第11章 11.9应用程序,自定义Filter,及注册

    writed by 张艳涛, 标签:全网独一份, 自定义一个Filter 起因:在学习深入刨析tomcat的学习中,第11章,说了调用过滤链的原理,但没有给出实例来,自己经过分析,给出来了一个Filt ...

  10. XML技术的应用

    XML技术的发展历史:gml--->sml--->html--->xml(可扩展标记语言). HTML和XML技术的区别: 1.HTML技术的标签不能自己定义,必须使用规定语法编写: ...

随机推荐

  1. 【一步步开发AI运动小程序】八、利用body-calc进行姿态识别

    随着人工智能技术的不断发展,阿里体育等IT大厂,推出的"乐动力"."天天跳绳"AI运动APP,让云上运动会.线上运动会.健身打卡.AI体育指导等概念空前火热.那 ...

  2. JVM最简生存指南

    本文由 ImportNew - Grey 翻译自 hadihariri.欢迎加入Java小组.转载请参见文章末尾的要求. 最近更新 : 2014年1月9日 为什么要写这个指南 持续更新 目标人群 基础 ...

  3. 深入解析Apache Mina源码(1)——Mina的过滤器机制实现

    1.深入解析Apache Mina源码(1)--Mina的过滤器机制实现 2.深入解析Apache Mina源码(2)--Mina的事件模型 3.深入解析Apache Mina源码(3)--Mina的 ...

  4. python开发包之远程隧道链接sshtunnel

    缘起: 公司很多的数据库的链接都是本地连接或者指定ip地址可以访问, 如果你没有该ip权限, 但是你可以登录该数据库所在的服务器, 这个时候就可以使用ssh链接上这个服务器,以此为跳板进行数据库的链接 ...

  5. 理解Flink之一编译Flink-1.11.1

    下载源码 git clone -b release-1.11.1 https://github.com/apache/flink.git --depth=1 flink-1.11.1 编译 mvn c ...

  6. git 推送代码到多个 远端仓库

    业务场景 在开发代码时,有时希望将代码推送到两个远端仓库. 实现方法 git remote add origin giturl1 git remote add backup giturl2 git p ...

  7. springgateway 路由转发

    有些情况下,我们希望不直接访问后端地址,这个时候可以通过springgateway网关进行处理.下面只是一个简单的例子. 至于URL,变化,我们可以通过编写程序逻辑来实现. 实现步骤: 1.新建项目 ...

  8. Redis原理—4.核心原理摘要

    大纲 1.Redis服务器的Socket网络连接建立 2.Redis多路复用监听与文件事件模型 3.基于队列串行化的文件事件处理机制 4.完整的Redis Server网络通信流程 5.Redis串行 ...

  9. 《JavaScript 模式》读书笔记(7)— 设计模式3

    这一篇,我们学习本篇中最为复杂的三个设计模式,代理模式.中介者模式以及观察者模式.这三个模式很重要!! 七.代理模式 在代理设计模式中,一个对象充当另一个对象的接口.它与外观模式的区别之处在于,外观模 ...

  10. VS C++ 出现debug assertion failed弹框,怎么定位代码

    当VS C++ 出现debug assertion failed弹框时,想定位代码,但是按弹框出现的3个按钮都不能定位代码,这个时候,你需要打开VS界面,暂停调试,然后打开函数调用栈,找到最后执行的函 ...