本篇包含两部分内容:X-DOM更新一节中我们会详细讨论LINQ to XML的更新方式,包括Value的更新、子节点和属性的更新、通过Parent节点实现更新;
和Value属性交互一节会详细讨论XElement和XAttribute的Value属性。如果一个元素只有单个XText子节点,那么XElement的Value属性即是对该子节点的快捷方式。对于XAttribute来讲,Value就是指其属性值。X-DOM为XElement和XAttribute的Value属性提供了一致的操作方式。
X-DOM更新
Value更新
成员 |
目标对象 |
Value { get; set } |
XElement,XAttribute |
SetValue (object value) |
XElement,XAttribute |
SetValue方法把element或attribue的内容替换为给定的简单值。设置Value属性完成相同的工作,但只接受string数据。下一节“和Value交互”详细讨论了这两个成员。
调用SetValue(或设置Value属性)时值得注意的地方是:它会替换所有的子节点/child nodes:
XElement settings = new XElement("settings",timeout30) ); settings.SetValue(XXX"); // SetValue后子节点timeout已经被替换了 Console.WriteLine(settings.ToString()); Result: <settings>XXX</settings>
更新子节点和属性
类型 |
成员 |
目标对象 |
Add |
Add (params object[] content) |
XContainer |
AddFirst (params object[] content) |
XContainer |
|
Remove |
RemoveNodes() |
XContainer |
RemoveAttributes() |
XElement |
|
RemoveAll() |
XElement |
|
Update |
ReplaceNodes (params object[] content) |
XContainer |
ReplaceAttributes (params object[] content) |
XElement |
|
ReplaceAll (params object[] content) |
XElement |
|
SetElementValue (XName name,object value) |
XElement |
|
SetAttributeValue (XName name,object value) |
XElement |
上表中最方便的方法是最后两个:SetElementValue 和SetAttributeValue。他们实例化一个XElement或XAttribute,把它加至parent,并替换掉同名的元素或属性:
XElement settings = "); settings.SetElementValue(30); 添加子节点 settings.SetElementValue(60); 更新至60
Add方法向element或document对象添加一个子节点。AddFirst完成相同的工作,但是新节点会插入到最前面。
RemoveNodes方法会删除所有的子节点;RemoveAttributes会删除所有的属性;RemoveAll等价于调用这两个方法,同时删除所有子节点和属性。
ReplaceXXX方法等价于先调用Remove再调用Add方法。他们会先行获得输入参数的一个快照(拷贝),所以settings.ReplaceNodes(settings.Nodes());也会按照我们期望的方式工作。
通过Parent节点更新
成员 |
目标对象 |
AddBeforeSelf (params object[] content) |
XNode |
AddAfterSelf (params object[] content) |
XNode |
Remove() |
XNode*,XAttribute* |
ReplaceWith (params object[] content) |
XNode |
上表中的方法AddBeforeSelf、AddAfterSelf、Remove、和ReplaceWith不是用来操作子节点(children)。相反,他们操作当前节点本身所在的集合。这就决定了节点必须有一个父元素,否则将会抛出异常。
AddBeforeSelf和AddAfterSelf根据当前节点的位置来插入一个新节点:
XElement items = itemsone"),0)">three") ); items.FirstNode.AddAfterSelf(two")); Console.WriteLine(items.ToString(SaveOptions.DisableFormatting)); 结果如下: <items><one /><two /><three /></items>
像上面这样在任意位置插入一个节点是非常有效的,哪怕是在一个有很多元素的sequence中,原因在于X-DOM中的nodes在内部是以链表的形式实现的。
Remove方法在其parent元素中删除当前节点本身;ReplaceWith完成相同的工作,然后在当前位置插入其他指定的内容。示例如下:
XElement items = XElement.Parse (<items><one/><two/><three/></items>"); items.FirstNode.ReplaceWith (new XComment (One was here")); <items><!--one was here--><two /><three /></items>
删除一个节点或属性sequence
正因为有了System.Xml.Linq中定义的扩展方法,我们可以在一个节点或属性sequence上调用Remove方法,实现一次删除多个节点或属性的功能:
假如我们有如下格式的XElement XElement contacts = XElement.Parse( @"<contacts> <customer name='Mary'/> <customer name='Chris' archived='true'/> <supplier name='Susan'> <phone archived='true'>012345678<!--confidential--></phone> </supplier> </contacts>"); 下面的查询删除所有的customers: contacts.Elements (customer").Remove(); 下面的代码删除所有archived为true的元素(Chris被删除): contacts.Elements().Where (e => (bool?) e.Attribute (archived") == true) .Remove(); 如果我们使用Descendants()来替换Elements(),phone元素也会被删除,结果如下: <contacts> <customer name=Mary" /> <supplier name=Susan" /> </contacts> 下面的示例删除任意位置带有confidential注释的所有元素: contacts.Elements().Where (e => e.DescendantNodes() .OfType<XComment>() .Any (c => c.Value == confidential") ).Remove(); 结果如下:" /> <customer name=Chris" archived=true 与之相比,下面这个这个更简单的查询删除所有的注释元素: contacts.DescendantNodes().OfType<XComment>().Remove();
和Values属性交互
XElement和XAttribute都有一个string类型的Value属性。如果一个元素只有单个XText子节点,那么XElement的Value属性即是对该子节点的快捷方式。对于XAttribute来讲,Value就是指其属性值。X-DOM为XElement和XAttribute的Value属性提供了一致的操作方式。
设置Values
有两种方式来设置一个value:调用SetValue方法或设置Value属性。SetValue更加灵活因为它不只接受string类型的参数,还可以接受其他类型数据:
var e = date1)); Console.WriteLine(e.Value); 2011-12-05T23:20:40.296875+08:00
上面的代码不只是设置了元素的Value属性,而且还意味着手动把DateTime值转换为string值。这种转换不是简单的调用ToString完成的,而是使用XmlConvert来得到一个符合XML格式要求的结果。
当我们把一个值传给XElement或XAttribute的构造函数时,对于非string类型参数会自动进行同样的转换。这保证了DateTime值被正确格式化,另外,true会被表示成小写形式,double.NegativeInfinity被表示成“-INF”。
获取Values
我们可以简单的把XElement或XAttribute强制转换为期望的数据类型来获得它的初始值,比如:
XElement e = nownew XAttribute(resolution1.234); double res = (double)a;
一个元素或属性并不会在内部存放 DateTime或double值,而总是转换为字符串存放起来。他们也没有任何办法记起原来的类型,所以我们必须负责把他们转换为正确的类型,否则会出现运行时错误。为了让代码更加健壮,可以在try/catch代码块中进行数据转换并捕捉FormatException异常。
可以将XElement和XAttribute强制转换为如下数据类型:
- 所有标准数值类型
- string,bool,DateTime,DateTimeOffset,TimeSpan,Guid
- 上述所列值类型的可空版本(Nullable<> versions)
转换为可空类型在与Element和Attribute方法一起使用时非常有效,因为即使请求的元素/属性不存在,转换也能正常完成,比如:
x 下面没有timeout元素 XElement x = 下面的代码会产生运行时错误 int timeout = (int) x.Element ( OK; timeout2 is null.int? timeout2 = (int?) x.Element (");
我们可以使用??操作符来去除最终结果中的可空类型。如下所示:
如果resolution属性不存在,则使用默认值1.0double resolution = (double?) x.Attribute (") ?? 1.0;
但是,如果元素或属性存在,但是拥有一个空值,或格式不正确的值,那么即使使用可空类型,我们还是会遇到麻烦。针对这种情况,我们必须捕捉FormatException异常。
我们也可以在LINQ查询中进行转换,下面的查询返回”John”:
var data = XElement.Parse( <data> <customer id='1' name='Mary' credit='100' /> <customer id='2' name='John' credit='150' /> <customer id='3' name='Anne' /> </data>"); 转换为nullable int会防止Anne中出现空引用异常,因为他没有credit属性 IEnumerable<string> query = from cust in data.Elements() where (int?)cust.Attribute(credit") > 100 select cust.Attribute(name").Value;
where cust.Attributes (").Any() && (int) cust.Attribute...
Values和多种类型子节点
前面说到,我们可以通过Value属性来处理元素的XText子节点。那么你也许想知道,我们还需要直接处理XText节点吗?答案是当我们有多种类型的子节点时。比如:
<summary>An XAttribute is <bold>not</bold> an XNode</summary>
一个简单的Value属性并不能完全表示summary的内容。Summary元素包含了三个子节点:XText node、XElement、另一个XText node。下面是构建该summary的代码:
XElement summary = summarynew XText(An XAttribute is boldnot an XNode") );
有意思的是,我们还是可以查询summary的Value属性,它并不会抛出异常。但是,它会返回所有子节点的Value属性在经过连接之后形成的一个字符串值:An XAttribute is not an XNode
当然,我们也可以重新对summary的Value属性赋值,只是它会替换上面所有的子节点。
XText自动拼接
当我们在XElement中添加简单的字符串值时,X-DOM会把值拼接在已有XText子节点的后面,而不是创建新的XText子节点。下面的示例中,e1和e2都只有一个XText节点,其Value为HelloWorld:
var e1 = testHello"); e1.Add(Worldvar e2 = "); Console.WriteLine(e1.Nodes().Count()); 1 Console.WriteLine(e2.Nodes().Count()); 1
但如果我们明确指定创建XText节点,则我们会得到多个子节点:
")); Console.WriteLine(e.Value); HelloWorld Console.WriteLine(e.Nodes().Count()); 2
下一篇博客中我会和大家讨论LINQ to XML中的Documents、Declarations和Namespaces。