第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
、XmlReader
XDocument 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系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行.但硬件问题.软件问题.网络环境等 ...
随机推荐
- VUE懒加载的table前端搜索
// 前端搜索 fliterData() { const search = this.search if (search) { this.blist = this.list.filter(item = ...
- Litctf2024-郑州轻工业大学第二届ctf-校内赛道wp
战队:怎落笔都不对 最终成绩校内第4 MISC 1. 盯帧珍珠 打开文件发现是一个图片,放入 010 查看得文件头是 gif 格式 改为gif后缀得到一个GIF图,在下面这个网站分解,即可得到flag ...
- Java8提供的Stream方式进行分组GroupingBy
有时我们需要对集合进行分组操作,这时可以使用Java8提供的Stream方式进行分组.挺好用的,此处记录下.直接贴code: Road实体: @Data @NoArgsConstructor @A ...
- 编译器-FOLLOW集合
语法分析器的两个重要函数 FIRST和FOLLOW 一.FOLLOW的定义 在句型中紧跟在A右边的终结符号的集合 如果A是某些句型的最右符号,那么$在FOLLOW(A)中 A:非终结符 二.计算方法 ...
- nginx的子路径重写替换
在nginx中配置proxy_pass代理转发时,如果在proxy_pass后面的url加/,表示绝对根路径:如果没有/,表示相对路径,把匹配的路径部分也给代理走. 假设下面四种情况分别用 http ...
- mysql5.7以后group by 报错 sql_mode=only_full_group_by的解决方法
一.发现问题 1.查询语句 SELECT * from class group by class_name; 2.报错结果 ..... this is incompatible with sql_mo ...
- k8s calico-node错误日志 listen tcp: lookup localhost on 8.8.4.4:53: no such host
项目场景:K8s搭建 问题描述:查看pods状态,发现 calico-node异常[root@k8s-master ~]# kubectl get pods --all-namespacesNAMES ...
- 龙哥量化:通达信的macd改进优化方法及选股公式源码
有很多同学是看macd的数值,遇到股价比较低的,macd数值变成0.00,就看不明白了, 优化: 第一步,给股价乘100,所有的哦 源码: DIF:EMA(CLOSE*100,12)-EMA(CLOS ...
- 在不同操作系统上安装 PostgreSQL
title: 在不同操作系统上安装 PostgreSQL date: 2024/12/26 updated: 2024/12/26 author: cmdragon excerpt: PostgreS ...
- 如何使用图片的exif信息计算相机焦距
135胶卷源于35mm高度的打孔电影胶片,1913年,德国人奥斯卡·巴纳克将其用于他发明的徕卡(Leica)牌小型照相机上,由此形成标准.35mm电影胶卷,35mm指的是胶卷的高度为35mm,由于上下 ...