当前位置: 首页 > 编程笔记 >

C#中的Linq to Xml详解

单品
2023-03-14
本文向大家介绍C#中的Linq to Xml详解,包括了C#中的Linq to Xml详解的使用技巧和注意事项,需要的朋友参考一下

前言

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

一、生成Xml

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

并在其中写入以下属性:


public static String Path

        {

            get

            {

                String path = String.Format("{0}\\test.xml", Environment.CurrentDirectory);

                return path;

            }

        }

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

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

1.创建简单的Xml

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

代码如下:


/// <summary>

/// 创建简单的xml并保存

/// </summary>

public static void CreateElement()

{

XDocument xdoc = new XDocument(

new XDeclaration("1.0", "utf-8", "yes"),

new XElement("root",

new XElement("item", "1"),

new XElement("item", "2")

));

xdoc.Save(Path);

}

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

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

最终生成的Xml如下所示:


<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<root>

    <item>1</item>

    <item>2</item>

</root>

2.创建注释

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

比如下面这段代码:


/// <summary>

        /// 创建注释

        /// </summary>

        public static void CreateComment()

        {

            XDocument doc = new XDocument(

                new XDeclaration("1.0", "utf-8", "yes"),

                new XComment("提示"),

                new XElement("item", "asd")

                );

            doc.Save(Path);

        }

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

最终的结果如下所示:


<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<!--提示-->

<item>asd</item>

3.根据对象创建xml

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

代码如下所示:


/// <summary>

        /// 根据对象创建xml并保存

        /// </summary>

        public static void CreateElementByObjects()

        {

            var s = Enumerable.Range(1, 10);

            XElement xele = new XElement(

                "Root",

                from item in s

                select new XElement("item", item.ToString())

                );

            xele.Save(Path);

        }

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

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

保存之后的结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <item>1</item>

  <item>2</item>

  <item>3</item>

  <item>4</item>

  <item>5</item>

  <item>6</item>

  <item>7</item>

  <item>8</item>

  <item>9</item>

  <item>10</item>

</Root>

4.创建属性

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

代码如下所示:


/// <summary>

        /// 创建属性

        /// </summary>

        public static void CreteAttribute()

        {

            XAttribute xa = new XAttribute("V2", "2");

            XElement xele = new XElement(

                "Root",

                new XElement("Item",

                    new XAttribute("V1", "1"),

                    xa

                    ));

            xele.Save(Path);

        }

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

最终的结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <Item V1="1" V2="2" />

</Root>

5.创建命名空间

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

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


/// <summary>

        /// 创建命名空间

        /// </summary>

        public static void CreateNamespace()

        {

            XElement xele = new XElement("{http://www.xamarin-cn.com}Root",

                new XElement("Item", "1"),

                new XElement("{http://www.baidu.com}Item", 2));

            xele.Save(Path);

        }

结果如下所示:


<?xml version="1.0" encoding="utf-8"?>

<Root xmlns="http://www.xamarin-cn.com">

  <Item xmlns="">1</Item>

  <Item xmlns="http://www.baidu.com">2</Item>

</Root>

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

二、查询并修改Xml

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

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


public static String Path

        {

            get

            {

                String path = String.Format("{0}\\test1.xml", Environment.CurrentDirectory);

                return path;

            }

        }

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


<?xml version="1.0" encoding="utf-8"?>

<Root>

   <Item v1="1" v2="2">Item1</Item>

   <Item v1="1" v2="2" >Item2</Item>

</Root>


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

1.通过文件读取xml

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

代码如下:


/// <summary>

        /// 通过文件读取xml

        /// </summary>

        public static void QueryElementByFile()

        {

            XElement xele = XElement.Load(Path);

            XElement xele1 = xele.Element("Item");

            Console.Write(xele1.Value.Trim());

            Console.ReadKey();

        }

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

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

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

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

代码如下:


/// <summary>

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

        /// </summary>

        public static void AddToElementAfterAndBefore()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item2")

                        select ele).SingleOrDefault();

            if (item != null)

            {

                XElement nele = new XElement("NItem", "NItem");

                XElement nele2 = new XElement("BItem", "BItem");

                item.AddAfterSelf(nele);

                item.AddBeforeSelf(nele2);

                xele.Save(Path);

            }

        }

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

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


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <Item v1="1" v2="2">Item1</Item>

  <BItem>BItem</BItem>

  <Item v1="1" v2="2">Item2</Item>

  <NItem>NItem</NItem>

</Root>

3.添加属性到节点中

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

代码如下:


/// <summary>

        /// 添加属性到节点中

        /// </summary>

        public static void AddAttributeToEle()

        {

            XElement xele = XElement.Parse(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注释-->

<Item v1='1' v2='2'>Item1</Item><!--后面的注释--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item2")

                        select ele).SingleOrDefault();

            item.SetAttributeValue("v3", "3");

            xele.Save(Path);

        }

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

Xml内容如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <!--前面的注释-->

  <Item v1="1" v2="2">Item1</Item>

  <!--后面的注释-->

  <Item v1="1" v2="2" v3="3">Item2</Item>

</Root>

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

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

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

代码如下:


/// <summary>

        /// 添加注释到节点前后

        /// </summary>

        public static void AddCommentToAfterAndBefore()

        {

            TextReader tr = new StringReader(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注释-->

<Item v1='1' v2='2'>Item1</Item><!--后面的注释--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");

            XElement xele = XElement.Load(tr);

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item1")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                XComment xcom = new XComment("后面的注释");

                XComment xcoma = new XComment("前面的注释");

                item.AddAfterSelf(xcom);

                item.AddBeforeSelf(xcoma);

            }

            tr.Close();

            xele.Save(Path);

        }

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

最终结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <!--前面的注释-->

  <!--前面的注释-->

  <Item v1="1" v2="2">Item1</Item>

  <!--后面的注释-->

  <!--后面的注释-->

  <Item v1="1" v2="2" v3="3">Item2</Item>

</Root>

5.替换指定节点

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

代码如下:


/// <summary>

        /// 替换指定节点

        /// </summary>

        public static void ReplaceElement()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item2")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                item.ReplaceWith(new XElement("Item", "Item3"));

            }

            xele.Save(Path);

        }

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

最后的xml结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <!--前面的注释-->

  <!--前面的注释-->

  <Item v1="1" v2="2">Item1</Item>

  <!--后面的注释-->

  <!--后面的注释-->

  <Item>Item3</Item>

</Root>

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

6.删除指定属性

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

代码如下:


/// <summary>

        /// 删除指定属性

        /// </summary>

        public static void RemoveAttribute()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item1")

                        select ele).FirstOrDefault().Attribute("v1");

            if (item != null)

            {

                item.Remove();

            }

            xele.Save(Path);

        }

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

结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <!--前面的注释-->

  <!--前面的注释-->

  <Item v2="2">Item1</Item>

  <!--后面的注释-->

  <!--后面的注释-->

  <Item>Item3</Item>

</Root>

7.删除指定节点

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

代码如下所示:


/// <summary>

        /// 删除指定节点

        /// </summary>

        public static void RemoveElement()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Elements("Item")

                        where ele.Value.Equals("Item1")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                item.Remove();

            }

            xele.Save(Path);

        }


依然是调用同样的方法。

结果如下:


<?xml version="1.0" encoding="utf-8"?>

<Root>

  <!--前面的注释-->

  <!--前面的注释-->

  <!--后面的注释-->

  <!--后面的注释-->

  <Item>Item3</Item>

</Root>

三、按节点关系查询

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

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

如下所示:


public static String Path

        {

            get

            {

                String path = String.Format("{0}\\test2.xml", Environment.CurrentDirectory);

                return path;

            }

        }

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


<?xml version="1.0" encoding="utf-8" ?>

<Root>

  <Item>

    <SubItem1>

      1

    </SubItem1>

    <SubItem>

      <Child>

        sss

      </Child>

    </SubItem>

    <SubItem2>

      2

    </SubItem2>

  </Item>

</Root>

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

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

代码如下所示:


/// <summary>

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

        /// </summary>

        public static void ShowAllParentEle()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Descendants("Child")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                foreach (var sub in item.Ancestors())

                {

                    Console.WriteLine(sub.Name);

                }

                Console.WriteLine("----------------");

                foreach (var sub in item.AncestorsAndSelf())

                {

                    Console.WriteLine(sub.Name);

                }

                Console.ReadKey();

            }

        }

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

最终结果如下所示:

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

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

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

代码如下所示:


/// <summary>

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

        /// </summary>

        public static void ShowAllChildEle()

        {

            XElement xele = XElement.Load(Path);

            foreach (var sub in xele.Descendants())

            {

                Console.WriteLine(sub.Name);

            }

            Console.WriteLine("-----------------");

            foreach (var sub in xele.DescendantsAndSelf())

            {

                Console.WriteLine(sub.Name);

            }

            Console.ReadKey();

        }

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

结果如下所示:

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

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

代码如下所示:


/// <summary>

        /// 显示同级节点之前的节点

        /// </summary>

        public static void ShowPrevEle()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Descendants("SubItem")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                foreach (var sub in item.ElementsBeforeSelf())

                {

                    Console.WriteLine(sub.Name);

                }

            }

            Console.ReadKey();

        }

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

最终结果如下:

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

作为上面的补充。

代码如下所示:


/// <summary>

        /// 显示同级节点后面的节点

        /// </summary>

        public static void ShowNextEle()

        {

            XElement xele = XElement.Load(Path);

            var item = (from ele in xele.Descendants("SubItem")

                        select ele).FirstOrDefault();

            if (item != null)

            {

                foreach (var sub in item.ElementsAfterSelf())

                {

                    Console.WriteLine(sub.Name);

                }

            }

            Console.ReadKey();

        }

最终结果如下所示:

四、监听xml事件

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

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

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

实例代码如下:


public static class EventXml

    {

        public static void BindChangeing()

        {

            XElement xele = new XElement("Root");

            xele.Changing += xele_Changing;

            xele.Changed += xele_Changed;

            xele.Add(new XElement("Item", "123"));

            var item = xele.Element("Item");

            item.ReplaceWith(new XElement("Item", "2"));

            item = xele.Element("Item");

            item.Remove();

            Console.ReadKey();

        }

        static void xele_Changed(object sender, XObjectChangeEventArgs e)         {             XElement ele = sender as XElement;             Console.WriteLine(String.Format("已完成 {0}-{1}", ele.Name, e.ObjectChange));         }

        static void xele_Changing(object sender, XObjectChangeEventArgs e)         {             XElement ele = sender as XElement;             Console.WriteLine(String.Format("正在进行中 {0}-{1}", ele.Name, e.ObjectChange));         } }


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

最终结果如下所示:

五、处理xml流

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

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

有xml都读取进内存。

Xml文件内容如下所示:


<?xml version="1.0" encoding="utf-8" ?>

<Root>

  <SubItem>1</SubItem>

  <SubItem>1</SubItem>

  <SubItem>1</SubItem>

  <Item>A</Item>

  <SubItem>1</SubItem>

  <Item>B</Item>

</Root>

代码如下所示:


public static class ReadXmlStream

    {

        public static String Path

        {

            get

            {

                String path = String.Format("{0}\\test3.xml", Environment.CurrentDirectory);

                return path;

            }

        }

        /// <summary>         /// 流式处理XML         /// </summary>         public static void ReadXml()         {             XmlReader reader = XmlReader.Create(Path);             while (reader.Read())             {                 if (reader.NodeType == XmlNodeType.Element && reader.Name.Equals("Item"))                 {                     XElement ele = XElement.ReadFrom(reader) as XElement;                     Console.WriteLine(ele.Value.Trim());                 }             }             Console.ReadKey();         } }

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

最终结果如下:

 类似资料:
  • 本文向大家介绍C/C++中的typedef和#define详解,包括了C/C++中的typedef和#define详解的使用技巧和注意事项,需要的朋友参考一下 C/C++中的typedef和#define 前言:      在C/C++中,我们平时写程序可能经常会用到typedef关键字和#define宏定义命令,在某些情况下使用它们会达到相同的效果,但是它们是有实质性的区别,一个是C/C++的关

  • 本文向大家介绍C/C++中*和&的用法详解,包括了C/C++中*和&的用法详解的使用技巧和注意事项,需要的朋友参考一下 C++中&和*的用法一直是非常让人头疼的难点,课本博客上讲这些知识点一般都是分开讲其用法的,没有详细的总结,导致我在这方面的知识结构格外混乱,在网上找到了一篇英文文章简单总结了这两个符号的一些用法,都是一些比较基础的知识,我比较关心的函数指针,指针函数等都没有涉及到,今后有时间把

  • 本文向大家介绍c++ 中__declspec 的用法详解,包括了c++ 中__declspec 的用法详解的使用技巧和注意事项,需要的朋友参考一下 c++ 中__declspec 的用法如下,想要了解的继续往下看吧。 语法说明: __declspec ( extended-decl-modifier-seq ) 扩展修饰符: 1:align(#) 用__declspec(align(#))精确控制

  • 本文向大家介绍详解C#中的out和ref,包括了详解C#中的out和ref的使用技巧和注意事项,需要的朋友参考一下 要想充分理解C# out和ref,必须先明确如下两个概念(对值类型与引用类型掌握比较好的,可以跳过“一、明确两个基本概念”) 一、明确两个基本概念 值类型:  定义:通过值的方式来传递,即实际参数向形式参数传递(关于形参和实参术语,这里不定义)。  存储方式:主要在栈中。  本质:通

  • 本文向大家介绍C++中的const和constexpr详解,包括了C++中的const和constexpr详解的使用技巧和注意事项,需要的朋友参考一下 C++中的const可用于修饰变量、函数,且在不同的地方有着不同的含义,现总结如下。 const的语义 C++中的const的目的是通过编译器来保证对象的常量性,强制编译器将所有可能违背const对象的常量性的操作都视为error。 对象的常量性可

  • 本文向大家介绍详解C++中static的用法,包括了详解C++中static的用法的使用技巧和注意事项,需要的朋友参考一下 C 语言的 static 关键字有三种(具体来说是两种)用途: 1. 静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数。 要明白这个用法,我们首先要了解c/c++的内存分布,以及static所在的区间。 对于一个完整的程序,在内存中的分布情况如下图:  1.栈