名稱與命名空間
與.NET類型可以擁有命名空間一樣, XML元素和屬性也同樣可以擁有命名空間.
XML命名空間主要完成兩件事情. 首先, 與C#的命名空間一樣, 它們可以幫助避免命名沖突. 當你要合並來自兩個不同XML文件的時候這可能會成為一個問題. 其次, 命名空間賦予了名稱一個絕對的意義. 例如, 名稱”nil” 可以代表任何意思, 然而, 如果和http://www.w3.org/2001/XMLSchema-instance命名空間一起, “nil”表示類似於C#當中null的意思, 並且有特定的規則指示其如何被應用.
XML的命名空間是使用xmlns屬性來定義的:
1: <customer xmlns="OReilly.Nutshell.CSharp"/>
xmlns是一個特殊的保留屬性. 當我們這樣使用時, 它主要執行兩個功能:
我們也可以使用一個前綴(prefix)指定命名空間——這可以用來避免重復.主要有兩個步驟——定義前綴和使用前綴. 我們也可以類似下面的做法將它們同時定義:
1: <nut:customer xmlns:nut="OReilly.Nutshell.CSharp"/>
兩件不同的事情在這裡產生. 在右邊, xmlns:nut=”…”定義了一個前綴叫做nut並使其對於元素本身以及它所有的後代元素都是可用的. 在左邊, nut:customer應用了最新定義的前綴到customer元素上.
一個被定義了前綴的元素不會為它的後代元素定義默認的命名空間. 在下面的XML片段中, firstname包含一個空的命名空間:
1: <nut:customer nut:xmlns="OReilly.Nutshell.CSharp">
2: <firstname>Joe</firstname>
3: </customer>
為了將OReilly.NutShell.CSharp的前綴給予firstname, 我們必須使用下面的做法:
1: <customer
2: xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
3: xmlns:z="http://schemas.microsoft.com/Serialization/">
4: ...
5: </customer>
我們也可以將命名空間賦值到屬性上, 不同之處在於它總是要求一個前綴. 例如:
1: <customer
2: xmlns:nut="OReilly.Nutshell.CSharp" nut:id="123" />
另一個不同之處在於一個未經修飾的屬性總是包含一個空的命名空間: 其永遠不會從父元素繼承一個默認的命名空間.
在X-DOM當中指定命名空間
有幾種辦法可以用來指定XML的命名空間. 首先是將其包括在local name之前的一個大括號裡面, 如:
1: var e = new XElement
2: ("{http://domain.com/xmlspace}customer", "Bloggs");
3: Console.WriteLine (e.ToString( ));
輸出的結果為:
1: <customer xmlns="http://domain.com/xmlspace">
2: Bloggs
3: </customer>
第二種做法是使用XNamespace和XName, 以下是它們的定義:
1: public sealed class XNamespace
2: {
3: public string NamespaceName { get; }
4: }
5: public sealed class XName
6: {
7: public string LocalName { get; }
8: public XNamespace Namespace { get; } // Optional
9: }
所有的類型定義都是使用string的隱式轉換, 因此下面的代碼是合法的:
1: XNamespace ns = "http://domain.com/xmlspace";
2: XName localName = "customer";
3: XName fullName = "{http://domain.com/xmlspace}customer";
XName還提供了+操作符的重載, 允許你在沒有使用大括號的情況下將命名空間和名稱組合在一起.
1: XNamespace ns = "http://domain.com/xmlspace";
2: XName fullName = ns + "customer";
3: Console.WriteLine (fullName);
4:
5: // RESULT: {http://domain.com/xmlspace}customer
所有在X-DOM當中定義的可以元素或者屬性名的構造器和方法時間上都是接受一個XName對象而不是字符串. 原因就是我們上面例子使用的方法——string的隱式轉換.
指定一個命名空間到元素或者屬性也是一樣的:
1: XNamespace ns = "http://domain.com/xmlspace";
2: var data = new XElement (ns + "data",
3: new XAttribute (ns + "id", 123)
4: );
X-DOM與默認命名空間
X-DOM會一直忽略默認命名空間的觀念指導它被輸出成了XML. 這意味著當你構建一個子XElement, 如果需要的話你必須顯式給予它一個命名空間: 因為它不會從父親元素那裡繼承:
1: XNamespace ns = "http://domain.com/xmlspace";
2: var data = new XElement (ns + "data",
3: new XElement (ns + "customer", "Bloggs"),
4: new XElement (ns + "purchase", "Bicycle")
5: );
然後,當X-DOM讀取或輸出XML的時候, 它將會應用默認的命名空間:
1: Console.WriteLine (data.ToString( ));
2:
3: OUTPUT:
4: <data xmlns="http://domain.com/xmlspace">
5: <customer>Bloggs</customer>
6: <purchase>Bicycle</purchase>
7: </data>
8:
9: Console.WriteLine
10: (data.Element (ns + "customer").ToString( ));
11:
12: OUTPUT:
13: <customer xmlns="http://domain.com/xmlspace">Bloggs
14: </customer>
如果你在構造XElement的子節點是沒有指定命名空間——換句話說:
1: XNamespace ns = "http://domain.com/xmlspace";
2: var data = new XElement (ns + "data",
3: new XElement ("customer", "Bloggs"),
4: new XElement ("purchase", "Bicycle")
5: );
6: Console.WriteLine (data.ToString( ));
你將會得到如下的輸出:
1: <data xmlns="http://domain.com/xmlspace">
2: <customer xmlns="">Bloggs</customer>
3: <purchase xmlns="">Bicycle</purchase>
4: </data>
另一個陷阱可能使你浏覽X-DOM的時候會失敗:
1: XNamespace ns = "http://domain.com/xmlspace";
2: var data = new XElement (ns + "data",
3: new XElement (ns + "customer", "Bloggs"),
4: new XElement (ns + "purchase", "Bicycle")
5: );
6: XElement x = data.Element (ns + "customer"); // ok
7: XElement y = data.Element ("customer"); // null
如果你沒有指定命名空間來構造X-DOM樹, 你可以隨後類似下面這樣將一個單一的命名空間賦值到每一個元素:
1: foreach (XElement e in data.DescendantsAndSelf( ))
2: if (e.Name.Namespace == "")
3: e.Name = ns + e.Name.LocalName;
前綴
X-DOM對待前綴就像它對待命名空間一樣, 主要也是為了序列化功能. 這意味著你可以選擇完全忽略前綴——這是可行的. 唯一的理由是為了輸出XML文件時更加有效. 例如, 考慮如下代碼:
1: XNamespace ns1 = "http://test.com/space1";
2: XNamespace ns2 = "http://test.com/space2";
3:
4: var mix = new XElement (ns1 + "data",
5: new XElement (ns2 + "element", "value"),
6: new XElement (ns2 + "element", "value"),
7: new XElement (ns2 + "element", "value")
8: );
默認情況下, XElement會將其序列化為類似下面:
1: <data xmlns="http://test.com/space1">
2: <element xmlns="http://test.com/space2">value</element>
3: <element xmlns="http://test.com/space2">value</element>
4: <element xmlns="http://test.com/space2">value</element>
5: </data>
正如你所看到的, 這裡有一些不必要的重復. 解決方案不需要去更改X-DOM的構建方式, 只需要在寫XML的時候提示序列化器. 通常我們可以在根元素來做這件事情:
1: mix.SetAttributeValue (XNamespace.Xmlns + "ns1", ns1);
2: mix.SetAttributeValue (XNamespace.Xmlns + "ns2", ns2);
這會將前綴”ns1″賦值給XNamespace變量ns1, “ns2″賦給了ns2. 當序列化的時候X-DOM會自動使用這些屬性去縮短結果XML. 這裡是在mix上調用ToString的結果:
1: <ns1:data xmlns:ns1="http://test.com/space1"
2: xmlns:ns2="http://test.com/space2">
3: <ns2:element>value</ns2:element>
4: <ns2:element>value</ns2:element>
5: <ns2:element>value</ns2:element>
6: </ns1:data>
前綴不會改變你構造, 查詢, 更新X-DOM的方式, 對於這些活動你完全可以忽略前綴並繼續使用全名. 只有當從序列化到文件或者流(反之也一樣)的時候才需要使用到前綴.
前綴對於序列化屬性也是一樣有用的. 在接下來的例子當中, 我們記錄了一個客戶的生日, 並使用W3C標准屬性用於信用卡. 接下來的代碼中注釋的代碼行確保序列化的時候不會有不必要的命名空間被重復:
1: XNamespace xsi =
2: "http://www.w3.org/2001/XMLSchema-instance";
3:
4: var nil = new XAttribute (xsi + "nil", true);
5:
6: var cust =
7: new XElement ("customers",
8: new XAttribute (XNamespace.Xmlns + "xsi", xsi),
9: new XElement ("customer",
10: new XElement ("lastname", "Bloggs"),
11: new XElement ("dob", nil),
12: new XElement ("credit", nil)
13: )
14: );
結果XML:
1: <customers>
2: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3: <customer>
4: <lastname>Bloggs</lastname>
5: <dob xsi:nil="true" />
6: <credit xsi:nil="true" />
7: </customer>
8: </customers>
簡短來說, 我們預先聲明的nil屬性因此可以在構建X-DOM的時候重復使用它兩次. 你可以使用相同的屬性兩次是因為它會象我們要求的那樣自動復制. 待續!