找回密码
 立即注册
首页 业界区 业界 LINQ之路17:LINQ to XML之X-DOM介绍

LINQ之路17:LINQ to XML之X-DOM介绍

周濡霈 2025-5-29 16:01:56
.NET Framework提供了数种操作XML数据的API。从Framework 3.5开始,最重要的用来处理XML文档的技术当属LINQ to XML。LINQ to XML由一个轻量级的XML文档对象模型和一组补充查询运算符组成,并且,该文档对象模型是LINQ友好的。多数情况下,它可以完全取代XML技术的前身:符合W3C规则的DOM,如XmlDocument。现在,就让我们一起开始LINQ to XML的学习之旅,看看它是怎样简化XML的查询与操作,提高我们的工作效率的。
所有的LINQ to XML类型都定义在System.Xml.Linq命名空间中,请记住在运行相关示例时导入该命名空间。
架构预览

认识DOM

让我们先来简单认识一下DOM的概念,然后再来解释LINQ to XML’s DOM背后的基本原理。
考虑下面的XML文件:
  1. <?xml version="1.0" encoding="utf-8" standalone="yes"?><br><customer id="123" status="archived"><br>  <firstname>Joe</firstname><br>  <lastname>Bloggs</lastname><br></customer>
复制代码
和所有的XML文件一样,我们从一个XML声明开始,然后是root元素,元素名为customer。它有2个attributes,名字分别为id和status,值分别为”123”和”archived”。在customer元素之内,包含了两个子元素firstname和lastname,分别包含一个文本内容 ("Joe"和"Bloggs")。
上面的每一种结构:declaration, element, attribute, value, 和text content,都可以用一个相应的类来表示。如果为这些类加上某些集合属性来存储子内容,那么我们就可以通过一个对象树来完整的描述一个文档,这就是文档对象模型,简称DOM。
LINQ to XML文档对象模型(DOM)

LINQ to XML由下面两部分内容组成:

  • 一个XML DOM,我们可以称为X-DOM
  • 一组(大约10个)补充查询运算符
X-DOM包含的类型有XDocument、XElement和XAttribute等。有意思的是,X-DOM并不是和LINQ绑定在一起的,我们可以撇开LINQ查询而单独装载、实例化、更新和保存X-DOM类型。相应的,我们可以使用LINQ来查询老的W3C式样的XML类型,当然,这会有很多限制。
X-DOM的重要特征是它是LINQ友好的,这意味着:

  • 它提供了产生IEnumerable sequences的方法,然后我们就可以在sequence之上来建立查询。
  • 它的构造函数允许我们通过一个LINQ数据转换来创建一个X-DOM树
X-DOM介绍

下图显示了X-DOM的核心类型。使用最频繁的类型当属XElement。XObject是该继承层次的根类型;XElement和XDocument是具体的容器类型。
1.png

XObject
XObject所有XML数据的抽象基类,它定义了Parent element和XDocument。
XNode
XNode是大部分XML数据的基类(除了XAttribute。一个XNode可以位于一个融合了多种XNode类型的有序集合之中,比如下面的XML:
  1. <data><br>  Hello world<br>  <subelement1/><br>  <br>  <subelement2/><br></data>
复制代码
在父元素之内,首先是一个XText node (Hello world),然后是一个XElement node,然后是XComment node,最后是第二个XElement node。
尽管一个XNode可以访问它的父元素,但是它并没有子节点/child nodes的概念。子节点由XContainer类提供。XNode除了包括XElement,它还包含了XText、XComment等类型节点(见上图),在后面讲述X-DOM导航时你会看到各种XXXNodes和XXXElements方法,请记得这个区别。
XContainer
XContainer定义了处理子节点的方法,它是XElement和XDocument的抽象基类。
XElement
XElement包含了管理属性的方法,以及Name和Value成员。并且,由于它的基类是XContainer,所以它可以包含子节点。通常情况下,一个element会有单个XText子节点,并且Value属性封装了对该XText子节点的操作。所以,多数情况下,我们并不需要直接和XText打交道。
XDocument
XDocument表示了XML树的根节点。它包装了root XElement、一个XDeclaration、processing instructions、和其他根级类型对象。和W3C DOM不同的是,对XDocument的使用是可选的,我们可以在不创建XDocument的情况下装载、操作和保存一个X-DOM!对XDocument的独立性甚至意味着我们可以高效的把一个node子树移动到另一个不相关的X-DOM层次对象中去。
Loading和Parsing

Xelement和XDocument都提供了静态的Load和Parse方法,他们用来创建X-DOM tree。

  • Load用来从file、URI、Stream、TextReader或XmlReader创建X-DOM
  • Parse用来从string创建X-DOM
示例如下:
  1.             XDocument fromWeb = XDocument.Load("http://albahari.com/sample.xml");<br>            XElement fromFile = XElement.Load(@"e:\media\somefile.xml");<br>            XElement config = XElement.Parse(<br>                @"<configuration><br>                    <client enabled='true'><br>                        <timeout>30</timeout><br>                    </client><br>                </configuration>");
复制代码
保存和序列化

调用任意node的ToString()方法都会把它的内容转换到一个XML string,该string由分行符和缩进格式化,当然我们可以通过SaveOptions.DisableFormatting属性来取消分行符合缩进。
XElement和XDocument还提供了Save方法来把一个X-DOM写到一个file, Stream, TextWriter, 或 XmlWriter。如果指定的是一个file,则会自动添加一个XML declaration。对XML declarations的详细介绍会在之后给出。
实例化X-DOM

除了使用Load和Parse方法,我们还可以通过实例化对象并把他们添加至父节点来创建X-DOM tree。
  1.             // 构建XElement和XAttribute时,只需提供name和value<br>            XElement lastName = new XElement("lastname", "Bloggs");<br>            lastName.Add(new XComment("nice name"));<br> <br>            XElement customer = new XElement("customer");<br>            customer.Add(new XAttribute("id", 123));<br>            customer.Add(new XElement("firstname", "Joe"));<br>            customer.Add(lastName);<br> <br>            Console.WriteLine(customer.ToString());
复制代码
结果如下:
  1. <customer id="123"><br>  <firstname>Joe</firstname><br>  <lastname>Bloggs</lastname><br></customer>
复制代码
创建XElement时,value参数是可选的,我们可以只指定name而在此之后再添加内容。当我们提供了value属性时,一个简单的string就可以了,XDOM会自动将其转换为XText子节点。
函数式构造/Functional Construction

在上一个例子中,我们很难从代码中看出XML的结构。现在,XDOM支持了另外一种实例化模式:函数式构造(由函数式编程而来)。通过函数式构造,我们在一个表达式中就可以创建完整的XDOM tree:
  1.             XElement customer =<br>                new XElement("customer", new XAttribute("id", 123),<br>                    new XElement("firstname", "joe"),<br>                    new XElement("lastname", "bloggs",<br>                        new XComment("nice name")<br>                    )<br>                );
复制代码
这样有两个优势。首先,代码反映了XML的结构;其次,它可以与LINQ查询的select子句进行交互。比如,下面的LINQ to SQL查询直接把结果转换到一个X-DOM:
  1.             XElement query =<br>                new XElement("customers",<br>                    from c in dataContext.Customers<br>                    select<br>                    new XElement("customer", new XAttribute("id", c.ID),<br>                        new XElement("firstname", c.FirstName),<br>                        new XElement("lastname", c.LastName,<br>                            new XComment("nice name")<br>                        )<br>                    )<br>                );
复制代码
这种技术的详细信息会在后续篇章中再作介绍。
函数式构造工作方式

函数式构造之所以可行,是因为XElement和XDocument的构造函数提供了如下的重载形式:
  1.         // params object[]意味着可以接受任意数量的参数<br>        public XElement (XName name, params object[] content)<br>        // XContainer的Add方法也是如此:<br>        public void Add (params object[] content)
复制代码
所以,当我们创建或添加X-DOM时可以指定任意数量任意类型的子对象(child objects)。那么XContainer为何能接受任意类型的子对象呢?要知道原因,我们就需要来查看它们在内部实现中是如何处理每个子对象的。下面就是XContainer类型(XElement和XDocument的基类型)对于子对象的处理方式:

  • 如果该对象为空,忽略它
  • 如果该对象基于XNode或XStreamingElement,添加至Nodes集合。
  • 如果该对象是一个XAttribute,添加至Attributes集合。
  • 如果是一个string,用XText节点封装该string并添加到Nodes集合。
  • 如果该对象实现了IEnumerable,则遍历它,上面的规则会应用到其中每一个element。
  • 否则,该对象会被转换至一个string,经XText节点封装后添加到Nodes集合。
所有的数据最后都会被添加到Nodes或Attributes集合之一,并且,任何对象都是有效的,因为不管怎样,我们都可以调用ToString来把它转换成一个string。
那么上面LINQ查询中的select语句是如何向XElement中添加元素的呢,记住,LINQ查询结果为一IEnumerable(上例T为XElement) sequence,所以应用规则5,遍历该sequence,把每一个XElement都添加至name为customers的XElement之中。
自动完全克隆

当一个node或attribute被添加到某个element后,它的Parent属性就指向了该element。一个node只能有一个parent element。所以当你把一个已经有Parent属性的node添加到另一个element后,该node会被自动深克隆(deep-cloned)。示例如下:
  1.             // 每个customer都有address对象的一份拷贝<br>            var address = new XElement("address",<br>                new XElement("street", "Hong mei"),<br>                new XElement("town", "Xu hui")<br>            );<br> <br>            var customer1 = new XElement("customer1", address);<br>            var customer2 = new XElement("customer2", address); // address被同时添加至customer1和customer2<br>            customer1.Element("address").Element("street").Value = "Yi shan";           //修改customer1的address<br>            Console.WriteLine(customer2.Element("address").Element("street").Value);    // Hong mei, customer2的address并没有被修改
复制代码
这种自动复制的技术保证了X-DOM对象实例化时避免了意想不到的副作用。
 
在下一篇中,我们将会对LINQ to XML的导航查询功能进行详细的介绍。
 
系列博客导航:
LINQ之路系列博客导航
LINQ之路 1:LINQ介绍
LINQ之路 2:C# 3.0的语言功能(上)
LINQ之路 3:C# 3.0的语言功能(下)
LINQ之路 4:LINQ方法语法
LINQ之路 5:LINQ查询表达式
LINQ之路 6:延迟执行(Deferred Execution)
LINQ之路 7:子查询、创建策略和数据转换
LINQ之路 8:解释查询(Interpreted Queries)
LINQ之路 9:LINQ to SQL 和 Entity Framework(上)
LINQ之路10:LINQ to SQL 和 Entity Framework(下)
LINQ之路11:LINQ Operators之过滤(Filtering)
LINQ之路12:LINQ Operators之数据转换(Projecting)
LINQ之路13:LINQ Operators之连接(Joining)
LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)
LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法
LINQ之路17:LINQ to XML之X-DOM介绍
LINQ之路18:LINQ to XML之导航和查询
LINQ之路19:LINQ to XML之X-DOM更新、和Value属性交互
LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces
LINQ之路21:LINQ to XML之生成X-DOM(Projecting)
LINQ之路系列博客后记
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册