LINQ学习笔记:XML命名空间
名称与命名空间
与.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的时候重复使用它两次. 你可以使用相同的属性两次是因为它会象我们要求的那样自动复制. 待续!