首页 > 其他分享 >第11章 其他 XML 技术

第11章 其他 XML 技术

时间:2025-01-01 18:09:33浏览次数:1  
标签:11 XML Attribute 技术 XmlReader reader 方法 public

第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,Attribute,技术,XmlReader,reader,方法,public
From: https://www.cnblogs.com/hihaojie/p/18646134/chapter-11-other-xml-technology-1ikm9j

相关文章

  • 【空间光-光纤耦合技术01】高斯光束、基模高斯光束、厄米-高斯光束、拉盖尔-高斯光束
    本部分的学习参考柯熙政老师的《无限光通信中的空间光——光纤耦合技术》,为自学笔记,博客末尾附上了在学习过程中参考的博客内容。        高斯光束在光纤通信中具有广泛的应用。在光束耦合方面,高斯光束的光强分布特性使其易于与光纤进行模式匹配,从而实现高效率的光束......
  • 人工智能短视频内容理解与生成技术在美团的创新实践15
     1.背景美团围绕丰富的本地生活服务电商场景,积累了丰富的视频数据。美团场景下的短视频示例上面展示了美团业务场景下的一个菜品评论示例。可以看到,视频相较于文本和图像可以提供更加丰富的信息,创意菜“冰与火之歌”中火焰与巧克力和冰淇淋的动态交互,通过短视频形式进......
  • 2025展望:基于Harry技术的发展视角
    后端开发Gitee仓库地址:https://gitee.com/harry-tech/harry.gitidea、eclipse需安装lombok插件,不然会提示找不到entity的getset方法创建数据库harry3,数据库编码为UTF-8执行db/harry3.sql文件,初始化数据修改application.yml,更新MySQL账号和密码Eclipse、IDEA运行HarryA......
  • 洛谷 P11487 「Cfz Round 5」Gnirts 10——题解
    洛谷P11487「CfzRound5」Gnirts10传送锚点摸鱼环节「CfzRound5」Gnirts10题目背景Englishstatement.YoumustsubmityourcodeattheChineseversionofthestatement.InMemoryof\(\text{F}\rule{66.8px}{6.8px}\).题目描述题面还是简单一点好。给定......
  • 【Java教程】Day11-07 时间与日期:日期与时间API的转换与数据库存储
    Java提供了两个日期与时间处理API:旧的 java.util.Date 和 java.util.Calendar,以及新的 java.time 包。新的API以 Instant、LocalDateTime 等为核心,具有更清晰的设计和更强大的功能。除非你需要与遗留代码进行交互,否则建议使用新的API。在需要将新旧API进行转换时,......
  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现11
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • C++11新增关键字之final和override
    C++11中新增了两个关键字final和override,我们分别来学习这两个关键字的使用。一,final   1,final用于修饰类,表示此类不想被其他的类继承。   2, final用于修饰虚函数,表示该虚函数不想被重写。     测试代码:   1,用于修饰类#include<iostream>......
  • 洛谷 P1102 A-B 数对
    题目:P1102A-B数对-洛谷|计算机科学教育新生态题目背景出题是一件痛苦的事情!相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的A+BProblem,改用A-B了哈哈!题目描述给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C的数对的个数(不同位置的......
  • 人工智能伦理与公平性:确保AI技术服务于全人类
    随着人工智能(AI)技术的飞速发展与广泛应用,AI不仅在各行各业中带来了巨大的变革,也带来了前所未有的伦理和公平性挑战。AI的决策不仅影响着我们日常生活的方方面面,还可能对社会、文化、经济等领域产生深远影响。如何确保AI技术的公平性、可解释性与隐私保护,已经成为全球范围内亟......
  • 人工智能芯片与硬件加速:提升AI性能的关键技术
    随着人工智能(AI)技术的迅速发展,AI模型的计算需求呈现爆炸式增长。尤其是在深度学习等复杂任务中,传统的通用处理器(CPU)已经无法满足高效计算的需求。因此,硬件加速成为了提高AI性能和处理速度的关键技术之一。不同类型的AI加速硬件,包括GPU(图形处理单元)、TPU(TensorProcessingUnit......