使用Dom与Linq to Xml

1.如何用Xml Dom的方式读取Xml

    Xml Dom方式是最原始的一种操作Xml的途径,从.netFramework 1.0开始就开始支持Dom方式。

1.1如何以Dom方式加载Xml

    要读取Xml首先要加载Xml,加载的方式有两种,一种是从流或类似的Reader加载,例如:

    当然还可以从字符串加载:

 

1.1读取无namespaceXml

    Xml已经准备好了,下面就开始读取这个Xml。现在希望读取data节下面的所有item中的text,那么就可以:

    看看运行结果:

    但是,这样写的问题有很多,例如在data节点中有非item的节点,这样访问,也就被无差别的把非item项也写出来了。例如把如果数据改成这样:

    这样,在data节里面,除了4item,还有一个other,这个other是不需要的,必须被排除掉,如果直接用第一中ChildNodes去访问的话,会得到这样的结果:

    显然“!@#”也被选择出来了,这可不是我们所期望的,

    所以,改用XPath的方式访问:

    其运行结果为:

    很好的other项排除在需要的节点外,这才是我们真正想要的结果:)

1.2读取有namespaceXml

    c#一样Xml也有namespace,并且namespaceXml中的作用巨大,也许你并未感受到namespace的作用,但是,你可能已经不得不面对那些有namespaceXml了。

    好吧,我们先加载一个有namespacexml

    这里,我们准备了一个namespace——urn:vwxyzh,并且把这个namespace缩写成v。举个例子来说,v:data就是urn:vwxyzh这个namespace下面的data

    现在再用原来的XPath去跑一下:

    Oh, no!一个也没有选择出来,为什么会这样哪?

    因为原来的/data/item中的data节是没有namespacedata,和urn:vwxyzhdata不是一回事,所以,这个XPath根本定为不到任何节点。

    必须要修改部分代码才能达到我们的目的,先来看看Select方法有哪些重载吧:

    第一个重载,就是之前使用的那个,第二个重载,需要额外提供一个XmlNamespaceManager实例,一看名字就知道,这个实例是用于管理XmlNamespace的。再查看一下这个类的成员:

    可以发现,创建这个实例需要一个XmlNameTable,谁能提供这个XmlNameTable的实例哪?XmlDocument本身就提供了这个XmlNameTable

    这样,我们就可以修改为:

    先创建一个Manager的实例,然后使用AddNamespace方法,把“v”设置为“urn:vwxyzh”的缩写。然后修改XPath,把data修改成v:dataitem修改成v:item,就可以了,现在来看一下运行结果:

     Yeah!这就是我们所需要的。

·        2 Dom的方式创建/修改xml

    上一篇讲了如何用dom的方式读一个xml,这一篇就讲一下如何用dom的方式去写一个xml。不过,用dom的写Xml本身并不是一个好主意,因为Dom方式本身的废话超多,做一个简单的事情就需要好几句语句,但是作为一个基本的方式还是有必要了解一下的。

2.1 Dom的方式去创建xml

    如果想写出这样一个xml

    那么你可能需要这样一大段代码:

    分析一下,在dom方式下要创建任何一个xml的节点都必须要使用XmlDocument的对应的Create方法创建,然后再添加到对应的位置,这也就是Dom方式最麻烦的地方。

    看看运行结果:

    这个xml和我们期望的xml是等价的,只是没有被格式好,好吧,想要一个格式化好的文档,那么就修改一下写xml的部分(在讲xmlwriter的时候还会讲到这个setting类):

    再看看运行结果:

    这样就和期望的xml一致了。

2.2 Dom的方式去创建有namespacexml

    如果有namespacexml怎么创建哪?

    其实也很简单,换一个重载就可以了,在创建节点的时候用带有namespace的重载就可以了:

    再看一下结果:

2.3 Dom的方式去修改xml

    修改xml其实也无非就是读取xml然后再做必要的增删改。

    在修改之前,首先当然就是要定为到xml的节点,这个在第一篇里面已经讲过。

    如果所做的修改是添加节点那么基本上就和上一节的内容相似:

    在原来这个xml的基础上添加一个person——Allen Lee,可以看到几乎就是把第一篇的读xml和前一节的创建xml结合起来,开看看运行结果吧:

    那么删除节点怎么办哪?

    例如,要从已经有多个Personxml中,删除凡是FirstNameAllenPerson,就可以这样写:

 

 

    注意,这里用了个XPath去查询所有的FirstNameAllenPerson,也就是:

    /v:persons/v:person[v:firstName='Allen']

    vnamespace,之前用已经解释过了,这个XPath要找的是根节点里面的(/persons节点(v:persons)里面的(/person节点(v:person),那么[]在这里是什么意思哪?[]中间的部分代表条件约束,或者说是where,前面的XPath部分已经选择person节点,现在对找到的Person做个条件约束,条件的内容是firstname的值需要是Allenv:firstName=’Allen’)。

    通过上面的这个XPath就可以定位到一个节点集,c#中为XmlNodeList类型,里面有一系列的节点(例子中为1个),然后将他移除即可,不过该死的Dom Api需要在父节点中删除这个节点,也就是不得不用这种很恶心的写法:

    node.ParentNode.RemoveChild(node);

    修改就暂时讲到这里,其他类型的修改由于比较简单,就展开再说了。

    看到这里,想必读者也知道如何操作xml了,但是,DomApi的繁琐写法确实非常影响工作效率,下一篇,将进入Linq to Xml时代,来看看新的Api带来的巨大的工作效率的提升。

·        c#进入了3.0时代,引入了强大的Linq,同时提供了Linqto Xml,这个全新的XmlApi。与Linq toXml相比,传统的DOMApi就显得笨重而繁杂了。

Linq to Xml的本质

    首先,linqto xml是一种in-memory的技术(官方说法是:LINQ to XML provides an in-memory XMLprogramming interface that leverages the .NET Language-Integrated Query (LINQ)Framework. LINQ to XML uses the latest .NET Framework language capabilities andis comparable to an updated, redesigned Document Object Model (DOM) XMLprogramming interface.),也就是说,如果用Linq to Xml去打开一个Xml,也就会占用相应的内存。所以和DOM一样,在极端情况下,会出现内存不足。

    其次,linqto xml从本质上来说,就是linqto object+一套Xml Api,与linq to sqllinq to entity framework不同,后两者是使用特定的LinqProvider去翻译成对应系统的语言。

Linq to Xml的表现

    撇开理论的东西,还是来看看最简单的表现吧。如果要创建这样一个Xml

<?xml version="1.0" encoding="utf-8"?>

<persons>

  <person>

    <firstName>Zhenway</firstName>

    <lastName>Yan</lastName>

    <address>http://www.cnblogs.com/vwxyzh/</address>

  </person>

  <person>

    <firstName>Allen</firstName>

    <lastName>Lee</lastName>

    <address>http://www.cnblogs.com/allenlooplee/</address>

  </person>

</persons>

 

    我们只需要这样一句语句(vb.net可以更简单,当然这个超出了本文的范围):

XDocument doc = new XDocument(

    new XDeclaration("1.0", "utf-8", null),

    new XElement("persons",

       new XElement("person",

           new XElement("firstName", "Zhenway"),

           new XElement("lastName", "Yan"),

           new XElement("address", "http://www.cnblogs.com/vwxyzh/")

           ),

       new XElement("person",

           new XElement("firstName", "Allen"),

           new XElement("lastName", "Lee"),

           new XElement("address", "http://www.cnblogs.com/allenlooplee/")

           )

       )

   );

 

    看起来还行,构造一个Xml要比DOM方式简单的多,不过比起直接写Xml还是复杂了点,不过这个方式也可以这么用:

var persons = new[]

    {

       new

        {

           FirstName = "Zhenway",

           LastName = "Yan",

           Address = "http://www.cnblogs.com/vwxyzh/"

       },

       new

        {

           FirstName = "Allen",

           LastName = "Lee",

           Address = "http://www.cnblogs.com/allenlooplee/"

       }

   };

XDocument doc = new XDocument(

    new XDeclaration("1.0", "utf-8", null),

    new XElement("persons",

       from personin persons

       select new XElement("person",

           new XElement("firstName", person.FirstName),

           new XElement("lastName", person.LastName),

           new XElement("address", person.Address)

           )

       )

   );

 

    这样,就可以看出Linqto Xml在通过外部数据构造Xml的便捷性(vb.net的方式更加简洁)。

    在来看看Linqto Xml的查询:

           XDocument doc = XDocument.Parse(@"<?xmlversion=""1.0"" encoding=""utf-8""?>

<persons>

 <person>

   <firstName>Zhenway</firstName>

   <lastName>Yan</lastName>

   <address>http://www.cnblogs.com/vwxyzh/</address>

 </person>

 <person>

   <firstName>Allen</firstName>

   <lastName>Lee</lastName>

   <address>http://www.cnblogs.com/allenlooplee/</address>

 </person>

</persons>");

           foreach (varitem in doc.Root.Descendants("address"))

           {

                Console.WriteLine((string)item);

           }

    这里,要注意Descendants方法的签名:

public IEnumerable<XElement> Descendants(XName name);

 

    参数类型是XName,而传的是一个string,这个为什么是合法的哪?来看看XName中的一个定义:

public static implicit operator XName(string expandedName);

    原来有个隐式转换,这样就好理解了,编译器自动调用了隐式转换。

    再来看一个更加复杂的查询:

            XDocument doc= XDocument.Parse(@"<?xmlversion=""1.0"" encoding=""utf-8""?>

<persons>

 <person>

   <firstName>Zhenway</firstName>

   <lastName>Yan</lastName>

   <address>http://www.cnblogs.com/vwxyzh/</address>

 </person>

 <person>

   <firstName>Allen</firstName>

   <lastName>Lee</lastName>

   <address>http://www.cnblogs.com/allenlooplee/</address>

 </person>

</persons>");

           foreach (varitem in from personin doc.Root.Descendants("person")

                                 where (string)person.Element("firstName") == "Zhenway"

                                 select (string)person.Element("address"))

           {

                Console.WriteLine(item);

           }

    那么有namespaceXml如何用Linqto Xml来处理哪?

·        上集初步介绍了Linq to Xml的基本操作,简单的新建xml操作和简单的查询xml操作。不过,可以注意到的是上集里面的xml都是没有Namespacexml,那么有Namespacexml如何操作哪?

设置目标

    先看看我们目标,完整这样一个xml

<?xml version="1.0" encoding="utf-8" ?>
<v:persons xmlns:v="http://www.cnblogs.com/vwxyzh/">
  <v:person>
    <v:firstName>Zhenway</v:firstName>
    <v:lastName>Yan</v:lastName>
    <v:address>http://www.cnblogs.com/vwxyzh/</v:address>
  </v:person>
</v:persons>

    注意,这个xml的每一个节点都是 http://www.cnblogs.com/vwxyzh/ 这个命名空间下的。

    当然,这样的xml也有很多种等效写法,具体请参考w3shools

分析实现手段

    与之前一集相比,这里的”persons”,不再是一个纯粹的”persons”,而是一个带有Namespacepersons,所以在创建这样一个节点时不再是之前的:

var persons = new XElement("persons");

    而是需要修改成带有Namespace的节点名。

    那么如何获得这个带有Namespace的节点名哪?

    好吧,让我们回过头来看看XElement的构造函数:

public XElement(XName name);

    注意哦,参数的类型是XName,而不是string,那么平时为什么能用string哪?因为上一集里面提到过,XName定义了一个隐式的转换,可以把string隐式的转换成XName

    所以,关于Namespace自然也要从XNamespace入手,然后找一个能够变成XName的方法,察看XNamespace的定义,就可以看到:

public static XName operator +(XNamespace ns, string localName);

 

    只要把XNamespace加上本地名称(string),就是一个XName了,非常简单。

    再看看如何创建一个XNamespace

public static implicit operator XNamespace(string namespaceName);

    又是隐式转换。。。来看看具体如何创建一个带namespacepersons吧:

XNamespace v = "http://www.cnblogs.com/vwxyzh/";
var persons = new XElement(v + "persons");

    定义一个namespace,在使用时直接+string即可。在c#里面这已经是最简单的方式了。

实现

    到这里,已经可以完成上面的那个目标xml了:

XNamespace v = "http://www.cnblogs.com/vwxyzh/";
XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", null),
    new XElement(v + "persons",
        new XElement(v + "person",
            new XElement(v + "firstName", "Zhenway"),
            new XElement(v + "lastName", "Yan"),
            new XElement(v + "address", "http://www.cnblogs.com/vwxyzh/")
            )
        )
    );
doc.Save(Console.Out);

    来看看执行结果:

<?xml version="1.0" encoding="gb2312"?>
<persons xmlns="http://www.cnblogs.com/vwxyzh/">
  <person>
    <firstName>Zhenway</firstName>
    <lastName>Yan</lastName>
    <address>http://www.cnblogs.com/vwxyzh/</address>
  </person>
</persons>

    和预期的略有不同,首先encoding被修改成gb2312,这是因为中文操作系统的Console的编码是gb2312,所以Xmlencoding被自动修改了,其次,原来的Namespacev来缩写,但是输出的xml缺是改用了默认Namespace,不过如果看过前面提到的w3schools的话,就知道这两者是等价xml

扩展

    在查找一个xml时,同样也是需要一个XName,因此当遇到有Namespacexml,也可以用同样的手法:

            XDocument doc = XDocument.Parse(@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<v:persons xmlns:v=""http://www.cnblogs.com/vwxyzh/"">
  <v:person>
    <v:firstName>Zhenway</v:firstName>
    <v:lastName>Yan</v:lastName>
    <v:address>http://www.cnblogs.com/vwxyzh/</v:address>
  </v:person>
  <v:person>
    <v:firstName>Allen</v:firstName>
    <v:lastName>Lee</v:lastName>
    <v:address>http://www.cnblogs.com/allenlooplee/</v:address>
  </v:person>
</v:persons>");
            XNamespace v = "http://www.cnblogs.com/vwxyzh/";
            foreach (var item in from person in doc.Root.Descendants(v + "person")
                                 where (string)person.Element(v + "firstName") == "Zhenway"
                                 select (string)person.Element(v + "address"))
            {
                Console.WriteLine(item);
            }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值