zoukankan      html  css  js  c++  java
  • 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就可以了。

    ##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####
  • 相关阅读:
    BlockingQueue(阻塞队列)详解
    支付宝系统架构(内部架构图)
    微博的消息队列
    JVM源码分析之堆外内存完全解读
    滑动冲突的补充——Event的流程走向
    BaseFragment的定义—所有Fragment的父类
    BaseActivity的定义——作为所有Activity类的父类
    BGARefreshLayout-Android-master的简单使用
    分析BGARefreshLayout-master
    简便数据库——ORMLite框架
  • 原文地址:https://www.cnblogs.com/johnyang/p/15796003.html
Copyright © 2011-2022 走看看