第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 通常用于以下场景:
- 自包含的文档:当需要一个自包含的 XML 文档,即所有必要的信息都包含在单个文件中时,内联 XSD 非常有用。
- 简化分发和部署:内联 XSD 可以简化 XML 文档的分发和部署,因为不需要额外的 Schema 文件。
- 即时验证:内联 XSD 允许 XML 处理器在解析文档时立即验证数据结构,而不需要先下载或访问外部 Schema。
- 特定于应用的 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