zoukankan      html  css  js  c++  java
  • C#学习之Linq to Xml

    前言

    我相信很多从事.NET开发的,在.NET 3.5之前操作XML会比较麻烦,但是在此之后出现了Linq to Xml,而今天的主人公就是Linq to Xml,废话不多说,直接进入主题。

    题外:最近由于身体原因去医院,耽误了不少时间,不然这篇随笔可能早就完成了。

    实例项目下载 

    目录: 

    生成xml

    创建简单的xml

    创建注释

    根据对象创建xml

    创建属性

    创建命名空间

    查询并修改xml

    通过文件读取xml

    在指定节点前后添加新节点

    添加属性到节点中

    添加注释到指定节点前后

    替换指定节点

    删除指定属性

    删除指定节点

     按节点关系查询

    显示指定节点的所有父节点

    显示指定节点的所有子节点

    显示同级节点之前的节点

    显示同级节点之后的节点

    监听xml事件

    处理xml流

    一、生成Xml

    为了能够在结构有一定的组织,笔者建议大家新建一个控制台项目,并且新建一个CreateXml(以下部分都属于该类中)

    并在其中写入以下属性:

    1         public static String Path
    2         {
    3             get
    4             {
    5                 String path = String.Format("{0}\test.xml", Environment.CurrentDirectory);
    6                 return path;
    7             }
    8         }

    这句代码很好理解,就是为了下面我们示例的时候可以将xml保存到当前程序的运行路径下。

    以下的示例中不会包含Main方法中的写法,因为Main中仅仅只要调用该静态方法即可。

    1.创建简单的Xml

    首先我们先练练手,创建一个简单的Xml并保存到一个文件中。

    代码如下:

     1 /// <summary>
     2 /// 创建简单的xml并保存
     3 /// </summary>
     4 public static void CreateElement()
     5 {
     6 XDocument xdoc = new XDocument(
     7 new XDeclaration("1.0", "utf-8", "yes"),
     8 new XElement("root",
     9 new XElement("item", "1"),
    10 new XElement("item", "2")
    11 ));
    12 xdoc.Save(Path);
    13 }

    很多学习过XML的人可以从结构就能够猜测出最终的xml的组织,而这也是linq to xml的优点之一。这句代码首先创建一个xml文档,并设置该xml的版本为1.0,

    采用utf-8编码,后面的yes表示该xml是独立的。下面就开始创建每个节点的,首先是Root节点,然后在Root节点中添加两个Item节点。

    最终生成的Xml如下所示:

    1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    2 <root>
    3   <item>1</item>
    4   <item>2</item>
    5 </root>

    2.创建注释

    xml有很多项时,我们就需要利用注释加以区别,通过linq to xml我们一样可以在其中添加注释。

    比如下面这段代码:

     1         /// <summary>
     2         /// 创建注释
     3         /// </summary>
     4         public static void CreateComment()
     5         {
     6             XDocument doc = new XDocument(
     7                 new XDeclaration("1.0", "utf-8", "yes"),
     8                 new XComment("提示"),
     9                 new XElement("item", "asd")
    10                 );
    11             doc.Save(Path);
    12         }

    这里我们直接在版本信息的后面添加了一条注释。

    最终的结果如下所示:

    1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    2 <!--提示-->
    3 <item>asd</item>

    3.根据对象创建xml

    很多时候我们都会将数组之类的类型转换成xml以便保存进永久性存储介质中,所以下面我们也简单的举了一个例子,将数组转换成xml

    代码如下所示:

     1         /// <summary>
     2         /// 根据对象创建xml并保存
     3         /// </summary>
     4         public static void CreateElementByObjects()
     5         {
     6             var s = Enumerable.Range(1, 10);
     7             XElement xele = new XElement(
     8                 "Root",
     9                 from item in s
    10                 select new XElement("item", item.ToString())
    11                 );
    12             xele.Save(Path);
    13         }

    一开始的代码 var s = Enumerable.Radge(1,10)是从1开始递增,生成含有10项的数组,以便后面我们进行添加,有了这个数组之后,

    我们通过简单的linq语句将数组转换成xml,添加到Root中。

    保存之后的结果如下:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <Root>
     3   <item>1</item>
     4   <item>2</item>
     5   <item>3</item>
     6   <item>4</item>
     7   <item>5</item>
     8   <item>6</item>
     9   <item>7</item>
    10   <item>8</item>
    11   <item>9</item>
    12   <item>10</item>
    13 </Root>

    4.创建属性

    有时我们不想创建新的子项去保存数据,而是使用属性的方式去保存。理所应当,linq to xml一样也支持这个功能,下面我们可以通过简单的语句去实现它。

    代码如下所示:

     1         /// <summary>
     2         /// 创建属性
     3         /// </summary>
     4         public static void CreteAttribute()
     5         {
     6             XAttribute xa = new XAttribute("V2", "2");
     7             XElement xele = new XElement(
     8                 "Root",
     9                 new XElement("Item",
    10                     new XAttribute("V1", "1"),
    11                     xa
    12                     ));
    13             xele.Save(Path);
    14         }

    我们依然可以看到熟悉的语法,这里我们利用了XAttribute去创建一个属性,并添加到XElement中。

    最终的结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <Item V1="1" V2="2" />
    4 </Root>

    5.创建命名空间

    对于一些企业级的xml格式,会非常的严格。特别是在同一个xml中可能会出现重复的项,但是我们又想区分开来,这个时候我们可以利用命名空间将他们分开(跟C#中的命名空间类似。)。

    下面是创建命名空间的示例:

     1         /// <summary>
     2         /// 创建命名空间
     3         /// </summary>
     4         public static void CreateNamespace()
     5         {
     6             XElement xele = new XElement("{http://www.xamarin-cn.com}Root",
     7                 new XElement("Item", "1"),
     8                 new XElement("{http://www.baidu.com}Item", 2));
     9             xele.Save(Path);
    10         }

    结果如下所示:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root xmlns="http://www.xamarin-cn.com">
    3   <Item xmlns="">1</Item>
    4   <Item xmlns="http://www.baidu.com">2</Item>
    5 </Root>

    从这个结果中我们可以看到对应的属性中有了xmlns属性,并且值就是我们赋给它的命名空间。

    二、查询并修改Xml

    Linq to xml不仅仅是创建xml简单,在查询,编辑和删除方面一样是非常方便的。下面我们就会介绍这些。

    首先我们创建一个QueryXml类,并在其中写入如下的属性

    1         public static String Path
    2         {
    3             get
    4             {
    5                 String path = String.Format("{0}\test1.xml", Environment.CurrentDirectory);
    6                 return path;
    7             }
    8         }

    同时在该路径下新建一个test1.xml文件,并在其中写入如下内容

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <Item v1="1" v2="2">Item1</Item>
    4   <Item v1="1" v2="2" >Item2</Item>
    5 </Root>

    下面我们就可以正式开始了。

    1.通过文件读取xml

    既然我们要对xml查询就需要读取对应的xml文件,当然后面会介绍其他的方式。

    代码如下:

     1         /// <summary>
     2         /// 通过文件读取xml
     3         /// </summary>
     4         public static void QueryElementByFile()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             XElement xele1 = xele.Element("Item");
     8             Console.Write(xele1.Value.Trim());
     9             Console.ReadKey();
    10         }

    我们可以利用XElement的静态方法Load读取指定路径下的xml文件,这里我们不仅读取了该xml文件,同时还获取的该xml的第一个item的值并输出。

    所以我们可以看到如下的结果:

    2.在指定节点前后添加新节点

    上面我们仅仅只是读取xml以及简单的查询,下面我们不仅仅查询并且还要在该节点前后插入新的节点。

    代码如下:

     1         /// <summary>
     2         /// 在指定节点前后添加新节点
     3         /// </summary>
     4         public static void AddToElementAfterAndBefore()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Elements("Item")
     8                         where ele.Value.Equals("Item2")
     9                         select ele).SingleOrDefault();
    10             if (item != null)
    11             {
    12                 XElement nele = new XElement("NItem", "NItem");
    13                 XElement nele2 = new XElement("BItem", "BItem");
    14                 item.AddAfterSelf(nele);
    15                 item.AddBeforeSelf(nele2);
    16                 xele.Save(Path);
    17             }
    18         }

    我们简单的分析一下上面的代码,首先我们利用linq从中查询Item的值为Item2的节点,然后获取其中第一个节点,然后通过AddAfterSelfAddBeforeSelf在该节点的后面和前面分别添加新的节点。

    添加完之后的xml结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <Item v1="1" v2="2">Item1</Item>
    4   <BItem>BItem</BItem>
    5   <Item v1="1" v2="2">Item2</Item>
    6   <NItem>NItem</NItem>
    7 </Root>

    3.添加属性到节点中

    我们已经可以动态的添加节点,但是创建的时候不仅仅可以创建节点,并且还能创建属性,下面我们可以通过SetAttributeValue去添加新的属性或者修改现有属性。

    代码如下:

     1         /// <summary>
     2         /// 添加属性到节点中
     3         /// </summary>
     4         public static void AddAttributeToEle()
     5         {
     6             XElement xele = XElement.Parse(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注释-->
     7 <Item v1='1' v2='2'>Item1</Item><!--后面的注释--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
     8             var item = (from ele in xele.Elements("Item")
     9                         where ele.Value.Equals("Item2")
    10                         select ele).SingleOrDefault();
    11             item.SetAttributeValue("v3", "3");
    12             xele.Save(Path);
    13         }

    我们可以明显的看出,这里我们已经不是使用XElement.Load去读取xml文件,而是通过直接读取xml字符串。接着我们还是跟上面一样去查询,然后通过SetAttributeValue添加了新的属性,并保存。

    Xml内容如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <!--前面的注释-->
    4   <Item v1="1" v2="2">Item1</Item>
    5   <!--后面的注释-->
    6   <Item v1="1" v2="2" v3="3">Item2</Item>
    7 </Root>

    我们可以看到第二个Item中多了一个 v3=”3” 新的属性。

    4.添加注释到指定节点前后

    这里的语法基本跟添加节点到指定节点前后是相似的,只是读取xml的方式不同。

    代码如下:

     1         /// <summary>
     2         /// 添加注释到节点前后
     3         /// </summary>
     4         public static void AddCommentToAfterAndBefore()
     5         {
     6             TextReader tr = new StringReader(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注释-->
     7 <Item v1='1' v2='2'>Item1</Item><!--后面的注释--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
     8             XElement xele = XElement.Load(tr);
     9             var item = (from ele in xele.Elements("Item")
    10                         where ele.Value.Equals("Item1")
    11                         select ele).FirstOrDefault();
    12             if (item != null)
    13             {
    14                 XComment xcom = new XComment("后面的注释");
    15                 XComment xcoma = new XComment("前面的注释");
    16                 item.AddAfterSelf(xcom);
    17                 item.AddBeforeSelf(xcoma);
    18             }
    19             tr.Close();
    20             xele.Save(Path);
    21         }

    上面我使用StringReaderTextReader读取xml字符串并使用XElement.Load读取该对象,然后就是在新建节点的时候新建的是注释节点,最后利用一样的语法添加到指定节点前后。

    最终结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <!--前面的注释-->
    4   <!--前面的注释-->
    5   <Item v1="1" v2="2">Item1</Item>
    6   <!--后面的注释-->
    7   <!--后面的注释-->
    8   <Item v1="1" v2="2" v3="3">Item2</Item>
    9 </Root>

    5.替换指定节点

    修改节点的值通过SetValue即可做到,但是有时涉及到子节点,而我们想一次性全部替换掉,那么我们就需要使用ReplaceWith

    代码如下:

     1         /// <summary>
     2         /// 替换指定节点
     3         /// </summary>
     4         public static void ReplaceElement()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Elements("Item")
     8                         where ele.Value.Equals("Item2")
     9                         select ele).FirstOrDefault();
    10             if (item != null)
    11             {
    12                 item.ReplaceWith(new XElement("Item", "Item3"));
    13             }
    14             xele.Save(Path);
    15         }

    这里的重点在于ReplaceWith方法,调用该方法会发生两个操作。首先是删除该节点,然后在该节点的位置上将我们的节点插入完成替换。

    最后的xml结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <!--前面的注释-->
    4   <!--前面的注释-->
    5   <Item v1="1" v2="2">Item1</Item>
    6   <!--后面的注释-->
    7   <!--后面的注释-->
    8   <Item>Item3</Item>
    9 </Root>

    这样我们很轻易的就替换了整个节点。

    6.删除指定属性

    前面我们介绍了创建、修改和添加属性,但是还没有介绍如何删除指定的属性,下面我们就通过一个简单的实例来演示。

    代码如下:

     1         /// <summary>
     2         /// 删除指定属性
     3         /// </summary>
     4         public static void RemoveAttribute()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Elements("Item")
     8                         where ele.Value.Equals("Item1")
     9                         select ele).FirstOrDefault().Attribute("v1");
    10             if (item != null)
    11             {
    12                 item.Remove();
    13             }
    14             xele.Save(Path);
    15         }

    我们首先查询出指定的节点,然后指定某个属性,最后调用XAttributeRemove方法既可。

    结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <!--前面的注释-->
    4   <!--前面的注释-->
    5   <Item v2="2">Item1</Item>
    6   <!--后面的注释-->
    7   <!--后面的注释-->
    8   <Item>Item3</Item>
    9 </Root>

    7.删除指定节点

    既然上面已经可以删除属性,自然也少不了删除属性。

    代码如下所示:

     1         /// <summary>
     2         /// 删除指定节点
     3         /// </summary>
     4         public static void RemoveElement()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Elements("Item")
     8                         where ele.Value.Equals("Item1")
     9                         select ele).FirstOrDefault();
    10             if (item != null)
    11             {
    12                 item.Remove();
    13             }
    14             xele.Save(Path);
    15         }

    依然是调用同样的方法。

    结果如下:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Root>
    3   <!--前面的注释-->
    4   <!--前面的注释-->
    5   <!--后面的注释-->
    6   <!--后面的注释-->
    7   <Item>Item3</Item>
    8 </Root>

    三、按节点关系查询

    上面的查询都是通过相关的条件进行查询,但是我们有时仅仅只需要通过之间的关系即可,这样反而可以避免很多的代码,当然稍加探索可以发现其实XElement都提供给我们了。

    我们依然要新建一个StructureXml类,并在其中新建一个属性。

    如下所示:

    1         public static String Path
    2         {
    3             get
    4             {
    5                 String path = String.Format("{0}\test2.xml", Environment.CurrentDirectory);
    6                 return path;
    7             }
    8         }

    同时在该文件夹下新建一个test2.xml并写入如下内容:

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <Root>
     3   <Item>
     4     <SubItem1>
     5       1
     6     </SubItem1>
     7     <SubItem>
     8       <Child>
     9         sss
    10       </Child>
    11     </SubItem>
    12     <SubItem2>
    13       2
    14     </SubItem2>
    15   </Item>
    16 </Root>

    1.显示指定节点的所有父节点

    通过上面的xml文件,我们清晰的看出xml是具有结构性的,彼此之间都存在关系,而现在我们需要显示某个节点的父级节点的名称。

    代码如下所示:

     1         /// <summary>
     2         /// 显示指定节点的所有父节点
     3         /// </summary>
     4         public static void ShowAllParentEle()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Descendants("Child")
     8                         select ele).FirstOrDefault();
     9             if (item != null)
    10             {
    11                 foreach (var sub in item.Ancestors())
    12                 {
    13                     Console.WriteLine(sub.Name);
    14                 }
    15                 Console.WriteLine("----------------");
    16                 foreach (var sub in item.AncestorsAndSelf())
    17                 {
    18                     Console.WriteLine(sub.Name);
    19                 }
    20                 Console.ReadKey();
    21             }
    22         }

    其中我们通过Descendants获取最底的节点,然后使用Ancestors获取所有的父级节点,而AncestorsAndSelf则表示包含本身。

    最终结果如下所示:

    我们从图中看出,分割线前显示的是不包含本身的,而下面是包含本身的。

    2.显示指定节点的所有子节点

    我们不仅仅可以输出一个节点的所有父级节点,同样也可以输出一个节点的所有子节点。

    代码如下所示:

     1         /// <summary>
     2         /// 显示指定节点的所有子节点
     3         /// </summary>
     4         public static void ShowAllChildEle()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             foreach (var sub in xele.Descendants())
     8             {
     9                 Console.WriteLine(sub.Name);
    10             }
    11             Console.WriteLine("-----------------");
    12             foreach (var sub in xele.DescendantsAndSelf())
    13             {
    14                 Console.WriteLine(sub.Name);
    15             }
    16             Console.ReadKey();
    17         }

    这里我们依然是分成输出子级节点以及包含自己的。

    结果如下所示:

    3.显示同级节点之前的节点

    既然有了父子关系,当然也少不了同级关系,首先我们先显示同级节点之前的节点。

    代码如下所示:

     1         /// <summary>
     2         /// 显示同级节点之前的节点
     3         /// </summary>
     4         public static void ShowPrevEle()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Descendants("SubItem")
     8                         select ele).FirstOrDefault();
     9             if (item != null)
    10             {
    11                 foreach (var sub in item.ElementsBeforeSelf())
    12                 {
    13                     Console.WriteLine(sub.Name);
    14                 }
    15             }
    16             Console.ReadKey();
    17         }

    这里我们看到我们通过ElementsBeforeSelf获取该节点之前的同级节点,当然我们还可以传入参数作为限制条件。这里我们通过查询获取了SubItem这个节点,并显示该节点之前的同级节点。

    最终结果如下:

    4.显示同级节点后面的节点

    作为上面的补充。

    代码如下所示:

     1        /// <summary>
     2         /// 显示同级节点后面的节点
     3         /// </summary>
     4         public static void ShowNextEle()
     5         {
     6             XElement xele = XElement.Load(Path);
     7             var item = (from ele in xele.Descendants("SubItem")
     8                         select ele).FirstOrDefault();
     9             if (item != null)
    10             {
    11                 foreach (var sub in item.ElementsAfterSelf())
    12                 {
    13                     Console.WriteLine(sub.Name);
    14                 }
    15             }
    16             Console.ReadKey();
    17         }

    最终结果如下所示:

    四、监听xml事件

    你可能会疑惑xml为什么还要监听,其实这样是有意义的,比如你要根据某个节点的值作为依赖,那么你就要监听这个节点,如果这个节点发生改变的时候,

    你才可以及时的作出反应。但是xml的事件监听有一个特点,跟浏览器中的DOM事件类似,监听父节点同样也可以监听的到它的子节点的事件。下面我们

    通过一个简单的实例来说明。

    实例代码如下:

     1     public static class EventXml
     2     {
     3         public static void BindChangeing()
     4         {
     5             XElement xele = new XElement("Root");
     6             xele.Changing += xele_Changing;
     7             xele.Changed += xele_Changed;
     8             xele.Add(new XElement("Item", "123"));
     9             var item = xele.Element("Item");
    10             item.ReplaceWith(new XElement("Item", "2"));
    11             item = xele.Element("Item");
    12             item.Remove();
    13             Console.ReadKey();
    14         }
    15 
    16         static void xele_Changed(object sender, XObjectChangeEventArgs e)
    17         {
    18             XElement ele = sender as XElement;
    19             Console.WriteLine(String.Format("已完成 {0}-{1}", ele.Name, e.ObjectChange));
    20         }
    21 
    22         static void xele_Changing(object sender, XObjectChangeEventArgs e)
    23         {
    24             XElement ele = sender as XElement;
    25             Console.WriteLine(String.Format("正在进行中 {0}-{1}", ele.Name, e.ObjectChange));
    26         }
    27 }

    其中的关键就是ChangingChanged事件,其次就是在事件中判断事件的来源。

    最终结果如下所示:

    五、处理xml流

    在实际的商业化的开发中,xml不可能仅仅保存这么点数据。有可能保存着非常多的数据。但是我们还是按照以往的方式,就会将xml全部读取进内存。

    这样会占据很多内存,影响系统的性能,针对这种情况我们需要使用流的方式去处理xml,因为流会按照我们的顺序读取部分xml进内存,并不会将所

    xml都读取进内存。

    Xml文件内容如下所示:

    1 <?xml version="1.0" encoding="utf-8" ?>
    2 <Root>
    3   <SubItem>1</SubItem>
    4   <SubItem>1</SubItem>
    5   <SubItem>1</SubItem>
    6   <Item>A</Item>
    7   <SubItem>1</SubItem>
    8   <Item>B</Item>
    9 </Root>

    代码如下所示:

     1     public static class ReadXmlStream
     2     {
     3         public static String Path
     4         {
     5             get
     6             {
     7                 String path = String.Format("{0}\test3.xml", Environment.CurrentDirectory);
     8                 return path;
     9             }
    10         }
    11 
    12         /// <summary>
    13         /// 流式处理XML
    14         /// </summary>
    15         public static void ReadXml()
    16         {
    17             XmlReader reader = XmlReader.Create(Path);
    18             while (reader.Read())
    19             {
    20                 if (reader.NodeType == XmlNodeType.Element && reader.Name.Equals("Item"))
    21                 {
    22                     XElement ele = XElement.ReadFrom(reader) as XElement;
    23                     Console.WriteLine(ele.Value.Trim());
    24                 }
    25             }
    26             Console.ReadKey();
    27         }
    28 }

    这里我们通过XmlReaderCreate静态方法打开xml文件,并通过Read一个节点的进行读取,并判断该节点的类型。

    最终结果如下:

  • 相关阅读:
    发行版Linux和麒麟操作系统下netperf 网络性能测试
    Linux下RPM包的安装
    CentOS7下安装NVIDIA独立显卡驱动出现X service error问题解决方法
    真实的物理机安装Centos7系统后网卡只有lo没有eno1的解决办法:实际上是物理机未安装网驱动卡
    Windows7系统下OpenCV2.4.4+PCL1.6.0+SSBA3.0+VS2010 IDE32环境下编译和安装以实现Sfm和PCL点云数据可视化
    VC++编译错误error C2065: “HANDLE”: 未声明的标识符及添加winbase.h后提示winbase.h(243): error C2146: 语法错误: 缺少“;”(在标识符“Internal”的前面)的解决办法
    [转]VS2013+简单稀疏光束调整库SSBA配置(64位编译)
    std::max、std::min error C2589: “(”:“::”右边的非法标记,error C2059: 语法错误:“::”
    理解图像卷积和滤波的典型博文
    [转]OPENCV3.3+CUDA9.0 环境搭建若干错误总结
  • 原文地址:https://www.cnblogs.com/yaozhenfa/p/CSharp_Linq_For_Xml.html
Copyright © 2011-2022 走看看