C# 之 Linq to Xml

▪ 前言

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

一、生成Xml

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

并在其中写入以下属性:

public static String Path
{
    get
    {
        return String.Format("{0}\\test.xml", Environment.CurrentDirectory);
    }
}

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

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 xdoc = new XDocument(
        new XDeclaration("1.0", "utf-8", "yes"),
        new XComment("提示"),
        new XElement("item", "asd")
    );
    
    // 保存内容
    xdoc.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);
    
    // 构建 XML 元素
    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");
    
    // 构建 XML 元素
    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()
{
    // 构建 XML 元素
    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
    {
        return String.Format("{0}\\test1.xml", Environment.CurrentDirectory);
    }
}

同时在该路径下新建一个 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 xeleItem = xele.Element("Item");
    
    Console.Write(xeleItem.Value.Trim());
    Console.ReadKey();
}

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

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

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

xele.Elements("Item") 表示获取 xele 元素的下级节点中名称为 Item 的节点

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

添加完之后的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);
}

上面我使用 StringReaderTextReader 读取 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
    {
        return String.Format("{0}\\test2.xml", Environment.CurrentDirectory);
    }
}

同时在该文件夹下新建一个 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 则表示包含本身。

最终结果如下所示:

SubItem
Item
Root
----------------
Child
SubItem
Item
Root

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

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

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

结果如下所示:

Item
SubItem1
SubItem
Child
SubItem2
-----------------
Root
Item
SubItem1
SubItem
Child
SubItem2
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 这个节点,并显示该节点之前的同级节点。

最终结果如下:

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

最终结果如下所示:

SubItem2

四、监听xml事件

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

但是 xml 的事件监听有一个特点,跟浏览器中的 DOM 事件类似,监听父节点同样也可以监听的到它的子节点的事件。下面我们通过一个简单的实例来说明。

实例代码如下:

public static class EventXml
{
    public static void BindChangeing()
    {
        XElement xele = new XElement("Root");
        
        xele.Changed += xele_Changed;
        xele.Changing += xele_Changing;
        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事件,其次就是在事件中判断事件的来源。

最终结果如下所示:

正在进行中 Item-Add
已完成 Item-Add
正在进行中 Item-Remove
已完成 Item-Remove
正在进行中 Item-Add
已完成 Item-Add
正在进行中 Item-Remove
已完成 Item-Remove

五、处理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一个节点的进行读取,并判断该节点的类型。

最终结果如下:

A
B
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在C#中,使用Linq to XML可以方便地进行XML文件的读写操作。首先,我们需要加载XML文件,可以使用XDocument类的Load方法来加载文件\[1\]。然后,我们可以通过获取根元素来访问XML文件的内容,使用Root属性可以获取根元素\[2\]。接下来,我们可以使用Elements方法获取根元素的所有直接子元素,并通过遍历这些子元素来获取它们的名称和属性值\[2\]。如果需要进一步访问子元素的子元素,可以使用Elements方法继续获取\[2\]。另外,如果XML文件中使用了命名空间,我们可以使用XName类的Get方法来指定命名空间\[3\]。通过这些方法,我们可以方便地读取和解析XML文件中的数据。当需要写入XML文件时,可以使用XDocument类的Save方法将修改后的XML文件保存到指定的路径。 #### 引用[.reference_title] - *1* *3* [Linq to Xml读写xml](https://blog.csdn.net/donghan2019/article/details/101946892)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C#基础精华06(Linq To XML,读取xml文件,写入xml)](https://blog.csdn.net/XHQT520/article/details/50401669)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值