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属性。而它的子类有XAttributeXNode,这两个其实具有不同的行为,即在一个元素下的多个特性都是同级的,也只能特性与特性是同级的,而在一个元素可以包含很多不同类型的同级的节点。再往下,XContainer作为XElementXDocuement的基类,与字面意义一样,它们都是容器类的对象,即都可以包裹其他节点,元素,特性等。而非容器的节点不具备这样的包裹能力。

最常用的类型是XElementXObject是继承类的根部,XElementXDocument是容器类的根部。

XObject是所有XML内容的抽象类,XNode是除了属性外的绝大多数XML内容的基类,其特点是它可以以一定的顺序来排列混合类型的XNode。

例如:

<data>
    Hello world
    <subelement1/>
        <!--comment-->
    <subelement2>
</data>

在父元素<data>中,第一个节点是XText,然后是XElement节点,再然后是XComment节点,最后是第二个XElement,与此相反的是,XAttribute没有这样的嵌套关系。

尽管XNode可以访问其父元素,但它没有子节点的概念,子节点是XContainer的子类所属的概念,XContainerXElementXDocument的基类。

XElement有NameValue属性,在相当多的情况下,XElement只有一个XText子节点,在这种情况下,Value就可以实现XElement对子内容的设置和获取,避免了不必要的索引,避免了再与XText发生直接关系。而其他节点并无这两个属性。

XDocument代表是XML树的树根,它包裹了XElement,再加上XDeclaration

创建X-DOM的两个方法:Loading 和Parsing

作为XContainer的两个子类:XElementXDocument提供了两个静态方法:LoadParse来从以下资源创建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

XElementXDocument也提供了Save方法,因此可以将X-DOM写入到文件,Stream,TextWriter,XmlWriter中。如果是文件,那么XML声明将被自动的写入进去。

XNode类中,还有一个WriteTo方法,该方法只接受XmlWriter

实例化X-DOM

不用Load,Parse,也可以它通过XContainerAdd方法手动的实例化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,这正是因为自动深度复制的缘故。

XNodeXContainer都定义了能够游历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提供了DescendantsDescendantNodes方法,前者返回所有后代元素(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);        }

其中,DbPtDbLine的基类均为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必须存在。

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

XElementXAttribute都有Value属性,该属性为string类型。如果一个元素只有一个单独的XText子节点,XElementValue属性就直接是获取该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异常。

XElementXAttribute可以直接转换为的类型,如下:

  • 所有标准数值类型
  • 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理解。XElementXDocument遵循下面的规则:

  • 用文件名调用Save总是会写入一个声明

  • XmlWriter调用Save会写入声明,除非XmlWriter被指明。可以指定XmlWriter不产生声明,通过设定XmlWriterSettings对象的的OmitXmlDeclaration属性和ConformanceLevel属性。

  • ToString永远不会写入声明

    XDeclaration的目的是给XML序列化提供线索:

    • 什么text encoding会使用
    • XML声明的encodingstandalone特性是什么。
    将声明写入字符串

    假定我们想把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就可以了。

posted @ 2022-01-13 00:03  JohnYang819  阅读(55)  评论(0编辑  收藏  举报