基于流的 XML 处理提供了最小的负载,但也只提供了最小的灵活性。在很多 XML 处理场景里,你不会在这么低的层次下工作。
对内存中的 XML 的处理则更加方便,但没有单一、标准的方式。如下所有的类都支持对 XML 的读取和导航:
XmlDocument 把信息保存为树的节点。节点是 XML 文件的基本组成部分,它可以使一个元素、特性、注释或者元素的一个值。
为了挖掘树的所有层次,下面这个示例使用了递归来操作 XmlDocument 类:
protected void Page_Load(object sender, EventArgs e)
{
string xmlFile = Server.MapPath("DvdList.xml");
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile); // doc.LoadXml() 可以接收一个XML格式的字符串
string str = GetChildNodesDescr(doc.ChildNodes, 0);
Response.Write(str);
}
private string GetChildNodesDescr(XmlNodeList nodeList, int level)
{
string indent = "";
for (int i = 0; i < level; i++)
{
indent += "";
}
StringBuilder str = new StringBuilder();
foreach (XmlNode node in nodeList)
{
switch (node.NodeType)
{
case XmlNodeType.Comment:
str.Append(indent);
str.Append("Comment: <b>");
str.Append(node.Value).Append("</b><br />");
break;
case XmlNodeType.Element:
str.Append(indent);
str.Append("Element: <b>").Append(node.Name).Append("</b><br />");
break;
case XmlNodeType.Text:
str.Append(indent);
str.Append(" - Value: <b>");
str.Append(node.Value).Append("</b><br />");
break;
case XmlNodeType.XmlDeclaration:
str.Append("XML Declaration: <b>").Append(node.Name);
str.Append(" ").Append(node.Value).Append("</b><br />");
break;
}
if (node.Attributes != null)
{
foreach (XmlAttribute attribute in node.Attributes)
{
str.Append(indent).Append(" - Attribute: <b>");
str.Append(attribute.Name).Append("</b> - Value: <b>");
str.Append(attribute.Value).Append("</b><br />");
}
}
if (node.HasChildNodes) // node.ChildNodes.Count != 0
{
// level 作为递归传递的变量,进入新方法后自然会递增
// 因此这里一定不能用 (level++) 或 ++level 否则排版会乱
str.Append(GetChildNodesDescr(node.ChildNodes, level+1));
}
}
return str.ToString();
}
并非所有类型的节点都同时具备名称和值。例如,对于元素 Title,它的名字就是 Title,不过它的值为空,因为它的值被保存到随后的 Text 节点中了。
XPathNavigator 类(在 System.Xml.XPath 命名空间里)和 XmlDocument 类的工作方式很相似。它把所有信息都加载到内存中并允许你在节点中移动。关键的区别是它使用基于游标的方式允许你使用 MoveToNext()之类的方法在 XML 数据间移动。每次 XPathNavigator 类只能定位到一个节点。
可以使用 XmlDocument.CreateNavigator()方法从 XmlDocument 创建一个 XPathNavigator。
下面是一个示例:
protected void Page_Load(object sender, EventArgs e)
{
string xmlFile = Server.MapPath("DvdList.xml");
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
XPathNavigator xnav = doc.CreateNavigator();
Response.Write(GetXNavDescr(xnav, 0));
}
// xnav 是定位在某一个节点上的游标,而不是节点的集合
private string GetXNavDescr(XPathNavigator xnav, int level)
{
string indent = "";
for (int i = 0; i < level; i++)
{
indent += " ";
}
StringBuilder str = new StringBuilder();
switch (xnav.NodeType)
{
case XPathNodeType.Comment:
str.Append(indent);
str.Append("Comment: <b>").Append(xnav.Value);
str.Append("</b><br />");
break;
case XPathNodeType.Element:
str.Append(indent);
str.Append("Element: <b>").Append(xnav.Name);
str.Append("</b><br />");
break;
case XPathNodeType.Root:
str.Append("<b>Root</b><br />");
break;
case XPathNodeType.Text:
str.Append(indent);
str.Append(" - Value: <b>").Append(xnav.Value);
str.Append("</b><br />");
break;
}
if (xnav.HasAttributes)
{
xnav.MoveToFirstAttribute();
do
{
str.Append(indent);
str.Append(" - Attribute: <b>").Append(xnav.Name);
str.Append("</b> Value: <b>").Append(xnav.Value);
str.Append("</b><br />");
} while (xnav.MoveToNextAttribute());
xnav.MoveToParent();
}
if (xnav.HasChildren)
{
xnav.MoveToFirstChild();
do
{
str.Append(GetXNavDescr(xnav, level + 1));
} while (xnav.MoveToNext());
xnav.MoveToParent();
}
return str.ToString();
}
效果和上个示例一样。代码中两处 xnav.MoveToParent()是必须的,因为操作的是节点的游标,递归时传递的游标也只有一个,里层处理完不返回上一层的话,外层方法循环的条件里无法正确处理游标,导致数据丢失。
XDocument 是管理内存中 XML 所有功能的模型!与 XmlDocument 和 XPathNavigator 不同,它擅长构建 XML 内容。XmlDocument 使 XML 构建不必太复杂,而 XPathNavigator 则完全不支持。
如果要以非线性的方式生成 XML ,例如需要把一系列元素写入根元素,然后又要在这些元素里添加更多的信息,就必须使用 XDocument 这样的内存类。
与 XmlDocument 非常相似,但有一个区别,在 LINQ to XML 模型里,特性没有被看做单独的节点而是被看做附加到其他元素的 名称/值 对。
从技术层面而言,XDocument 是 LINQ 的一部分。它在 System.Xml.Linq 命名空间里,并且它是 .NET 3.5 中 System.Xml.Linq 程序集的一部分,使用这个类需要添加对该程序集的引用。
1. 使用 XDocument 创建 XML
通过 XDocument 可以使用整洁和精确的代码生成 XML 内容,用 XElement 类还可以创建一个不表示完整的 XML 内容。
所有的 LINQ to XML 类都提供了有用的构造函数,它允许在同一步里对其创建和初始化:
XElement element = new XElement("Price", "23.99");
保存的代码甚至变得更加疯狂,它们能够在一行代码里创建嵌套的节点树。这两个类(XDocument、XElement)包含最后一个接收参数数组(params)的构造函数。这个参数数组包含一套嵌套的节点。
下面的示例创建一个有两个嵌入元素以及它们内容的元素:
XElement element = new XElement("Starring",
new XElement("Star", "Tom Hanks"),
new XElement("Star", "Robin Wright")
);
扩展上述的技术,我们创建一个完整的 XML 文档,包括全部的元素、文本内容、特性、注释:
private void WriteXmlWithXDocument()
{
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Created: " + DateTime.Now.ToString()),
new XElement("DvdList",
new XElement("DVD",
new XAttribute("ID", "1"),
new XAttribute("Category", "Science Fiction"),
new XElement("Title", "The Matrix"),
new XElement("Director", "Larry Wachowski"),
new XElement("Price", "18.74"),
new XElement("Starring",
new XElement("Star", "Keanu Reeves"),
new XElement("Star", "Laurence Fishburne")
)
),
new XElement("DVD",
new XAttribute("ID", "2"),
new XAttribute("Category", "Drama"),
new XElement("Title", "Forrest Gump"),
new XElement("Director", "Robert Zemeckis"),
new XElement("Price", "23.99"),
new XElement("Starring",
new XElement("Star", "Tom Hanks"),
new XElement("Star", "Robin Wright")
)
),
new XElement("DVD",
new XAttribute("ID", "3"),
new XAttribute("Category", "Horror"),
new XElement("Title", "The Others"),
new XElement("Director", "Alejandro Amenábar"),
new XElement("Price", "22.49"),
new XElement("Starring",
new XElement("Star", "Nicole Kidman"),
new XElement("Star", "Cristopher Eccleston")
)
),
new XElement("DVD",
new XAttribute("ID", "4"),
new XAttribute("Category", "Mystery"),
new XElement("Title", "Mulholland Drive"),
new XElement("Director", "David Lynch"),
new XElement("Price", "25.74"),
new XElement("Starring",
new XElement("Star", "Laura Harring")
)
),
new XElement("DVD",
new XAttribute("ID", "5"),
new XAttribute("Category", "Science Fiction"),
new XElement("Title", "A.I. Artificial Intelligence"),
new XElement("Director", "Steven Spielberg"),
new XElement("Price", "23.99"),
new XElement("Starring",
new XElement("Star", "Haley Joel Osment"),
new XElement("Star", "Jude Law")
)
)
)
);
doc.Save(Server.MapPath("DvdList.xml"));
}
这段代码的作用和以前介绍的 XmlTextWrite 代码的作用完全相同,但是它更短,更容易阅读。它也比创建内存中的 XmlDocument 的等效代码更简单!另外,这样书写的代码通过语句精确的缩进反映了 XML 元素的嵌套关系,这让你迅速知道 XML 文档的整体形状。
结论:
创建 XML 文档的首选操作类就是 XDocument 。
2. 使用 XDocument 读取流
XDocument 还可以简化对 XML 内容的读取和导航,得到一个含有内容的 XDocument 之后,可以使用 XElement 类的主要属性和方法深入节点树。
XElement 类的核心方法:
Attributes() | 获取这个元素的 Attribute 对象的集合 |
Attribute() | 获取特定名称的 Attribute |
Elements() | 获取第一层的子 XElement 元素,或者你可以指定元素的名称。 |
Element() | 获取由该元素包含的具有指定名称的 XElement ,如果没有匹配的就返回空值 |
Nodes() | 获取该元素包含的所有 XNode 对象,这包括元素以及其他内容,如注释 |
请注意 XDocument 和 XmlDocument 一个很重要的区别。XDocument 嵌套的元素通过方法而不是属性来暴露,这让你能够过滤出你感兴趣的那部分元素,当然,你也可以获取所有元素。
XDocument 类(以及其他 LINQ to XML 类)通常提供更多的成员:
LINQ to XML 的另一个简化是它不要求你区分元素和其中的文本,它们在 XML DOM 里由两个单独的节点表示。你可以通过把他转换为相应的数据类型得到内部的值:
string title = (string)titleElement;
Decimal price = (Decimal)decimalElement;
设置元素内的文本内容同样简单,只要把新值赋给 Value 属性:
priceElement = (decimal)priceElement * 2;
下面这段直观的代码重写之前的 XPathNavigator 的 XML 处理代码。它扫描可用的元素,并把标题、导演以及价格信息添加到列表里:
private string ReadXML()
{
string xmlFile = Server.MapPath("DvdList.xml");
XDocument doc = XDocument.Load(xmlFile);
StringBuilder str = new StringBuilder();
foreach (XElement element in doc.Element("DvdList").Elements())
{
// element.Element("Title").ToString() 出来的格式是
// <Title>......</Title>,因此取值只能使用下面的转换
str.Append("<ul><b>");
str.Append((string)element.Element("Title"));
str.Append("</b><li>");
str.Append((string)element.Element("Director"));
str.Append("</li><li>");
str.Append(string.Format("{0:C}", (decimal)element.Element("Price")));
str.Append("</li></ul>");
}
return str.ToString();
}
3. 命名空间
XDocument 类以特别优雅的方式处理命名空间。只要定义一个 XNameSpace 对象,然后使用它作为名称的一部分创建 XElement 元素即可:
XNamespace ns = "http://www.somecompany.com/DvdList";
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Created: " + DateTime.Now.ToString()),
new XElement(ns + "DvdList",
new XElement(ns + "DVD",
new XAttribute("ID", "1"),
new XAttribute("Category", "Science Fiction"),
new XElement(ns + "Title", "The Matrix"),
new XElement(ns + "Director", "Larry Wachowski"),
new XElement(ns + "Price", "18.74"),
new XElement(ns + "Starring",
new XElement(ns + "Star", "Keanu Reeves"),
new XElement(ns + "Star", "Laurence Fishburne")
)
)
)
......
);
所有元素被放到了新的 XML 命名空间,但特性没有,因为特性已经是附加到元素的了,所以没必要特别再把特性放到同一个命名空间里。
如果元素在 XML 命名空间里,对 XML 文档进行遍历的时候必须同时考虑命名空间:
XNamespace ns = "http://www.somecompany.com/DvdList";
string title = (string)element.Element(ns + "Title");