LINQ之路19:LINQ to XML之X-DOM更新、和Value屬性交互
本篇包含兩部分內容: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",
new XElement("timeout", 30)
);
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 = new XElement("settings");
settings.SetElementValue("timeout", 30); // 添加子節點
settings.SetElementValue("timeout", 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 = new XElement("items",
new XElement("one"),
new XElement("three")
);
items.FirstNode.AddAfterSelf(new XElement("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();
// 結果如下:
<contacts>
<customer name="Mary" />
<customer name="Chris" archived="true" />
</contacts>
// 與之相比,下面這個這個更簡單的查詢刪除所有的注釋元素:
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 = new XElement("date", DateTime.Now);
e.SetValue(DateTime.Now.AddDays(1));
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 = new XElement("now", DateTime.Now);
DateTime dt = (DateTime)e;
XAttribute a = new XAttribute("resolution", 1.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 = new XElement("now", DateTime.Now);
// 下面的代碼會產生運行時錯誤
int timeout = (int) x.Element ("timeout");
// OK; timeout2 is null.
int? timeout2 = (int?) x.Element ("timeout");
我們可以使用??操作符來去除最終結果中的可空類型。如下所示:
// 如果resolution屬性不存在,則使用默認值1.0
double resolution = (double?) x.Attribute ("resolution") ?? 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子句中添加一個條件:
where cust.Attributes ("credit").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 = new XElement("summary",
new XText("An XAttribute is "),
new XElement("bold", "not"),
new XText(" 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 = new XElement("test", "Hello"); e1.Add("World");
var e2 = new XElement("test", "Hello", "World");
Console.WriteLine(e1.Nodes().Count()); // 1
Console.WriteLine(e2.Nodes().Count()); // 1
但如果我們明確指定創建XText節點,則我們會得到多個子節點:
var e = new XElement("test", new XText("Hello"), new XText("World"));
Console.WriteLine(e.Value); // HelloWorld
Console.WriteLine(e.Nodes().Count()); // 2
下一篇博客中我會和大家討論LINQ to XML中的Documents、Declarations和Namespaces。

浙公網安備 33010602011771號