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();
这段代码有几点需要注意:
- 使用静态方法XmlReader.Create创建XmlReader对象
- 遍历文档时,调用Read方法进入下一节点
- 通过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文档,发现价格已被修改:
参考资料
-
《C#高级编程(第9版)》