LINQ to XML
DOM是什么?
如下是一个xml文件:
<?xml version="1.0"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name id="123" status="archived">johnyang</Name>
<Age>30</Age>
<address>
<Street>kehua</Street>
<PostCode>1234</PostCode>
</address>
</Person>
就像其他所有的XML文件,开始有一个声明,然后根元素,根元素有两个属性,还有子元素。
所有的这些--声明,元素,属性,值,文字等都可以用一个类来表示。如果这样的类有很多属性来储存子级内容,我们就可以组装树状的一组数据来描述一个文档,即文档对象模型(Document Object Model---DOM)。
LINQ to XML
包含两个东西:
- XML DOM,即称X-DOM
- 大概10多个附加的询问操作符
X-DOM包含了XDocument,XElement,XAttribute,而X-DOM类型并没与LINQ绑定死,可以不用LINQ而完成X-DOM的装载,实例化,更新,保存等。
X-DOM是LINQ友好的,也就是说:它有可以返回IEnumerable序列的方法;可以通过LINQ的Projection来建立一个X-DOM。
X-DOM概述
猛一看上面的架构图,有点懵,但主要反映的是几点:XObject是所有XML对象的抽象类,从图上也可以看到,它定义了Parent的属性,和Document属性。而它的子类有XAttribute
和XNode
,这两个其实具有不同的行为,即在一个元素下的多个特性都是同级的,也只能特性与特性是同级的,而在一个元素可以包含很多不同类型的同级的节点。再往下,XContainer
作为XElement
,XDocuement
的基类,与字面意义一样,它们都是容器类的对象,即都可以包裹其他节点,元素,特性等。而非容器的节点不具备这样的包裹能力。
最常用的类型是XElement
,XObject
是继承类的根部,XElement
和XDocument
是容器类的根部。
XObject
是所有XML内容的抽象类,XNode是除了属性外的绝大多数XML内容的基类,其特点是它可以以一定的顺序来排列混合类型的XNode。
例如:
<data>
Hello world
<subelement1/>
<!--comment-->
<subelement2>
</data>
在父元素<data>
中,第一个节点是XText
,然后是XElement
节点,再然后是XComment
节点,最后是第二个XElement
,与此相反的是,XAttribute
没有这样的嵌套关系。
尽管XNode
可以访问其父元素,但它没有子节点的概念,子节点是XContainer
的子类所属的概念,XContainer
是XElement
和XDocument
的基类。
XElement有Name
和Value
属性,在相当多的情况下,XElement
只有一个XText
子节点,在这种情况下,Value
就可以实现XElement
对子内容的设置和获取,避免了不必要的索引,避免了再与XText
发生直接关系。而其他节点并无这两个属性。
XDocument
代表是XML树的树根,它包裹了XElement
,再加上XDeclaration
。
创建X-DOM的两个方法:Loading 和Parsing
作为XContainer
的两个子类:XElement
和XDocument
提供了两个静态方法:Load
和Parse
来从以下资源创建X-DOM:
- Load从文件,URL,Stream,TextReader,XmlReader来创建X-DOM
- Parse从字符串创建
X-DOM
比如:
public static void Main()
{
var config = XElement.Parse(
@"
<configuration>
<client enabled='true'>
<timeout>30</timeout>
</client>
</configuration>
"
);
foreach(var child in config.Elements())
{
Console.WriteLine(child.Name);
}
var client = config.Element("client");
bool enabled = (bool)client.Attribute("enabled");
Console.WriteLine(enabled);
client.Attribute("enabled").SetValue(!enabled);
int timeout = (int)client.Element("timeout");
Console.WriteLine(timeout);
client.Element("timeout").SetValue(timeout * 2);
client.Add(new XElement("retries", 2));
Console.WriteLine(client);
}
output
:
client
True
30
<client enabled="false">
<timeout>60</timeout>
<retries>2</retries>
</client>
保存与序列化
对任何节点调用ToString
都将它的内容转变为XML字符串,有缩进格式(如果不想要缩进格式,在调用ToString
时候,加上SaveOptions.DisableFormatting
)
XElement
和XDocument
也提供了Save
方法,因此可以将X-DOM写入到文件,Stream,TextWriter,XmlWriter中。如果是文件,那么XML声明将被自动的写入进去。
在XNode
类中,还有一个WriteTo
方法,该方法只接受XmlWriter
。
实例化X-DOM
不用Load
,Parse
,也可以它通过XContainer
的Add
方法手动的实例化X-DOM。
为了创建XElement,XAttribute
,仅仅需要提供name,value
:
public static void Main()
{
var lastName = new XElement("lastname", "Bloggs");
lastName.Add(new XComment("nice name"));
var customer = new XElement("Customer");
customer.Add(new XAttribute("id", 123));
customer.Add(new XElement("firstname", "Joe"));
customer.Add(lastName);
Console.WriteLine(customer);
}
output
<Customer id="123">
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</Customer>
当创建XElement
时候,Value
是可选的,即可以先提供一个name,后面再赋值Value,比如上面例子中的customer
,另外,注意到,简单的字符串就可以作为值赋予XElement
,而不必显式地创建和添加XText
子节点。
函数式地创建(Functional Construction)
在上一个例子,很难直接看出来XML的结构。XML支持另一种创建的模式:函数式创建。
public static void Main()
{
var customer = new XElement("customer",new XAttribute("id",123),
new XElement("firstname","Joe"),
new XElement("lastname","bloggs"),
new XComment("nice name")
);
Console.WriteLine(customer);
}
output:
<customer id="123">
<firstname>Joe</firstname>
<lastname>bloggs</lastname>
<!--nice name-->
</customer>
这有两个好处:其一,可以直接看出来XML的结构,其二,可以从LINQ直接投射为X-DOM
比如:
public class Person { public int ID; public string FirstName; public string LastName; } class Program { public static void Main() { var names = new List<Person> { new Person(){ID=123,FirstName="John",LastName="Yang"}, new Person(){ID=124,FirstName="Tom",LastName="Cruse"}, new Person(){ID=125,FirstName="Daviad",LastName="Trump"} }; var query = new XElement("customers", from c in names select new XElement("customer",new XAttribute("id",c.ID), new XElement("firstname",c.FirstName), new XElement("lastname",c.LastName), new XComment("nice name") ) ); Console.WriteLine(query); } }
output:
<customers> <customer id="123"> <firstname>John</firstname> <lastname>Yang</lastname> <!--nice name--> </customer> <customer id="124"> <firstname>Tom</firstname> <lastname>Cruse</lastname> <!--nice name--> </customer> <customer id="125"> <firstname>Daviad</firstname> <lastname>Trump</lastname> <!--nice name--> </customer></customers>
自动深度复制
当一个节点或者特性被添加到一个元素时(不论是通过函数式方法,或者Add方法),该节点或特性的Parent
属性就被赋值为那个元素,而一个节点只能有一个父元素,如果将一个已有父元素的节点添加到第二个父元素下,那么该节点将自动深度复制。
public static void Main() { var address = new XElement("Address", new XElement("street","TianFuSiJie"), new XElement("town","WenXin") ); var customer1 = new XElement("customer1", address); var customer2 = new XElement("customer2", address); customer1.Element("Address").Element("street").Value = "RenMinNanLu"; Console.WriteLine(customer2); }
output:
<customer2> <Address> <street>TianFuSiJie</street> <town>WenXin</town> </Address></customer2>
可以发现变更customer2的street,并不会影响customer1的street,这正是因为自动深度复制的缘故。
Navigating and Querying
XNode
和XContainer
都定义了能够游历X-DOM树的方法,属性,这些方法,属性要么返回一个值,要么返回实现IEnumerable<T>
的序列。
子节点游历
FirstNode,LastNode,Nodes
public static void Main()
{
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool","Hammer"),
new XElement("handtool","Rasp")
),
new XElement("toolbox",
new XElement("handtool","Saw"),
new XElement("poweetool","Nailgun"))
);
foreach(var node in bench.Nodes())//bench下的所有节点
{
Console.WriteLine(node.ToString());
}
}
output:
<toolbox>
<handtool>Hammer</handtool>
<handtool>Rasp</handtool>
</toolbox>
<toolbox>
<handtool>Saw</handtool>
<poweetool>Nailgun</poweetool>
</toolbox>
得到elements
Elements
方法返回XElement的所有子节点,
public static void Main()
{
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool", "Hammer"),
new XElement("handtool", "Rasp")),
new XElement("toolbox",
new XElement("handtool", "Saw"),
new XElement("powertool", "Nailgun")),
new XAttribute("test",123)
);
foreach (var c in bench.Elements())
Console.WriteLine(c.Name + "=" + c.Value);
var a = from tool in bench.Elements("toolbox").Elements("handtool")
select tool.Value.ToUpper();
var b = a.ToList();
foreach (var ele in a)
Console.WriteLine(ele);
}
output:
toolbox=HammerRasptoolbox=SawNailgunHAMMERRASPSAW
var a = from tool in bench.Elements("toolbox").Elements("handtool") select tool.Value.ToUpper();
第一个Elements方法是XElement
的方法,返回IEnumerable<XContainer>
类型,第二个Elements方法是扩展方法,正好可被IEnumerable<XContainer>
类调用,返回的仍然是IEnumerable<XContainer>
类型。
得到单个Element
Element
方法返回与给定名字一致的首个元素,相当于先调用Elements()
,然后用LINQ的FirstOrDefault
询问方法,如果找不到,则返回null,来表明没找到。
public static void Main() { var bench = new XElement("bench", new XElement("toolbox", new XElement("handtool", "Hammer"), new XElement("handtool", "Rasp")), new XElement("toolbox", new XElement("handtool", "Saw"), new XElement("powertool", "Nailgun")), new XAttribute("test",123) ); var tlb = bench.Element("toolbox"); Console.WriteLine(tlb.Element("handtool").Value); var jhn = bench.Element("johnYang"); Console.WriteLine(jhn == null); }
output:
HammerTrue
得到Descendants
XContainer
提供了Descendants
和DescendantNodes
方法,前者返回所有后代元素(XElement),后者返回所有后代节点,即不包含所有XAttribute的对象。
public static void Main() { var bench = new XElement("bench", new XElement("toolbox", //子后代元素1,子后代节点1 new XElement("handtool", "Hammer"), //子后代元素2,子后代节点2,3(字符串被算为XText) new XElement("handtool", "Rasp"),//子后代元素3,子后代节点4,5 new XComment("testcomment")//子后代节点6 ), new XElement("toolbox",//子后代元素4 ,子后代节点7 new XElement("handtool", "Saw"),//子后代元素5 ,子后代节点8,9 new XElement("powertool", "Nailgun")),//子后代元素6,子后代10,11 new XAttribute("test",123) ); Console.WriteLine(bench.Descendants().Count()); foreach (var des in bench.Descendants()) Console.WriteLine(des.Name); Console.WriteLine(bench.DescendantNodes().Count()); foreach (var node in bench.DescendantNodes()) Console.WriteLine(node); }
output
6
toolbox
handtool
handtool
toolbox
handtool
powertool
11
<toolbox>
<handtool>Hammer</handtool>
<handtool>Rasp</handtool>
<!--testcomment-->
</toolbox>
<handtool>Hammer</handtool>
Hammer
<handtool>Rasp</handtool>
Rasp
<!--testcomment-->
<toolbox>
<handtool>Saw</handtool>
<powertool>Nailgun</powertool>
</toolbox>
<handtool>Saw</handtool>
Saw
<powertool>Nailgun</powertool>
Nailgun
上述打印并无XAttribute
。
下方代码抽取了所有XComment:
public static void Main()
{
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool", "Hammer"),
new XElement("handtool", "Rasp"),
new XComment("testcomment")
),
new XElement("toolbox",
new XElement("handtool", "Saw"),
new XElement("powertool", "Nailgun")),
new XComment("nice name"),
new XAttribute("test",123)
);
Console.WriteLine(bench);
var commentQuery = from c in bench.DescendantNodes().OfType<XComment>()
select c;
Console.WriteLine("-------------------------------------");
foreach (var c in commentQuery)
Console.WriteLine(c);
}
output:
<bench test="123"> <toolbox> <handtool>Hammer</handtool> <handtool>Rasp</handtool> <!--testcomment--> </toolbox> <toolbox> <handtool>Saw</handtool> <powertool>Nailgun</powertool> </toolbox> <!--nice name--></bench>-------------------------------------<!--testcomment--><!--nice name-->
LINQ中的OfType<T>
在筛选特定类型时候,起了作用,与Cast<T>
不同的是,当类型转换失败后,OfType
选择忽略,而Cast
选择报错。
例如:
static void Main(string[] args) { var a=new List<Db> { new DbPt(0,90),new DbLine(new DbPt(0,0),new DbPt(100,100)),new DbPt(9,9)}; var b=from c in a.OfType<DbPt>() select c; foreach(var c in b) Console.WriteLine(c); }
其中,DbPt
和DbLine
的基类均为Db
。
output:
(0,90,0)(9,9,0)
Parent Navigation
所有节点都有Parent
属性,和AncestorXXX
方法,用来游历父元素。
如果x是XElement
下面的打印永远是true:
foreach(var child in x.Nodes){ Console.WriteLine(child.Parent==x);}
如果x是XDocument
就不对了,因为XDocument有点奇怪,它可以有子元素,却不是任何子元素的父元素。为了访问到XDocument
,可以使用Document
属性,而这是X-DOM中任何对象都有的属性,因为XObject
就定义了它。
Ancestors
返回一个序列,即它的父元素,父元素的父元素,等等,直到根部元素。
public static void Main()
{
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool","Hammer"),
new XElement("handtool","Rasp")
),
new XElement("toolbox",
new XElement("handtool","Saw"),
new XElement("poweetool","Nailgun"))
);
var a = bench.Element("toolbox").Element("handtool");
var b = a.Ancestors();
foreach (var c in b)
Console.WriteLine(c.Name);
}
output:
toolbox
bench
可以通过AncestorsAndSelf().Last()
获取根元素,另一种方法是Document.Root
,但前提是XDocument
必须存在。
Navigating Peer Node,Update X-DOM
public static void Main()
{
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool","Hammer"),
new XElement("handtool","Rasp"),
new XComment("nice name")
),
new XElement("toolbox",
new XElement("handtool","Saw",new XAttribute("Test","Yang")),
new XElement("poweetool","Nailgun"),
new XAttribute("pd","John")
),
new XAttribute("id",123)
);
Console.WriteLine("显示bench");
Console.WriteLine(bench);
Console.WriteLine("bench后代节点数量:");
Console.WriteLine(bench.DescendantNodes().Count());
var a = bench.Elements().Elements().Last(); //最后一个元素
Console.WriteLine("bench最后一个元素");
Console.WriteLine(a);
var b = a.PreviousNode;//最后一个元素的前一个节点,即Saw元素
Console.WriteLine("倒数第二个元素b");
Console.WriteLine(b);
Console.WriteLine("b是否在a之前");
Console.WriteLine(b.IsBefore(a));
var c = b.ElementsBeforeSelf();
Console.WriteLine("在b之前的元素的个数");
Console.WriteLine(c.Count());
Console.WriteLine("b是否有特性");
Console.WriteLine(((XElement)b).HasAttributes);//是否有特性
var bb = (XElement)b;
bb.SetValue("Not Only Saw But Also Do it");
Console.WriteLine("b修改value值后");
Console.WriteLine(b);
Console.WriteLine("b添加子节点timeout");
bb.SetElementValue("timeout", 30);
Console.WriteLine(bb);
Console.WriteLine("b更新子节点timeout");
bb.SetElementValue("timeout", 60);
Console.WriteLine(bb);
bb.ReplaceWith(new XComment("replaced"));
Console.WriteLine(bb);
var cc = bb.Elements();
Console.WriteLine("去除b元素的子元素");
cc.Remove();//只能是nodes或者attributes的序列调用Remove
Console.WriteLine(bb);
Console.WriteLine("bench去除所有XComment");
bench.DescendantNodes().OfType<XComment>().Remove();
Console.WriteLine(bench);
}
output
显示bench<bench id="123"> <toolbox> <handtool>Hammer</handtool> <handtool>Rasp</handtool> <!--nice name--> </toolbox> <toolbox pd="John"> <handtool Test="Yang">Saw</handtool> <poweetool>Nailgun</poweetool> </toolbox></bench>bench后代节点数量:11bench最后一个元素<poweetool>Nailgun</poweetool>倒数第二个元素b<handtool Test="Yang">Saw</handtool>b是否在a之前True在b之前的元素的个数0b是否有特性Trueb修改value值后<handtool Test="Yang">Not Only Saw But Also Do it</handtool>b添加子节点timeout<handtool Test="Yang">Not Only Saw But Also Do it<timeout>30</timeout></handtool>b更新子节点timeout<handtool Test="Yang">Not Only Saw But Also Do it<timeout>60</timeout></handtool><handtool Test="Yang">Not Only Saw But Also Do it<timeout>60</timeout></handtool>去除b元素的子元素<handtool Test="Yang">Not Only Saw But Also Do it</handtool>bench去除所有XComment<bench id="123"> <toolbox> <handtool>Hammer</handtool> <handtool>Rasp</handtool> </toolbox> <toolbox pd="John"> <poweetool>Nailgun</poweetool> </toolbox></bench>
Values
XElement
和XAttribute
都有Value
属性,该属性为string
类型。如果一个元素只有一个单独的XText
子节点,XElement
的Value
属性就直接是获取该XText
子节点的内容。
设定值
有两种方法来赋值:(1)调用SetValue
(2)直接对Value
属性进行赋值。
SetValue
更灵活,因为它不仅接受字符串,而且还可以接受简单的数据类型,比如:
public static void Main()
{
var e = new XElement("data", DateTime.Now);
e.SetValue(DateTime.Now.AddDays(1));
Console.WriteLine(e);
}
output:
<data>2022-01-15T23:02:51.5792172+08:00</data>
而直接对Value进行赋值,则必须先将Datetime转换为字符串,才可以赋值。
得到值
可仅仅将XElement
转换为所需要的类型,就可以获取值。
public static void Main()
{
var e = new XElement("date", DateTime.Now);
string b = e.Value;
var d = new XAttribute("resolution", 1.234);
var c = (DateTime)e;//获取值,只需要直接转换类型即可
var f = (double)d;
Console.WriteLine(f);
Console.WriteLine(c);
}
output:
1.234
2022/1/14 23:10:22
元素和特性并没有储存DateTimes
或者数字,DateTimes
或数字是被储存为文字,它也不记得之前的类型,所以就需要正确的转换类型,来防止运行时错误。考虑到健壮性,可以加一层try/catch
来捕捉FormatException
异常。
XElement
和XAttribute
可以直接转换为的类型,如下:
- 所有标准数值类型
- string,bool,DateTime,DateTimeOffset,TimeSpan,Guid
- Nullable<>类型
结合XElement
,Attribute
方法,转换为可空类型是非常有用的,也因为如果转换的元素不存在,整个转换过程仍然工作。
public static void Main()
{
var e = new XElement("date", DateTime.Now);
int? timeout = (int?)e.Element("timeout");
Console.WriteLine(timeout == null);
}
output
true
还可以用空接符
public static void Main() { var e = new XElement("date", DateTime.Now); int? timeout = (int?)e.Element("timeout")?? 100; Console.WriteLine(timeout); }
output
100
public static void Main() { int? a = null; Console.WriteLine(a > 10); }
output
false
public static void Main() { var summary = new XElement("summary", new XText("An XAttribute is "), new XElement("bold","not"), new XText(" an XNode")); var text = (string)summary; var text0 = summary.Value; Console.WriteLine(text); Console.WriteLine(text0); }
output:
An XAttribute is not an XNodeAn XAttribute is not an XNode
XText自动联结
当给XElement添加内容时,X-DOM自动的在现有的XText之后添加内容,而不是覆盖它。
public static void Main()
{
var e1 = new XElement("test", "Hello");e1.Add("world");
var e2 = new XElement("test", "Hello", "world");
Console.WriteLine(e1);
Console.WriteLine(e2);
}
output:
<test>Helloworld</test>
<test>Helloworld</test>
Documents 和Declarations
XDocument
包裹着根XElement
,允许添加XDeclaration
,处理指令,文档类型,根级别的元素。XDocument
是可选的,可以被忽略。
因为XDocument
也是XContainer
的子类,所以它也支持AddXXX,RemoveXXX,ReplaceXXX
等方法。而与XElement不同的是,XDocument
仅能接受如下内容:
- 单个
XElement
对象(根部) - 单个
XDeclaration
对象 - 单个
XDocumentType
对象 - 任意数量的
XProcessingInstruction
对象 - 任意数量的
XComment
对象
以上,只有根元素是强制性的,Declaration
是可选的。
public static void Main()
{
var doc = new XDocument(
new XElement("test","this is the root")
);
Console.WriteLine(doc);
doc.Save(@"C:\Users\PC\Desktop\test.xml");
}
output:
<test>this is the root</test>
生成的xml:
<?xml version="1.0" encoding="utf-8"?><test>this is the root</test>
调用doc.Save
方法,将仍然会包含一个XML声明,但它是默认生成的。
public static void Main() { var styleInstruction = new XProcessingInstruction( "xml-stylesheet","href='style.css' type='text/css'" ); var docType = new XDocumentType("html", "-//W3C//DTD XHTML 1.0 Strtict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",null ); XNamespace ns = "http://www.w3.oorg/1999/xhtml"; var root = new XElement(ns+"html", new XElement(ns+"head", new XElement(ns+"title","An XHTML Page")), new XElement(ns+"body", new XElement(ns+"p","This is the content")) ); var doc = new XDocument( new XDeclaration("1.0","utf-8","no"), new XComment("Reference a stylesheet"), styleInstruction, docType, root ); doc.Save(@"C:\Users\PC\Desktop\test.xml"); }
生成的xml
<?xml version="1.0" encoding="utf-8" standalone="no"?><!--Reference a stylesheet--><?xml-stylesheet href='style.css' type='text/css'?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strtict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.oorg/1999/xhtml"> <head> <title>An XHTML Page</title> </head> <body> <p>This is the content</p> </body></html>
XML声明
一个标准的XML文件以XML声明开头,如下:
<?xml versioon="1.0" encoding="utf-8" standalong="yes"?>
XML声明确保了文件会被正确的parse,被reader理解。XElement
和XDocument
遵循下面的规则:
-
用文件名调用
Save
总是会写入一个声明 -
用
XmlWriter
调用Save
会写入声明,除非XmlWriter
被指明。可以指定XmlWriter不产生声明,通过设定XmlWriterSettings对象的的OmitXmlDeclaration属性和ConformanceLevel属性。 -
ToString
永远不会写入声明XDeclaration
的目的是给XML序列化提供线索:- 什么text encoding会使用
- XML声明的
encoding
和standalone
特性是什么。
将声明写入字符串
假定我们想把
XDocument
序列化为一个字符串,包括声明,但是因为ToString
不写入声明,我们就不得不使用XmlWriter
了:public static void Main() { var doc = new XDocument( new XDeclaration("1.0","utf-8","yes"), new XElement("test","data") ); Console.WriteLine(doc); var output = new StringBuilder(); var settings = new XmlWriterSettings { Indent = true }; using(var xw = XmlWriter.Create(output, settings)) { doc.Save(xw); } Console.WriteLine(output.ToString()); }
output:
<test>data</test> <?xml version="1.0" encoding="utf-16" standalone="yes"?> <test>data</test>
名称与命名空间
正如.NET类型可以有命名空间,XML元素与特性也可以有命名空间。
XML命名空间有两个作用:其一,与C#中的命名空间一样,都是为了防止命名冲突,当将一个XML文件与另一个融合的时候,防止命名冲突。其二,命名空间赋予了名称特殊的含义,比如,在http://www.w3.org/2001/xmlschema-instance
命名空间中,nil
意味着是C#中的null
。
XML中的命名空间
设想,我们想定义一个customer
在命名空间JohnYang.Nutshell.CSharp
中,这里有两种方法来处理:第一种是用xmlns
特性,如下:
<customer xmlns="JohnYang.Nutshell.CSharp"/>
以这样的方法,它实际上有以下两个作用:
- 它为元素标明了命名空间
- 它为该元素的后代元素标明了命名空间
Prefixes
另一种标明命名空间的方法是Prefixes(前缀),所谓prefix就是将命名空间赋值一个别名。通常有两个步骤,第一是定义一个prefix,第二个是使用这个prefix,当然也可以同时完成这两个事。
比如:
<nut:customer xmlns:nut="JohnYang.Nutshell.CSharp">
就同时完成了这两个事,其中xmlns:nut=...
就是将该命名空间赋值为nut,存在customer
元素中,而nut:customer
就是声明该customer就是在nut命名空间中,如果没有nut:customer
,那么该customer只负责储存nut的命名空间,自己的命名空间为空,另外,如果对customer的后代元素来讲,如果不指明nut,那么这些后代元素的命名空间也为空,这点与上一个方法不一样。
<nut:customer xmlns:nut="JohnYang.Nutshell.CSharp" >
<firstName>Joe</firstName>
</customer>
这里,Joe
的命名空间就是空。
而
<nut:customer xmlns:nut="JohnYang.Nutshell.CSharp" >
<nut:firstName>Joe</firstName>
</customer>
才标明Joe
在nut命名空间中。
特性(Attributes)
也可以将命名空间赋予特性。
<customer xmlns:nut="JohnYang.Nutshell.CSharp" nut:id="123"/>
特性往往倾向于需要一个命名空间,因为它们对于元素来讲,经常是局部性的。对于元数据,或者通用功能的的特性,可以不需要命名空间。
在X-DOM中标明命名空间
第一个方法是,在本地名称前加括号,将命名空间括起来:
var e=new XElement("{http://domain.com/xmlspace}customer","Bloggs");
Console.WriteLine(e);
output:
<customer xmlns="http://domain.com/xmlspace">Bloggs</customer>
第二个方法是用XNamespace
,XName
,这两个类型都定义了从字符串到 这两个类型的隐式转换,所以可以这样写:
XNamespace ns="htttp://doomain.com/xmlspace";
XName localName="customer";
XName fullName="{http://domain.com/xmlspace}custoer";
XNamespace
还重载了+
操作符,允许结合命名空间和名字到XName
而不用括号。
XNamespace ns = "http://doomain.com/xmlspace";
XName fullName = ns + "customer";
Console.WriteLine(fullName);
output
{http://doomain.com/xmlspace}customer
public static void Main()
{
XNamespace ns = "http://doomain.com/xmlspace";
var data = new XElement(
ns+"data",new XAttribute(ns+"id",123)
);
Console.WriteLine(data);
}
output:
<data p1:id="123" xmlns:p1="http://doomain.com/xmlspace" xmlns="http://doomain.com/xmlspace" />
X-DOM和默认命名空间
当输出XML时,X-DOM会忽略默认命名空间的概念,这就意味着当创建子元素时候,必须显式给出它的命名空间。
public static void Main()
{
XNamespace ns = "http://doomain.com/xmlspace";
var data = new XElement(
ns+"data",new XAttribute(ns+"id",123),
new XElement(ns+"customer","Bloggs"),
new XElement(ns+"purchase","Bicycle")
);
Console.WriteLine(data.ToString());
}
output:
<data p1:id="123" xmlns:p1="http://doomain.com/xmlspace" xmlns="http://doomain.com/xmlspace">
<p1:customer>Bloggs</p1:customer>
<p1:purchase>Bicycle</p1:purchase>
</data>
public static void Main()
{
XNamespace ns = "http://doomain.com/xmlspace";
var data = new XElement(
ns+"data",new XAttribute(ns+"id",123),
new XElement("customer","Bloggs"),
new XElement("purchase","Bicycle")
);
Console.WriteLine(data.ToString());
}
output:
<data p1:id="123" xmlns:p1="http://doomain.com/xmlspace" xmlns="http://doomain.com/xmlspace">
<customer xmlns="">Bloggs</customer>
<purchase xmlns="">Bicycle</purchase>
</data>
当游历X-DOM,需要注意其命名空间:
public static void Main()
{
XNamespace ns = "http://doomain.com/xmlspace";
var data = new XElement(
ns+"data",new XAttribute(ns+"id",123),
new XElement(ns+"customer","Bloggs"),
new XElement(ns+"purchase","Bicycle")
);
var x = data.Element(ns + "customer");
var y = data.Element("customer");
Console.WriteLine(x == null);
Console.WriteLine(y == null);
}
output:
False
True
Prefixes
public static void Main()
{
XNamespace ns1 = "http://domain.com/space1";
XNamespace ns2 = "http://domain.com/space2";
var mix = new XElement(ns1 + "data",
new XElement(ns2+"element","value"),
new XElement(ns2+"element","value"),
new XElement(ns2+"element","value")
);
Console.WriteLine(mix);
}
output:
<data xmlns="http://domain.com/space1">
<element xmlns="http://domain.com/space2">value</element>
<element xmlns="http://domain.com/space2">value</element>
<element xmlns="http://domain.com/space2">value</element>
</data>
可以看到,命名空间重复了很多次。
public static void Main()
{
XNamespace ns1 = "http://domain.com/space1";
XNamespace ns2 = "http://domain.com/space2";
var mix = new XElement(ns1 + "data",
new XElement(ns2+"element","value"),
new XElement(ns2+"element","value"),
new XElement(ns2+"element","value")
);
mix.SetAttributeValue(XNamespace.Xmlns + "ns1", ns1);
mix.SetAttributeValue(XNamespace.Xmlns + "ns2", ns2);
Console.WriteLine(mix);
}
output
<ns1:data xmlns:ns1="http://domain.com/space1" xmlns:ns2="http://domain.com/space2">
<ns2:element>value</ns2:element>
<ns2:element>value</ns2:element>
<ns2:element>value</ns2:element>
</ns1:data>
投射为X-DOM
之前,我们一直用LINQ来从X-DOM获取数据,我们当然也可以用LINQ来将数据投射为X-DOM。
不管数据源是什么,用LINQ来投射为X-DOM的策略都一样:首先用函数式的方式创建X-DOM基本结构,然后用LINQ来填充数据。
public class Person
{
public int ID;
public string FirstName;
public string LastName;
}
class Program
{
public static void Main()
{
var persons = new List<Person>
{
new Person{ID=0,FirstName="John",LastName="Yang"},
new Person{ID=1,FirstName="Tom",LastName="Croose"},
new Person{ID=2,FirstName="Bacon",LastName="Smith"},
new Person{ID=3,FirstName="Sithen",LastName="Trump"}
};
var personsXdom = new XElement("persons",
from p in persons
select
new XElement("person",new XAttribute("id",p.ID),
new XElement("firstName",p.FirstName),
new XElement("lastName",p.LastName)
)
);
Console.WriteLine(personsXdom);
}
output:
<persons>
<person id="0">
<firstName>John</firstName>
<lastName>Yang</lastName>
</person>
<person id="1">
<firstName>Tom</firstName>
<lastName>Croose</lastName>
</person>
<person id="2">
<firstName>Bacon</firstName>
<lastName>Smith</lastName>
</person>
<person id="3">
<firstName>Sithen</firstName>
<lastName>Trump</lastName>
</person>
</persons>
消除空元素
如果要添加更多细节数据,比如
public class Person
{
public int ID;
public string FirstName;
public string LastName;
public List<Goods> goods;
}
public class Goods
{
public double price;
public string description;
}
class Program
{
public static void Main()
{
var persons = new List<Person>
{
new Person{ID=0,FirstName="John",LastName="Yang",goods=new List<Goods>{new Goods{ price=500,description="clothes"} } },
new Person{ID=1,FirstName="Tom",LastName="Croose",goods=new List<Goods>{new Goods{ price=1500,description="perfumer"} } },
new Person{ID=2,FirstName="Bacon",LastName="Smith",goods=new List<Goods>{new Goods{ price=1000,description="food"}} },
new Person{ID=3,FirstName="Sithen",LastName="Trump",goods=new List<Goods>{new Goods{ price=500,description="toy"}} }
};
var personsXdom = new XElement("persons",
from p in persons
let bigBuy=(from c in p.goods
where c.price>=1000
select c).FirstOrDefault()
select
new XElement("person",new XAttribute("id",p.ID),
new XElement("firstName",p.FirstName),
new XElement("lastName",p.LastName),
new XElement("bigBuy",
new XElement("description",bigBuy?.description),
new XElement("price",bigBuy?.price)
)
)
);
Console.WriteLine(personsXdom);
}
output
</person>
<person id="1">
<firstName>Tom</firstName>
<lastName>Croose</lastName>
<bigBuy>
<description>perfumer</description>
<price>1500</price>
</bigBuy>
</person>
<person id="2">
<firstName>Bacon</firstName>
<lastName>Smith</lastName>
<bigBuy>
<description>food</description>
<price>1000</price>
</bigBuy>
</person>
<person id="3">
<firstName>Sithen</firstName>
<lastName>Trump</lastName>
<bigBuy>
<description />
<price />
</bigBuy>
</person>
</persons>
bigBuy
使用了FirstOrDefault
,当不满足要求时,使用默认值,对于引用类型,其默认值为null,因此在构造XElement
时候使用了?.
语法糖,即当调用者不为null时,调用方法,避免了NullReferenceException
,但是,也因此形成了空元素!
解决方法:
在XElement构造函数中,添加判断条件bigBuy==null?:
public class Person
{
public int ID;
public string FirstName;
public string LastName;
public List<Goods> goods;
}
public class Goods
{
public double price;
public string description;
}
class Program
{
public static void Main()
{
var persons = new List<Person>
{
new Person{ID=0,FirstName="John",LastName="Yang",goods=new List<Goods>{new Goods{ price=500,description="clothes"} } },
new Person{ID=1,FirstName="Tom",LastName="Croose",goods=new List<Goods>{new Goods{ price=1500,description="perfumer"} } },
new Person{ID=2,FirstName="Bacon",LastName="Smith",goods=new List<Goods>{new Goods{ price=1000,description="food"}} },
new Person{ID=3,FirstName="Sithen",LastName="Trump",goods=new List<Goods>{new Goods{ price=500,description="toy"}} }
};
var personsXdom = new XElement("persons",
from p in persons
let bigBuy=(from c in p.goods
where c.price>=1000
select c).FirstOrDefault()
select
new XElement("person",new XAttribute("id",p.ID),
new XElement("firstName",p.FirstName),
new XElement("lastName",p.LastName),
bigBuy==null?null: //添加该条件
new XElement("bigBuy",
new XElement("description",bigBuy?.description),
new XElement("price",bigBuy?.price)
)
)
);
Console.WriteLine(personsXdom);
}
output:
<persons>
<person id="0">
<firstName>John</firstName>
<lastName>Yang</lastName>
</person>
<person id="1">
<firstName>Tom</firstName>
<lastName>Croose</lastName>
<bigBuy>
<description>perfumer</description>
<price>1500</price>
</bigBuy>
</person>
<person id="2">
<firstName>Bacon</firstName>
<lastName>Smith</lastName>
<bigBuy>
<description>food</description>
<price>1000</price>
</bigBuy>
</person>
<person id="3">
<firstName>Sithen</firstName>
<lastName>Trump</lastName>
</person>
</persons>
Streaming a Projection
如果投射为X-DOM仅仅是保存它,那么可以通过XStreamingElement
来提高内存效率,仅仅用它来替换上面代码中的XElement
就可以了。