LINQ to XML

前言


可扩展标记语言(Extensible Markup Language,XML)是一种标记语言,它定义了一组规则,用于以人和机器都可以理解的格式对文档进行编码。下面是一个简单的XML示例:

<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

本文首先介绍Sysem.Xml命名空间中处理XML的一般方法,如XmlReader、XmlWriter以及XmlDocument等。随后讲解如何通过LINQ to XML操作XML,体会LINQ的方便与快捷。

System.Xml


本文将使用product.xml作为数据源,以下是文件结构:

<?xml version="1.0" encoding="utf-8" ?>
<products>
  <product vendor="Apple" productiondate="2018/01/01">
    <name>IphoneX</name>
    <price>7088</price>
  </product>
  <product vendor="Huawei" productiondate="2018/01/12">
    <name>Huawei P20 Pro</name>
    <price>6288</price>
  </product>
</products>

XmlReader

表示提供对XML数据进行快速、非缓存、只进访问的读取器。

首先来看一个非常简单的示例:

XmlReader xr = XmlReader.Create("product.xml");
while (xr.Read())
{
    if (xr.NodeType == XmlNodeType.Text)
    {
        Console.WriteLine(xr.Value);
    }
}
Console.ReadLine();

这段代码有几点需要注意:

  1. 使用静态方法XmlReader.Create创建XmlReader对象
  2. 遍历文档时,调用Read方法进入下一节点
  3. 通过Value属性获取节点的值

当然,System.Xml命名空间中读取XML文档的方式很多(例如,可以使用EOF属性作为循环条件),这里就不一一介绍了。

使用XmlReader类进行验证

XmlReader类可以使用XmlReaderSettings类,根据XSD架构验证XML的有效性。下面的示例使用product.xsd验证product.xml文档。xsd文件结构:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="products">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="product">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="name" type="xs:string" />
              <xs:element name="price" type="xs:unsignedShort" />
            </xs:sequence>
            <xs:attribute name="vendor" type="xs:string" use="required" />
            <xs:attribute name="productiondate" type="xs:string" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

向product.xml添加如下内容,注意:这里故意将product节点错写为products

<products vendor="Xiaomi" productiondate="2018/01/12">
    <name>Xiaomi MIX 2</name>
    <price>2599</price>
</products>

客户端调用

static void Main(string[] args)
{
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.Schemas.Add(null, "product.xsd");
    settings.ValidationType = ValidationType.Schema;
    settings.ValidationEventHandler += new System.Xml.Schema.ValidationEventHandler(settings_ValidationEventHandler);
    XmlReader xr = XmlReader.Create("product.xml", settings);
    while (xr.Read())
    {
        if (xr.NodeType == XmlNodeType.Text)
        {
            Console.WriteLine(xr.Value);
        }
    }
    Console.ReadLine();
}

static void settings_ValidationEventHandler(object sender, System.Xml.Schema.ValidationEventArgs e)
{
    Console.WriteLine(e.Message);
}

说明:

  • 创建XmlReaderSettings后,将product.xsd架构添加到XmlSchemaSet对象中。添加时第一个参数设置为null,默认使用当前的Namespace。
  • 当文档验证失败时,会引发一个XmlSchemaValidationException异常,这里使用ValidationEvent处理验证失败,避免引发异常。

输出结果

XmlWriter

表示一个编写器,该编写器提供一种快速、非缓存和只进的方式来生成包含 XML 数据的流或文件。

下面是一个简单的示例,说明如何使用XmlWriter类:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = true;
XmlWriter xw = XmlWriter.Create("newProduct.xml", settings);
xw.WriteStartDocument();
xw.WriteStartElement("products");
xw.WriteStartElement("product");
xw.WriteAttributeString("vendor", "Xiaomi");
xw.WriteAttributeString("productiondate", "2018/01/12");
xw.WriteElementString("name", "Xiaomi MIX 2");
xw.WriteElementString("price", "2599");
xw.WriteEndElement();
xw.WriteEndElement();
xw.WriteEndDocument();
xw.Flush();
xw.Close();

输出结果:

<?xml version="1.0" encoding="utf-8"?>
<products>
  <product
    vendor="Xiaomi"
    productiondate="2018/01/12">
    <name>Xiaomi MIX 2</name>
    <price>2599</price>
  </product>
</products>

XmlDocument

XmlDocument类是用于在.NET中表示DOM的类,与XmlReader和XmlWriter不同,XmlDocument类具有读写功能,并可以随意访问DOM树。下面介绍一个使用XmlDocument的示例:

XmlDocument xd = new XmlDocument();
xd.Load("product.xml");
XmlNodeList nodeList = xd.GetElementsByTagName("name");
foreach (XmlNode node in nodeList)
{
    Console.WriteLine(node.OuterXml);
}
Console.ReadLine();

XmlDocument和XmlReader加载文本的区别:

XmlReader只浏览一次文档,它是一种只进访问的读取器。也就是说,如果需要反复查看节点,使用XmlReader是不合适的,这种情况下最好使用XmlDocument。

LINQ to XML


在介绍如何使用LINQ to XML操作XML文档之前,首先介绍几个相关的对象。

  • XDocument:XDocument取代之前的XmlDocument对象,使得处理XML文档更容易,XDocument对象调用Load方法将xml文件内容加载到一个XDocument对象中。

    XDocument xd = XDocument.Load("product.xml");
  • XElement:使用XElement可以轻松创建包含单个元素的对象。

    XElement xe = new XElement("product",
        new XElement("name", "vivo"),
        new XElement("price", "2899"));
  • XNamespace:XNamespace对象表示XML命名空间。在前面的例子中,给根元素应用一个命名空间。

    XNamespace xn = "http://www.xxx.com/";
    XElement xe = new XElement(xn + "product",
        new XElement("name", "vivo"),
        new XElement("price", "2899"));
  • XComment:XComment对象可以将注释添加到XML文档中。

    XDocument xd = XDocument.Load("product.xml");
    XComment xc = new XComment("this is a commet");
    xd.Add(xc);
  • XAttribute:通过XAttribute对象可以添加和使用XML特性。

    XElement xe = new XElement("product",
       new XAttribute("vendor", "vivo"),
       new XAttribute("productiondate","2018/03/05"),
       new XElement("name", "vivo"),
       new XElement("price", "2899"));

查询XML文档

下面的示例使用LINQ查询product.xml文件,获取所有的产品及价格。

XDocument doc = XDocument.Load("product.xml");
var query = from q in doc.Descendants("product")
            select q.Value;
foreach (var product in query)
{
    Console.WriteLine(product);
}

这里调用了XDocument对象的Descendants属性获取所有的product节点的子元素,接着select语句获取这些元素的值,最后循环输出这些值。输出结果如下:

使用Element方法,还可以获取XML文档中特定节点。例如只需要查询所有产品的名字,只需要将LINQ语句中select表达式后面跟上q.Element("name").Value即可。

XDocument doc = XDocument.Load("product.xml");
var query = from q in doc.Descendants("product")
            select q.Element("name").Value;
foreach (var product in query)
{
    Console.WriteLine(product);
}
//-----------output---------------
//IphoneX
//Huawei P20 Pro
//Xiaomi MIX 2
//--------------------------------

通过Attribute方法查询指定特性的节点,以下示例查询vendor特性为Apple的产品名:

XDocument doc = XDocument.Load("product.xml");
var query = from q in doc.Descendants("product")
            where q.Attribute("vendor").Value.Equals("Apple")
            select q.Element("name").Value;
foreach (var product in query)
{
    Console.WriteLine(product);
}
//-----------output:IphoneX----------

通过Element方法查询指定子节点,以下示例查询Xiaomi MIX 2的价格:

XDocument doc = XDocument.Load("product.xml");
var query = from q in doc.Descendants("product")
            where q.Element("name").Value.Equals("Xiaomi MIX 2")
            select q.Element("price").Value;
foreach (var product in query)
{
    Console.WriteLine(product);
}
//-----------output:2599-------------

Elements和Descendants的区别:

同样是获取指定节点,Descendants方法获取XDocument中所有符合条件的节点,而Elements方法仅仅能够获取当前节点的下一层中所有符合条件的节点。举个例子来说,如下xml文档结构:

<Employees>
  <Employee>
    <ID>0001</ID>
    <Name>Zhang San</Name>
  </Employee>
  <Employee>
    <ID>0002</ID>
    <Name>Li Si</Name>
    <New>
      <Employee>
        <ID>0020</ID>
        <Name>Wang Wu</Name>
      </Employee>
    </New>
  </Employee>
</Employees>

使用Elements和Descendants方法查询所有Employee的姓名:

XDocument doc = XDocument.Load("employee.xml");
var query1 = from q in doc.Root.Elements("Employee")
             select q.Element("Name").Value;
var query2 = from q in doc.Descendants("Employee")
             select q.Element("Name").Value;

Console.WriteLine("Query1:");
foreach (var result in query1)
{
    Console.WriteLine(result);
}
Console.WriteLine("-----------------------------");
Console.WriteLine("Query2:");
foreach (var result in query2)
{
    Console.WriteLine(result);
}
//-----------output---------------
//Query1:
//Zhang San
//Li Si
//-----------------------------
//Query2:
//Zhang San
//Li Si
//Wang Wu
//--------------------------------

根据输出可以看出,Query1只查询到根节点下一层中的员工信息,而Query2查询到文档中所有员工的信息。

使用XSD文件进行格式验证

任然将product节点错写为products,下面的示例使用product.xsd文件对xml进行格式验证。

XDocument doc = XDocument.Load("product.xml");
XmlSchemaSet settings = new XmlSchemaSet();
settings.Add(null, XmlReader.Create("product.xsd"));
doc.Validate(settings, (p, e) =>
{
    Console.WriteLine(e.Message);
});

//output:The element 'products' has invalid child element 'products'. List of possible elements expected: 'product'.

写入XML文档

除了读取XML文档之外,同样可以轻松的写入文档。如下将product.xml中IPhoneX的价格修改为6888。

XDocument doc = XDocument.Load("product.xml");
doc.Root.Elements("product")
   .Where(p => p.Attribute("vendor").Value.Equals("Apple"))
   .Single()
   .Element("price")
   .SetValue("6888");
doc.Save("product.xml");

查看product.xml文档,发现价格已被修改:



参考资料


  1. 《C#高级编程(第9版)》

  2. 微软官方文档 - LINQ to XML

posted @ 2018-05-24 11:01 Answer.Geng 阅读(...) 评论(...) 编辑 收藏