欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#中的Linq to Xml详解

程序员文章站 2023-12-11 20:43:52
前言 我相信很多从事.net开发的,在.net 3.5之前操作xml会比较麻烦,但是在此之后出现了linq to xml,而今天的主人公就是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的值并输出。

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

C#中的Linq to Xml详解

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则表示包含本身。

最终结果如下所示:

C#中的Linq to Xml详解

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

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();
        }

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

结果如下所示:

C#中的Linq to Xml详解

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这个节点,并显示该节点之前的同级节点。

最终结果如下:

C#中的Linq to Xml详解

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();
        }

最终结果如下所示:

C#中的Linq to Xml详解

四、监听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事件,其次就是在事件中判断事件的来源。

最终结果如下所示:

C#中的Linq to Xml详解

五、处理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#中的Linq to Xml详解

上一篇:

下一篇: