第10章 LINQ to XML
第10章 LINQ to XML
10.1 架构概述——DOM 和 LINQ to XML 的 DOM
XML 文档可以用一棵对象树完整的表示,这称为“文档对象模型(document object model)”
LINQ to XML 由两部分组成:
- XML DOM,简称为 X-DOM
- 大约 10 个查询运算符
LINQ 也可以用于查询 W3C 标准的旧 DOM,不过 X-DOM 对 LINQ 查询更为友好:
- X-DOM 部分方法可以返回
IEnumerable 序列; - X-DOM 的构造器支持通过 LINQ 构建对象树。
Tips
W3C 标准的 DOM 对应 C# 中的
XmlDocument;X-DOM 对应 C# 中的XDocument 等一系列类型。更多内容见12 System.Xml 的使用
10.2 X-DOM 概览
XObject 是所有类型的基类,XElement 和 XDocument 是所有容器类型的基类
以如下代码为例,它对应的 X-DOM 如图:
string xml = @"
<customer id='123' status='archived'>
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</customer>
";
XElement customer = XElement.Parse(xml);
XObject
XObject 为抽象类,是所有 XML 内容(XML Content)的 基 类,它内含一个指向 父 元素( Parent element)的属性、一个指向 XDocument 的(可选)属性。
XNode
XNode 为抽象类,是大多数 XML 内容的基类(不含 XAttribute )。XNode 指向 父 元素(Element),不会指向 子 节点(Node)。
XContainer
我们在 XNode 提到,XNode 不会指向 子 节点。指向 子 节点的工作由它的派生类 XContainer 完成。
XContainer 为抽象类,用于处理子项。它也是 XElement 和 XDocument 的基类。
XElement
XElement 引入了诸如 Name、Value 等成员,用于管理特性。多数 XElement 仅包含一个 XText 节点,Value 用于快捷地 get、set 其内容。
XDocument
XDocument 封装了根节点的 XElement ,添加了 XDeclaration 、一系列处理指令及其他根元素+功能。
与 W3C DOM 不同,XDocument 是可选的,因此我们可以高效移动任意子节点至其他 X-DOM 中。
10.2.1 加载和解析
XElement 和 XDocument 提供了静态 Load 和 Parse 方法,用于从现有源建立 X-DOM 树。支持的源有:
Load 方法:通过文件建立 X-DOM:URI、Stream、
TextReader、XmlReaderXDocument fromWeb = XDocument.Load ("http://albahari.com/sample.xml");
XElement fromFile = XElement.Load (@"e:\media\somefile.xml");
XElement config = XElement.Parse (@"
<configuration>
<client enabled='true'>
<timeout>30</timeout>
</client>
</configuration>");
Parse 方法:通过字符串建立 X-DOM
Tips
XNode 也提供了一个静态方法 ReadFrom ,从 XmlReader 中实例化+填充任意类型的节点(node)。与Load 不同,它每次仅读取一个完整节点,因此我们可以用它进行手动读取。
10.2.2 保存和序列化
任何 node 实例都可以通过其 ToString 方法输出 XML 格式的字符串,通过 WriteTo 方法将数据写入 XmlWriter 中。
Tips
通过
ToString 获得的字符串包含缩进、换行等格式化内容,可以通过传入SaveOptions.DisableFormatting 参数关闭该特性。注意:若原始 XML 内容包含格式化内容,即使传入
SaveOptions.DisableFormatting 参数,仍会保持原缩进样式。
XElement 和 XDocument 提供了 Save 方法将 X-DOM 保存至 URI、Stream、TextWriter、XmlWriter 中。该方法会自动添加 XML 声明(见10.7.2 XML 声明(declaration))。
10.3 实例化 X-DOM
10.3.0 构造器 + Add 方法
任意 XContainer 的子类都可以使用构造器 + Add 方法创建 X-DOM 树,方法如下:
XElement lastName = new XElement ("lastname", "Bloggs");
lastName.Add (new XComment ("nice name"));
XElement customer = new XElement ("customer");
customer.Add (new XAttribute ("id", 123));
customer.Add (new XElement ("firstname", "Joe"));
customer.Add (lastName);
customer.Dump();
<customer id="123">
<firstname />
<lastname>Bloggs<!--nice name--></lastname>
</customer>
其中 Name 参数必选, Value 参数可选(可以在创建完成后再设置 Value 值)。Value 对应 XText 节点,它会被隐式创建。
10.3.1 函数式构建(Functional Construction)
X-DOM 支持“函数式”构建(源自函数式编程 functional programming),用法如下:
new XElement ("customer", new XAttribute ("id", 123),
new XElement ("firstname", "joe"),
new XElement ("lastname", "bloggs",
new XComment ("nice name")
)
)
Eureka
XElement 利用了params 关键字实现该效果:public XElement(XName name, params object?[] content)
优点有2:
和 XML 自身结构相似;
它可以使用 LINQ 的
select 语句。以如下代码为例,其中
Customers 为 EF Core 实例。new XElement ("customers",
from c in Customers.AsEnumerable()
select new XElement ("customer",
new XAttribute ("id", c.ID),
new XElement ("name", c.Name,
new XComment ("nice name")
)
)
)
10.3.2 指定内容(Specifying Content)
实际上,10.3.1 函数式构建(Functional Construction)利用了 C# 中的可选参数数组,XElement 的构造器和 XContainer 的 Add 方法定义如下:
public XElement(XName name, params object?[] content)
public void Add (params object[] content)
XContainer 将可选参数数组的所有对象都转为了 Node 或 Attribute ,其处理逻辑如下:
- 忽略 null 对象;
-
XNode、XStreamingElement 对象,添加至 Node 集合中; -
XAttribute 对象,添加至 Attribute 集合中; -
string 对象,包装成 XText 节点,添加至 Node 集合中; -
IEnumerable 对象,遍历所有内容,按照 1~4 步处理; - 其他:将对象转化为
string ,按照步骤 4 处理。
Tips
object 包含ToString 方法,所有object 都可以转化为XText 节点,因此不存在无效对象。此外,
XContainer 在调用ToString 前会检查对象是否是如下类型,是则调用XmlCovert,保证序列化不受CultureInfo 影响,符合 XML 格式规则:
float、double、decimal、bool、DateTime、DateTimeOffset、TimeSpan
10.3.3 自动深度克隆(Automatic Deep Cloning)
如XObject中提到,所有元素都包含 Parent Element 指针。当实例已有 Parent,将其赋值给其他 XContainer 时,将自动进行深克隆:
var address =
new XElement ("address",
new XElement ("street", "Lawley St"),
new XElement ("town", "North Beach")
);
var customer1 = new XElement ("customer1", address);
var customer2 = new XElement ("customer2", address);
customer1.Element ("address").Element ("street").Value = "Another St";
customer2.Element ("address").Element ("street").Value.Dump(); // 输出 Lawley St
Extra
因 X-DOM 深拷贝的特性,它的实例化没有任何副作用,这也是“函数式编程”的特点。
10.4 导航和查询(Navigating and Querying)
XNode 和 XContainer 定义了方法和属性用于游历 X-DOM 树。与常规 DOM 不同,这些函数返回单个值或 IEnumerable<T> 对象,而非 IList<T>,因此需要通过 LINQ 进行查询。
Warn
在 X-DOM 中,Element 和 Attribute 的 name 是大小写敏感的,与 XML 一致。
10.4.1 导航至子节点
子节点
单一子节点
FirstNode
LastNode
Element
单层子节点
Nodes
Elements
深层子节点
Decendants
DecendantNodes
Tips
带 * 的方法可以在序列(sequences)上使用(由 LINQ 支持)。
Info
本节用到的 XML 内容均为:
<bench>
<toolbox>
<handtool>Hammer</handtool>
<handtool>Rasp</handtool>
</toolbox>
<toolbox>
<handtool>Saw</handtool>
<powertool>Nailgun</powertool>
</toolbox>
<!--Be careful with the nailgun-->
</bench>
10.4.1.1 FirstNode()、LastNode() 和 Nodes()
这三个方法(属性)用于操作 直接 子节点,Nodes() 返回 所有直接子节点序列(sequences )。以如下代码为例:
var bench =
new XElement ("bench",
new XElement ("toolbox",
new XElement ("handtool", "Hammer"),
new XElement ("handtool", "Rasp")
),
new XElement ("toolbox",
new XElement ("handtool", "Saw"),
new XElement ("powertool", "Nailgun")
),
new XComment ("Be careful with the nailgun")
);
bench.FirstNode.ToString(SaveOptions.DisableFormatting).Dump ("FirstNode");
bench.LastNode.ToString(SaveOptions.DisableFormatting).Dump ("LastNode");
foreach (XNode node in bench.Nodes())
Console.WriteLine (node.ToString (SaveOptions.DisableFormatting) + ".");
FirstNode:
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>
LastNode:
<!--Be careful with the nailgun-->
Nodes():
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>.
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>.
<!--Be careful with the nailgun-->.
Tips
FirstNode 和LastNode 的返回值类型为 XNode ,Nodes 的返回值类型为 IEnumerable<XNode> 。
10.4.1.2 检索 elements
Elements() 方法返回 XElement 类型的单层子节点:
foreach (XNode node in bench.Elements("handtool"))
Console.WriteLine(node.ToString(SaveOptions.DisableFormatting) + ".");
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>.
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>.
Elements() 可以返回指定名称的元素:
int toolboxCount = bench.Elements ("toolbox").Count();
Summary
从上面两个例子可以看出,
Nodes() 与Elements() 的区别:
-
Nodes() 不 支持寻找指定元素;-
Elements() 只列出 XElement 成员。
Elements() 与 LINQ
<bench>
<toolbox>
<handtool>Hammer</handtool>
<handtool>Rasp</handtool>
</toolbox>
<toolbox>
<handtool>Saw</handtool>
<powertool>Nailgun</powertool>
</toolbox>
<!--Be careful with the nailgun-->
</bench>
如下代码查询含有 Nailgun 的 toolBox:
var toolboxWithNailgun =
from toolbox in bench.Elements()
where toolbox.Elements().Any (tool => tool.Value == "Nailgun")
select toolbox.Value;
如下代码查询所有 handtool :
var handTools =
from toolbox in bench.Elements()
from tool in toolbox.Elements()
where tool.Name == "handtool"
select tool.Value;
如下代码返回指定名称的元素:
var count = bench.Elements().Where(e => e.Name == "toolbox").Count();
等价于:
int toolboxCount = bench.Elements ("toolbox").Count();
Elements() 与 IEnumerable<T> where T : XContainer
XContainer.Elements() 方法的 LINQ 查询与 XContainer.Nodes() 方法的 LINQ 查询等价,之前的示例还可以写为:
from toolbox in bench.Nodes().ofType<XElement>()
where ...
但是 XContainer 有额外的扩展方法,XElement 作为它的子类同样可以用它处理元素序列。使用方式形下:
var handTools2 =
from tool in bench.Elements ("toolbox").Elements ("handtool")
select tool.Value.ToUpper();
上述查询,第一次调用的 Elements 方法绑定的是 XContainer 的实例方法,而第二次 Elements 方法则绑定到了扩展方法上。
Eureka
Nodes 方法的返回值类型是IEnumerable<XNode>,Elements 方法的返回值是IEnumerable<XElements>,而 LINQ 的Elements 方法不支持IEnumerable<XNode>,因此无法对Nodes 使用Elements 方法。
10.4.1.3 检索单个元素(element)
Element() 方法等价于 LINQ 中的 FirstOrDefault ,返回单层子节点匹配到的第一个元素,若元素不存在,返回 null。
Tips
Element("xyz").Value 调用在 xyz 元素不存在时将 抛出 NullReferenceException 。XElement 为string 类型定义了显式转换,可以通过强制类型避免此异常。即:string xyz = (string)settings.Element ("xyz");
当然,我们也可以使用
?.
10.4.1.4 获取子元素:Descendants 和 DescendantNodes
XContainer 提供了 Descendants 方法和 DescendantNodes 方法,用于访问全部子元素(Element)或全部子节点(Node)(以至整棵树)。
Descendants 方法可以接收一个元素名称,返回所有子元素 (XElement 对象)。
DescendantNodes 方法不接收参数,返回所有类型的子节点(包括 XText)。
以如下代码为例,输出内容如下:
/*输出 XElement 元素
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>
<handtool>Hammer</handtool>
<handtool>Rasp</handtool>
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>
<handtool>Saw</handtool>
<powertool>Nailgun</powertool>
*/
foreach (var node in bench.Descendants())
Console.WriteLine(node.ToString(SaveOptions.DisableFormatting));
/* 输出全部节点
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>
<handtool>Hammer</handtool>
Hammer
<handtool>Rasp</handtool>
Rasp
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>
<handtool>Saw</handtool>
Saw
<powertool>Nailgun</powertool>
Nailgun
<!--Be careful with the nailgun-->
*/
foreach (XNode node in bench.DescendantNodes())
Console.WriteLine (node.ToString (SaveOptions.DisableFormatting));
10.4.2 导航至父节点
XNode 及其子类(XDocument 除外)可以使用 AncestorXXX 方法导航至父节点,父节点的类型必然是 XElement 。
Ancestors 返回一个序列,第一个元素是 Parent,第二个元素是 Parent.Parent ,直至根元素。
Tips
XDocument 不是任何节点的父节点,但是任何XObject 都可以通过 Document 属性访问XDocument。
Tips
可以使用 LINQ 查询根元素:
var root = bench.AncestorsAndSelf().Last();
上述代码不使用
Ancestors 方法,是因为bench 本身可能就是根节点。
如果存在
XDocument,也可以通过 XObject.Document.Root 属性获取根节点。
10.4.3 导航至同级节点
可以像链表一样使用 PreviousNode 和 NextNode 属性遍历节点。
Extra
事实上,节点在内部确实是以(单)链表的方式存储,因此
PreviousNode 属性的效率较低。
10.4.4 导航至节点的 Attribute
Attribute 方法接受 name 参数,返回 0~1 个元素的序列(一个 XML 元素不能包含同名 Attribute)。
Tips
上述是
XElement 中的方法,XAttribute 类型还提供了Parent 属性、PreviousAttribute 和NextAttribute 属性。
10.5 更新 X-DOM
更新子节点
XNode
AddBeforeSelf
AddAfterSelf
Remove
ReplaceWith
XContainer
Add
AddFirst
RemoveNodes
ReplaceNodes
XElement
RemoveAttributes
RemoveAll
ReplaceAttributes
ReplaceAll
Value 属性
SetValue
SetElementValue
SetAttributeValue
XAttribute
Value 属性
SetValue
Remove
LINQ
Elements.Remove
Decendents.Remove
10.5.1 简单的值更新
SetValue 方法和 Value 属性用于替换/设置 Element 或 Attribute 的当前值。SetValue 方法接受 object 类型的数据,Value 属性仅接受 string 类型的数据。
二者赋值时,新值将替换所有子节点。
Tips
SetValue 方法内部实际调用的也是Value 属性。
10.5.2 更新子节点(Node)和 Attribute
上述方法都用于更新当前节点。
10.5.2.1 SetElementValue 方法和 SetAttributeValue 方法
这两个方法将自动实例化 XElement/XAttribute 对象,并作为 子 元素添加至 当前 元素中,若有同名 Element/Attribute 则进行覆盖:
XElement settings = new XElement ("settings");
settings.SetElementValue ("timeout", 30);
settings.SetElementValue ("timeout", 60);
<settings>
<timeout>30</timeout>
</settings>
<settings>
<timeout>60</timeout>
</settings>
10.5.2.2 Add 方法和 AddFirst 方法
Add 方法向内部节点的队尾插入节点; AddFirst 向内部节点的排头插入节点。
Tips
Add 方法定义在XContainer 中,AddAfterSelf 定义在XNode 中。
10.5.2.3 RemoveNodes 方法、 RemoveAttributes 方法和 RemoveAll 方法
RemoveNodes 方法用于移除持有的全部节点, RemoveAttributes 方法用于移除持有的全部 Attribute。 RemoveAll 可以一次性将二者全部移除。
10.5.2.4 ReplaceXXX 方法
等价于 RemoveXXX 方法 + Add 方法。
10.5.3 通过父节点更新子节点
上述方法操作的是当前节点的父节点(Parent),因此父节点不能为 null 。
AddBeforeSelf 方法和 AddAfterSelf 方法
用于在当前节点的前、后插入其他节点。
Remove 方法
用于在父节点中移除当前节点。
ReplaceWith 方法
用于在父节点中替换当前节点
10.5.3.1 移除节点或属性序列(LINQ)
System.Xml.Linq 提供了一系列扩展方法用于从父节点移除元素。后续代码对应的 XML 如下:
<contacts>
<customer name="Mary" />
<customer name="Chris" archived="true" />
<supplier name="Susan">
<phone archived="true">012345678<!--confidential--></phone>
</supplier>
</contacts>
Elements().Remove()
从10.4.1.2 检索 elements可知,Elements 方法返回的是单层子节点,因此如下代码只会移除当前层的子节点:
contacts.Elements()
.Where (e => (bool?) e.Attribute ("archived") == true)
.Remove();
<contacts>
<customer name="Mary" />
<supplier name="Susan">
<phone archived="true">012345678<!--confidential--></phone>
</supplier>
</contacts>
Descendants().Remove()
从10.4.1.4 获取子元素:Descendants 和 DescendantNodes可知,Descendants 方法返回所有层次的子节点,因此如下代码会移除任何匹配到的子节点:
contacts.Descendants()
.Where (e => (bool?) e.Attribute ("archived") == true)
.Remove();
<contacts>
<customer name="Mary" />
<supplier name="Susan" />
</contacts>
综合使用
以下代码移除了注释为“confidential”的联系人:
contacts.Elements()
.Where (
e => e.DescendantNodes().OfType<XComment>().Any (c => c.Value == "confidential")
)
.Remove();
<contacts>
<customer name="Mary" />
<customer name="Chris" archived="true" />
</contacts>
10.6 使用 Value
10.6.1 设置 Value
如10.5.1 简单的值更新所述:
SetValue 方法和Value 属性用于替换/设置 Element 或 Attribute 的当前值。SetValue 方法接受 object 类型的数据,Value 属性仅接受 string 类型的数据。
Warn
通过
Value 设置值时,DataTime 要使用 XmlConvert 转化数据。
SetValue 和XElement/XAttribute 的构造器会自动调用 XmlConvert 对数据格式化,保证了数据格式的正确性。
10.6.2 获得 Value
XElement/XAttribute 内部定义了诸多显式转换(如下类型),因此可以直接通过自定义转换获取 Value。
- 标准数值类型
-
string、bool、DateTime(Offset)、TimeSpan、Guid - 上述值类型的
Nullable<> 版本。
XElement e = new XElement ("now", DateTime.Now);
DateTime dt = (DateTime) e;
XAttribute a = new XAttribute ("resolution", 1.234);
double res = (double) a;
Suggestion
XML 的元素和 Attribute 不会记录数据的原始类型,上述显式转换可能执行失败。推荐将代码包裹在 try/catch 块中,并捕获
FormatException 异常。
10.6.2.1 XML 对象与空运算符
Element 方法和 Attribute 方法的返回值非常适合转化为 Nullable<> 类型,以如下代码为例,程序不会因为“timeout”不存在而抛出异常:
int timeout1 = (int) x.Element ("timeout");
int? timeout2 = (int?) x.Element ("timeout");
配合空合并运算(??)可以去除最终结果中的可空类型。如下代码在 resolution 属性不存在时返回 1.0:
double resolution = (double?) x.Attribute ("resolution") ?? 1.0;
10.6.3 值与混合内容节点
XML 是允许混合内容的,形式如下:
<summary>An XAttribute is <bold>not</bold> an XNode</summary>
要得到上述 X-DOM,需通过 XText 节点:
XElement summary =
new XElement ("summary",
new XText ("An XAttribute is "),
new XElement ("bold", "not"),
new XText (" an XNode")
);
<!--输出-->
<summary>An XAttribute is <bold>not</bold> an XNode</summary>
其中 summary 的 Value 如下,它拼接了各个子节点的 Value:
An XAttribute is not an XNode
Tips
实际传入 string 也是可以的,构造器内部会隐式转为
XText:XElement summary =
new XElement ("summary",
"An XAttribute is ",
new XElement ("bold", "not"),
" an XNode"
);
10.6.4 自动连接 XText 节点
向 XElement 中添加简单内容(字符串)时,X-DOM 会将内容附加至现有 XText :
// 1 个 XText节点
var e1 = new XElement ("test", "Hello"); e1.Add ("World");
e1.Nodes().Count().Dump (); // 输出 1
// 1 个 XText节点
var e2 = new XElement ("test", "Hello", "World");
e2.Nodes().Count().Dump (); // 输出 1
如果显式创建、添加 XText 节点,则会得到多个子节点:
// 2 个 XText节点
var e3 = new XElement ("test", new XText ("Hello"), new XText ("World"));
e3.Nodes().Count().Dump (); // 输出 2
XElement 不会连接这两个 XText 节点,节点对象的标识均得到保留。即便如此,其 ToString 输出的内容仍是拼接的:
<test>HelloWorld</test>
10.7 文档和声明
10.7.1 XDocument
XDocument 可接受的内容包括:
XElement |
XDeclaration |
XDocumentType |
XProgressingInstruction |
XComment |
|
|---|---|---|---|---|---|
| 数量 | 1 | 1 | 1 | 多个 | 多个 |
| 是否必选 | 是 | 否 | 否 | 否 | 否 |
其中 XElement 作为 X-DOM 的根节点。
若 XDocument 未定义 XDeclaration ,调用 XDocument.Save 时,会自动添加默认的 XML 声明:
// 未定义 XDeclaration
var value = new XDocument (
new XElement("test", "data")
);
<!--Save 方法生成的内容:-->
<?xml version="1.0" encoding="utf-16"?>
<test>data</test>
10.7.2 XML 声明(declaration)
10.7.2.1 XML 声明的作用
XDeclaration 对象主要用于指导 XML 的序列化进程,影响的内容有二:
- 文本编码标准
- 声明中的 encoding 和 standalone 如何定义
XDeclaration 构造器接受三个参数:version、encoding 和 standalone。
ExtraNotice
XML 写入器(writer)会忽略指定的 version 信息,总是写入“1.0”。
XML 声明中的编码方式必须使用 IETF 编码方式书写,例如“utf-16”。
10.7.2.2 XElement 和 XDocument 遵循的声明规则
XML 声明用于保证文件被阅读器(reader)正确解析(parse)并理解。XElement 和 XDocument 都遵循以下声明规则:
- 调用
Save 方法将内容写入文件,总是 会自动 写入 XML 声明。 - 调用
Save 方法将内容写入XmlWriter 时,除非XmlWriter 特别指定,否则 会 写入 XML 声明。 -
ToString 方法 不会 生成XML 声明。
Tips
如果不想让
XmlWriter 生成 XML 声明,可以设置XmlWriterSettings 对象的OmitXmlDeclaration 和ConformanceLevel 属性。
Notice
XNode 的WriteTo 方法向XmlWriter 写入, 也会 添加 XML 声明。
10.7.2.3 将 XML 声明输出为字符串
若要将 XDocument 序列化为 string,且包含声明,需使用 Save 方法:
var doc =
new XDocument (
new XDeclaration ("1.0", "utf-8", "yes"),
new XElement ("test", "data")
);
var output = new StringBuilder();
var settings = new XmlWriterSettings { Indent = true };
using (XmlWriter xw = XmlWriter.Create (output, settings))
doc.Save (xw);
<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<test>data</test>
Warn
上述代码即使我们设置编码格式为 utf-8,实际输出的是 utf-16。因为我们输出的对象是
StringBuilder,编码必然是 utf-16。XmlWriter 会自动判断实际输出编码格式,这有效避免了编码格式错误导致的异常。正因此,为避免输出错误的编码格式,
XDocument.ToString 不会包含XDeclaration 内容:var doc =
new XDocument (
new XDeclaration ("1.0", "utf-8", "yes"),
new XElement ("test", "data")
);
doc.ToString().Dump();
输出:
<test>data</test>
10.8 名称(Name)和 命名空间(namespace)
XML 的 namespace 用于避免 命名 冲突。例如 nil 可能有多种含义,但在 http://www.w3.org/2001/xmlschema-instance 命名空间下,表示 C# 中的 null。
10.8.1 XML 中的命名空间
XML 中的 namespace 通过 Attribute 声明:
<customer xmlns="http://domain.com/xmlspace">
<address>
<postcode>02138</postcode>
</address>
</customer>
上述 XML 中,address 和 postcode 也 属于 http://domain.com/xmlspace 命名空间。若不希望子节点继承父节点的命名空间,需显式的令子节点 namespace 为 空 :
<customer xmlns="http://domain.com/xmlspace">
<address xmlns="">
<postcode>02138</postcode>
</address>
</customer>
当然,我们也可以按照10.8.1.1 前缀(namespace 别名)中的方式,为父节点分配前缀。
Info
关于专门设为空的 namespace,我仅在 XAML 中见过一次这样的应用。见x:XData
10.8.1.1 前缀(namespace 别名)
以如下 XML 为例,一次性完成了两步操作(定义和使用):
-
xmlns:nut 定义了前缀 nut; -
nut:customer 将前缀分配至 当前 元素。
<nut:customer xmlns:nut="http://domain.com/xmlspace"/>
Notice
拥有前缀的元素,它的子元素 不会 自动使用相同的 namespace。在如下 XML 中,firstname 的 namespace 分别为 空 和 nut :
<nut:customer xmlns:nut="http://domain.com/xmlspace">
<firstname>Joe</firstname>
</nut:customer>
<nut:customer xmlns:nut="http://domain.com/xmlspace">
<nut:firstname>Joe</nut:firstname>
</customer>
在 XAML 中我们会同时引入多个 namespace,此时可以通过前缀区分不同 namespace 下的成员:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid> </Grid>
</Window>
10.8.1.2 Attribute 与 namespace
XML 中的 Attribute 若要标记 namespace,必须通过前缀。如:
<customer xmlns:nut="OReilly.Nutshell.CSharp" nut:id="123" />
Warn
未用前缀限定的 Attribute 默认使用 空 的 namespace,它不从父元素继承默认 namespace。
一般来说,Attribute 是元素的本地特征,不需要 namespace。通用 Attribute、元数据 Attribute 例外,譬如之前提到的 W3C 中的 nil 代表了 C# 中的 null:
<customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<firstname>Joe</firstname>
<lastname xsi:nil="true" />
</customer>
10.8.2 在 X-DOM 中指定 namespace
为 X-DOM 添加 namespace 的方法有二:
本地名称前用 大括号 指定
以如下代码为例:
new XElement ("{http://domain.com/xmlspace}customer",
new XAttribute("{http://domain.com/xmlspace}id", "123"),
"Bloggs"
);
<customer p1:id="123"
xmlns:p1="http://domain.com/xmlspace"
xmlns="http://domain.com/xmlspace">
Bloggs
</customer>
使用 XNamespace
使用方式如下:
XNamespace ns = "http://domain.com/xmlspace";
new XElement(ns + "data",
new XAttribute(ns + "id", 456),
"123"
);
<data p1:id="123"
xmlns:p1="http://domain.com/xmlspace"
xmlns="http://domain.com/xmlspace">
123
</data>
XNamespace 和 XName 都定义了与 string 类型的隐式转换;XNamespace 还重载了 + 运算符,返回类型为 XName 。
X-DOM 的所有构造器和方法,都使用 XName 类型作为 Element/Attribute 的名称参数,因此我们可以使用 XNamespace + string 的方式传入参数。
10.8.3 X-DOM 和默认 namespace
在 X-DOM 中,不存在“继承 namespace”的概念,若想继承父项 namespace,每个成员都需要 显式指定 。而 X-DOM 在读取或输出 XML 时,若父子 namespace 相同,将 自动缺省子项的 namespace :
XNamespace ns = "http://domain.com/xmlspace";
var data =
new XElement (ns + "data",
new XElement (ns + "customer", "Bloggs"),
new XElement (ns + "purchase", "Bicycle")
);
<data xmlns="http://domain.com/xmlspace">
<customer>Bloggs</customer>
<purchase>Bicycle</purchase>
</data>
若父项指定了 namespace,子项未指定,子项的 namespace 会标记为 空 :
XNamespace ns = "http://domain.com/xmlspace";
var data =
new XElement (ns + "data",
new XElement ("customer", "Bloggs"),
new XElement ("purchase", "Bicycle")
);
<data xmlns="http://domain.com/xmlspace">
<customer xmlns="">Bloggs</customer>
<purchase xmlns="">Bicycle</purchase>
</data>
Warn
当成员的 namespace 不为 空 ,查找元素时传入的 Name 需包含 namespace 信息,例如:
XElement x = data.Element (ns + "customer"); // OK
XElement y = data.Element ("customer"); // null
Suggest
上述指定 namespace 的方式显然很麻烦,我们可以在后期统一指定 namespace:
foreach (XElement e in data.DescendantsAndSelf())
if (e.Name.Namespace == "")
e.Name = ns + e.Name.LocalName;
10.8.4 添加前缀
namespace 在 XML 中本质是 Attribute ,因此我们可以通过 XAttribute 为成员添加前缀。该 Attribute 的 Name 为 XNamespace.Xmlns + 别名 ,Value 为对应的 namespace。以如下 X-DOM 为例:
<data xmlns="http://domain.com/space1">
<element xmlns="http://domain.com/space2">value</element>
<element xmlns="http://domain.com/space2">value</element>
<element xmlns="http://domain.com/space2">value</element>
</data>
插入前缀方式为:
<ns1:data xmlns:ns1="http://domain.com/space1" xmlns:ns2="http://domain.com/space2">
<ns2:element>value</ns2:element>
<ns2:element>value</ns2:element>
<ns2:element>value</ns2:element>
</ns1:data>
XNamespace ns1 = "http://domain.com/space1";
XNamespace ns2 = "http://domain.com/space2";
var mix =
new XElement (ns1 + "data",
new XElement (ns2 + "element", "value"),
new XElement (ns2 + "element", "value"),
new XElement (ns2 + "element", "value")
);
// 插入 namespace
mix.SetAttributeValue (XNamespace.Xmlns + "ns1", ns1);
mix.SetAttributeValue (XNamespace.Xmlns + "ns2", ns2);
// 或
mix.Add(new XAttribute(XNamespace.Xmlns + "ns1", ns1));
mix.Add(new XAttribute(XNamespace.Xmlns + "ns2", ns2));
前缀对于 Attribute 同样有效:
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
var nil = new XAttribute (xsi + "nil", true);
var cust =
new XElement ("customers",
//new XAttribute (XNamespace.Xmlns + "xsi", xsi),
new XElement ("customer",
new XElement ("lastname", "Bloggs"),
new XElement ("dob", nil),
new XElement ("credit", nil)
)
);
cust.SetAttributeValue(XNamespace.Xmlns + "xsi", xsi);
<customers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<customer>
<lastname>Bloggs</lastname>
<dob xsi:nil="true" />
<credit xsi:nil="true" />
</customer>
</customers>
Tips
前缀的引入不会影响 X-DOM 内部的实际结构,它仅在输入、输出时才会用到(如序列化和反序列化)。
Error
虽然 namespace 的声明方式和 XAttribute 极其相似,但不可以用如下方式声明:
var mix = new XElement("data", new XAttribute("xmlns", "{http://domain.com/space1}"));
xmlns 是 xml 中的特殊关键字,上述代码mix 在使用时将抛出XmlException 异常。
xmlns 特性是 XML 中的一个特殊 特性 ,专门用于声明 namespace。
10.9 注解(Annotations)
注解用于存放私有数据,可以附加在任何的 XObject 上,X-DOM 将其视为黑盒。有如下方法操作注解对象:
// 添加或移除
public void AddAnnotation (object annotation)
public void RemoveAnnotations<T>() where T : class
// 检索
public T Annotation<T>() where T : class
public IEnumerable<T> Annotations<T>() where T : class
public T Annotation<T>() where T : class
public IEnumerable<T> Annotations<T>() where T : class
注解使用 Type 作为键(必须是引用类型)。用法如下:
XElement e = new XElement ("test");
e.AddAnnotation (new CustomData { Message = "Hello" } );
e.Annotations<CustomData>().First().Message.Dump();
e.RemoveAnnotations<CustomData>();
e.Annotations<CustomData>().Count().Dump();
class CustomData { internal string Message; }
Error
在10.3.3 自动深度克隆(Automatic Deep Cloning)中我们提到,
XObject 如果有父项,该节点赋值给其他父项时会进行深拷贝。但注解不参与该拷贝,它所在的节点进行拷贝时,新节点的注解为空。
10.10 将数据映射到 X-DOM #delay# 用不到,看不懂,剩余内容推迟再看
我们可以使用 LINQ 将数据从数据源映射至 X-DOM 中,只要该数据源支持 LINQ 查询。
例如我们要通过LINQ查询得到形如下方的 XML:
<customers>
<customer id="1">
<name>Sue</name>
<buys>3</buys>
</customer>
...
</customers>
var customers =
new XElement ("customers",
new XElement ("customer", new XAttribute ("id", 1),
new XElement ("name", "Sue"),
new XElement ("buys", 3)
)
);
在新版 EF 上的操作如下:
var customers =
new XElement ("customers",
from c in Customers.AsEnumerable()
select
new XElement ("customer", new XAttribute ("id", c.ID),
new XElement ("name", c.Name),
new XElement ("buys", c.Purchases.Count)
)
);
// or
var sqlQuery =
from c in Customers.AsEnumerable()
select
new XElement ("customer", new XAttribute ("id", c.ID),
new XElement ("name", c.Name),
new XElement ("buys", c.Purchases.Count)
);
var customers = new XElement ("customers", sqlQuery);
<customers>
<customer id="1">
<name>Tom</name>
<buys>3</buys>
</customer>
<customer id="2">
<name>Harry</name>
<buys>2</buys>
</customer>
...
</customers>
第10章 LINQ to XML的更多相关文章
- 24.C#LINQ TO XML(十二章12.3)
自己也写了那么多,但还有很多不懂,有点浮躁吧,但饭还是要吃啊,说说LINQ TO XML吧. LINQ TO XML位于System.Xml.Linq程序集,并且大多数类型位于System.Xml.L ...
- XML操作:2.LINQ TO XML(http://www.cnblogs.com/AlexLiu/archive/2008/10/27/linq.html)
LINQ to XML 建立,读取,增,删,改 LINQ to XML的出现使得我们再也不需要使用XMLDocument这样复杂的一个个的没有层次感的添加和删除.LINQ可以使的生成的XML文档在 ...
- LINQ系列:LINQ to XML类
LINQ to XML由System.Xml.Linq namespace实现,该namespace包含处理XML时用到的所有类.在使用LINQ to XML时需要添加System.Xml.Linq. ...
- LINQ系列:LINQ to XML操作
LINQ to XML操作XML文件的方法,如创建XML文件.添加新的元素到XML文件中.修改XML文件中的元素.删除XML文件中的元素等. 1. 创建XML文件 string xmlFilePath ...
- LINQ系列:LINQ to XML查询
1. 读取XML文件 XDocument和XElement类都提供了导入XML文件的Load()方法,可以读取XML文件的内容,并转换为XDocument或XElement类的实例. 示例XML文件: ...
- Linq对XML的简单操作
前两章介绍了关于Linq创建.解析SOAP格式的XML,在实际运用中,可能会对xml进行一些其它的操作,比如基础的增删该查,而操作对象首先需要获取对象,针对于DOM操作来说,Linq确实方便了不少,如 ...
- C#学习之Linq to Xml
前言 我相信很多从事.NET开发的,在.NET 3.5之前操作XML会比较麻烦,但是在此之后出现了Linq to Xml,而今天的主人公就是Linq to Xml,废话不多说,直接进入主题. 题外:最 ...
- C#中的Linq to Xml详解
这篇文章主要介绍了C#中的Linq to Xml详解,本文给出转换步骤以及大量实例,讲解了生成xml.查询并修改xml.监听xml事件.处理xml流等内容,需要的朋友可以参考下 一.生成Xml 为了能 ...
- LINQ TO XML 个人的一些心得1
最近没事做,刚来到一个新公司.写了一些处理xml的项目 就是把一些xml的数据处理后存储到数据库中.原本还是准备用原来的xml来写的.在群里有个人说,用linq to xml 好了,比较快捷.就看了 ...
- 高性能Linux服务器 第10章 基于Linux服务器的性能分析与优化
高性能Linux服务器 第10章 基于Linux服务器的性能分析与优化 作为一名Linux系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行.但硬件问题.软件问题.网络环境等 ...
随机推荐
- mkdir递归创建文件夹
mkdir -p 能递归创建文件夹 mkdir 只能创建一级文件夹,如果父文件夹不存在 则报错,所以如果你想用一个很确定的路径 在SHELL脚本里面可以直接写 mkdir -p /home/log/ ...
- 【Azure Function】FTP上传了Python Function文件后,无法在门户页面加载函数的问题
问题描述 通过FTP的方式,把本地能正常运行的Python Function文件上传到云上后,无法加载函数列表问题. 1:上传 function_app.py,requirements.txt文件到 ...
- VS中使用Qt方法详解
相信大家都知道在 Qt Creator 中可以使用 MSVC 编译工具对 Qt 项目进行编译.若有人比较习惯于使用 Visual Studio,或某些项目必须使用 Visual Studio,也可以在 ...
- (Python基础教程之四)Python中的变量的使用
Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...
- 利用Stripes实现Java Web开发
Stripes是一个以让程序员的web开发简单而高效为准则来设计的基于动作的开源Java web框架.本文将介绍Stripes与其它如Struts之类基于动作的框架的区别和其提供的一些存在于Ruby ...
- 定时任务监控服务Healthchecks
GitHub地址:https://github.com/healthchecks/healthchecks 官方文档:https://healthchecks.io/docs/ 按照步骤进行安装: 1 ...
- Ubuntu下xrdp登陆故障解决方案
故障描述: Ubuntu使用xrdp远程桌面运行一段时间后,出现登陆错误: xrdp_mm_process_login_response: login failed 原因分析: 远程桌面没有正确关闭所 ...
- Ubuntu默认启动到字符界面
修改/etc/default/grub sudo cp /etc/default/grub /etc/default/grub.bak sudo chmod 0777 /etc/default/gru ...
- CSS 样式百分比
1.宽高百分比 元素宽度/高度百分比是基于父级元素的width/height,不包含padding,border 注意:高度百分比一定要求父元素有设置height属性,只设置 min-height 虽 ...
- 覆盖全品类数据,腾讯云COS内容审核全新上线
今年,国家网信办深入推进"清朗·春节网络环境"专项行动.截至3月24日,网信办共累计清理相关违法违规信息208万余条,处置账号7.2万余个,协调关闭.取消备案网站平台2300余家. ...