1.如何用Xml Dom的方式读取Xml
Xml Dom方式是最原始的一种操作Xml的途径,从.netFramework 1.0开始就开始支持Dom方式。
1.1如何以Dom方式加载Xml
要读取Xml首先要加载Xml,加载的方式有两种,一种是从流或类似的Reader加载,例如:
当然还可以从字符串加载:
1.1读取无namespace的Xml
Xml已经准备好了,下面就开始读取这个Xml。现在希望读取data节下面的所有item中的text,那么就可以:
看看运行结果:
但是,这样写的问题有很多,例如在data节点中有非item的节点,这样访问,也就被无差别的把非item项也写出来了。例如把如果数据改成这样:
这样,在data节里面,除了4个item,还有一个other,这个other是不需要的,必须被排除掉,如果直接用第一中ChildNodes去访问的话,会得到这样的结果:
显然“!@#”也被选择出来了,这可不是我们所期望的,
所以,改用XPath的方式访问:
其运行结果为:
很好的other项排除在需要的节点外,这才是我们真正想要的结果:)
1.2读取有namespace的Xml
和c#一样Xml也有namespace,并且namespace在Xml中的作用巨大,也许你并未感受到namespace的作用,但是,你可能已经不得不面对那些有namespace的Xml了。
好吧,我们先加载一个有namespace的xml:
这里,我们准备了一个namespace——urn:vwxyzh,并且把这个namespace缩写成v。举个例子来说,v:data就是urn:vwxyzh这个namespace下面的data。
现在再用原来的XPath去跑一下:
Oh, no!一个也没有选择出来,为什么会这样哪?
因为原来的/data/item中的data节是没有namespace的data,和urn:vwxyzh的data不是一回事,所以,这个XPath根本定为不到任何节点。
必须要修改部分代码才能达到我们的目的,先来看看Select方法有哪些重载吧:
第一个重载,就是之前使用的那个,第二个重载,需要额外提供一个XmlNamespaceManager实例,一看名字就知道,这个实例是用于管理Xml的Namespace的。再查看一下这个类的成员:
可以发现,创建这个实例需要一个XmlNameTable,谁能提供这个XmlNameTable的实例哪?XmlDocument本身就提供了这个XmlNameTable:
这样,我们就可以修改为:
先创建一个Manager的实例,然后使用AddNamespace方法,把“v”设置为“urn:vwxyzh”的缩写。然后修改XPath,把data修改成v:data,item修改成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的方式去创建有namespace的xml
如果有namespace的xml怎么创建哪?
其实也很简单,换一个重载就可以了,在创建节点的时候用带有namespace的重载就可以了:
再看一下结果:
2.3 用Dom的方式去修改xml
修改xml其实也无非就是读取xml然后再做必要的增删改。
在修改之前,首先当然就是要定为到xml的节点,这个在第一篇里面已经讲过。
如果所做的修改是添加节点那么基本上就和上一节的内容相似:
在原来这个xml的基础上添加一个person——Allen Lee,可以看到几乎就是把第一篇的读xml和前一节的创建xml结合起来,开看看运行结果吧:
那么删除节点怎么办哪?
例如,要从已经有多个Person的xml中,删除凡是FirstName叫Allen的Person,就可以这样写:
注意,这里用了个XPath去查询所有的FirstName叫Allen的Person,也就是:
/v:persons/v:person[v:firstName='Allen']
v是namespace,之前用已经解释过了,这个XPath要找的是根节点里面的(/)persons节点(v:persons)里面的(/)person节点(v:person),那么[]在这里是什么意思哪?[]中间的部分代表条件约束,或者说是where,前面的XPath部分已经选择person节点,现在对找到的Person做个条件约束,条件的内容是firstname的值需要是Allen(v: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 sql和linq 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);
}
那么有namespace的Xml如何用Linqto Xml来处理哪?
· 上集初步介绍了Linq to Xml的基本操作,简单的新建xml操作和简单的查询xml操作。不过,可以注意到的是上集里面的xml都是没有Namespace的xml,那么有Namespace的xml如何操作哪?
设置目标
先看看我们目标,完整这样一个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”,而是一个带有Namespace的persons,所以在创建这样一个节点时不再是之前的:
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);
又是隐式转换。。。来看看具体如何创建一个带namespace的persons吧:
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,所以Xml的encoding被自动修改了,其次,原来的Namespace用v来缩写,但是输出的xml缺是改用了默认Namespace,不过如果看过前面提到的w3schools的话,就知道这两者是等价xml。
扩展
在查找一个xml时,同样也是需要一个XName,因此当遇到有Namespace的xml,也可以用同样的手法:
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);
}