11.3 基于内存中的XML处理
虽然基于流形式的XmlTextWriter与XmlTextReader
处理XML文档方法简单,但它缺乏灵活性,尤其是在针对大的XML文档的处理时,显得有心无力。如XmlTextReader类只能够以顺序读取,而不能够像处理内存中的XML那样自由地移动到父、子、兄弟节点。相反,它每次只能够读取一个节点。
下面就来阐述如何使用基于内存的形式来处理XML文档。
11.3.1 XmlDocument
XmlDocument是日常最常用的XML处理类,它继承自XmlNode类。同时,它实现W3C文档对象模型((Dcument Object Model, DOM)级别1核心和DOM级别2核心。DOM是XML文档的内存中(缓存)树状表示形式,并允许此文档的导航和编辑。由于XmlDocument实现IXPathNavigable接口,因此它还可用做XslTransform类的源文档。
XmlDocument把信息保存为树的节点。节点是XML文件的基本组成部分,它可以是一个元素、特性、注释或者元素的一个值。每个单独的XmlNode对象代表一个节点,XmlDocument将处于同一层的XmlNode对象放在XmlNodeList集合中。
使用XmlDocument来操作一个XML文档时,首先需要创建一个XmlDocument对象,并加载要处理的XML文档。其中,XML的加载方法如下所示:
❑public virtual void Load(Stream inStream):从指定的流加载XML文档。
❑public virtual void Load(string filename):从指定的URL加载XML文档。其中,URL既可以是本地文件,也可以是HTTP URL(Web地址)。
❑public virtual void Load(TextReader txtReader):从指定的TextReader加载XML文档。
❑public virtual void Load(XmlReader reader):从指定的XmlReader加载XML文档。
❑public virtual void LoadXml(string xml):从指定的字符串加载XML文档。默认情况下,
LoadXml方法既不保留空白,也不保留有意义的空白。而且,此方法不执行DTD或架构验证。下面的示例代码演示了如何创建一个XmlDocument对象,并加载要处理的XML文档。
StringBuilder str=new StringBuilder();
string xml=Server.MapPath("MyBook.xml");
XmlDocument doc=new XmlDocument();
doc.Load(xml);
创建好XmlDocument对象之后,就可以来对这个XML文档进行处理了。可以通过调用它的SelectSingleNode方法来选择匹配XPath表达式的第一个XmlNode,并通过调用XmlNode的Attributes属性来将该节点的属性值输出。如下面的代码所示:
XmlNode node=doc.SelectSingleNode("MyBook/book");
str.Append(node.Attributes["name"].InnerText);
Response.Write(str.ToString());
上面的代码因为使用的是SelectSingleNode方法,所以显示的结果是第一个book节点的name属性的值,即“易学C#”。
当然,也可以通过XmlDocument的SelectNodes方法来选择匹配XPath表达式的节点列表。如下面的代码所示:
XmlNodeList node=doc.SelectNodes("MyBook/book");
for(int i=0;i<node.Count;i++)
{
str.Append(node[i].Attributes["name"].InnerText+"<br>");
}
Response.Write(str.ToString());
示例运行结果如图11-3所示。
除此之外,还可以通过使用ChildNodes属性来获取节点的所有子节点的方式来获取MyBook节点下的所有book节点。如下面的代码所示:
XmlNode node=doc.SelectSingleNode("MyBook");
XmlNodeList list=node.ChildNodes;
for(int i=0;i<list.Count;i++)
{
if(list[i].NodeType==XmlNodeType.Element)
{
str.Append(list[i].Attributes["name"].InnerText+"<br>");
}
}
Response.Write(str.ToString());
上面代码的运行结果与图11-3相同。
图 11-3 显示指定节点的属性示例运行结果
如果需要修改节点的属性的值,则只需要将新值赋给节点的Attributes属性就可以了,然后再调用Save方法来保存修改的结果。如下面的代码所示:
XmlNode node=doc.SelectSingleNode("MyBook");
XmlNodeList list=node.ChildNodes;
for(int i=0;i<list.Count;i++)
{
if(list[i].NodeType==XmlNodeType.Element)
{
//修改属性的值
list[i].Attributes["name"].InnerText=
list[i].Attributes["name"].InnerText+"(马伟)";
str.Append(list[i].Attributes["name"].InnerText+"<br>");
}
}
doc.Save(xml);
Response.Write(str.ToString());
示例运行结果如图11-4所示。
在实际开发中,在更多情况下是利用XmlElement类来操作XML的元素节点。与XmlNode类相比,可以把XmlElement看做特殊的XmlNode类。我们知道,Xml的节点有多种类型,如属性节点、注释节点、文本节点、元素节点等,而XmlNode就是这多种节点的统称,但XmlElement则专指的就是元素节点。其中,XmlElement类可以直接实例化,而XmlNode是抽象类,必须通过XmlDocument实例来创建。对于属性操作而言,XmlElement更为专业,它拥有众多对Attribute的操作方法,如GetAttribute、SetAttribute、RemoveAttribute、GetAttributeNode等,可以方便地对其属性进行读写操作。同样,也可使用它的Attributes属性,它会返回一个XmlAttributeCollection,使你能够按名称或索引访问集合中的特性。
图 11-4 修改节点的属性示例运行结果
下面的示例代码演示了如何使用XmlElement类来修改元素节点的属性值与节点的值。
XmlNode node=doc.SelectSingleNode("MyBook");
XmlNodeList list=node.ChildNodes;
foreach(XmlNode xn in list)
{
if(xn.NodeType==XmlNodeType.Element)
{
//将子节点类型转换为XmlElement类型
XmlElement element=((XlElement)xn;
if(element.GetAttribute("name")=="易学C#")
{
//修改name属性的值
element.SetAttribute("name","易学C#(马伟)");
XmlNodeList clist=element.ChildNodes;
foreach(XmlNode cxn in clist)
{
XmlElement celement=((XlElement)cxn;
if(celement.Name=="price")
{
//修改节点price的值
celement.InnerText="60";
break;
}
}
break;
}
}
}
doc.Save(xml);
示例运行结果如图11-5所示。
图 11-5 示例运行结果
上面的示例代码演示了如何使用XmlElement修改元素节点,如果需要插入新属性,应该怎么办?其方法与修改一样,同样使用SetAttribute方法。如下面的代码所示:
XmlNode node=doc.SelectSingleNode("MyBook");
XmlNodeList list=node.ChildNodes;
foreach(XmlNode xn in list)
{
if(xn.NodeType==XmlNodeType.Element)
{
XmlElement element=((XlElement)xn;
if(element.GetAttribute("name")=="易学C#")
{
element.SetAttribute("ISBN","978-7-115-21198");
element.SetAttribute("出版日期","2009年10月");
XmlNodeList clist=element.ChildNodes;
foreach(XmlNode cxn in clist)
{
XmlElement celement=((XlElement)cxn;
if(celement.Name=="price")
{
celement.InnerText="45";
break;
}
}
break;
}
}
}
doc.Save(xml);
示例运行结果如图11-6所示。
如果需要插入新节点,可以调用XmlDocument的CreateElement方法。下面的示例代码将在book节点中插入一个Press元素节点:
XmlNode node=doc.SelectSingleNode("MyBook");
XmlNodeList list=node.ChildNodes;
foreach(XmlNode xn in list)
{
if(xn.NodeType==XmlNodeType.Element)
{
XmlElement element=((XlElement)xn;
XmlNodeList clist=element.ChildNodes;
XmlElement press=doc.CreateElement("press");
if(element.GetAttribute("name")=="易学C#")
{
press.InnerText="人民邮电出版社";
}
if(element.GetAttribute("name")=="ASP.NET4程序设计")
{
press.InnerText="机械工业出版社";
}
element.AppendChild(press);
}
}
doc.Save(xml);
示例运行结果如图11-7所示。
如果需要删除某个节点的属性或者全部节点,可以调用RemoveAttribute(删除指定的属性)与RemoveAll(删除全部节点)方法来完成。
图 11-6 示例运行结果
图 11-7 示例运行结果
11.3.2 用XPath搜索XmlDocument
XPath表达式使用路径表示法(与URL中使用的路径表示法类似)寻找XML文档的各个部分。表达式计算为生成节点集、布尔值、数字或字符串类型的对象。例如,表达式MyBook/book将返回<MyBook>元素中包含的<book>元素的节点集,前提是此类元素已在源XML文档中声明。此外,XPath表达式还可以包含谓词(筛选表达式)或函数调用。例如MyBook/book[@name="易学C#"]。表11-3列举出了基本的XPath语法。
要在XmlDocument中执行XPath表达式,可以选择下面的方法:
❑public XmlNodeList SelectNodes(string xpath)
选择匹配XPath表达式的节点列表。
❑public XmlNodeList SelectNodes(string xpath, XmlNamespaceManager nsmgr)
选择匹配XPath表达式的节点列表。XPath表达式中的任何前缀都使用提供的XmlNamespaceManager进行解析。
❑public XmlNode SelectSingleNode(string xpath)
选择匹配XPath表达式的第一个XmlNode。
❑public XmlNode SelectSingleNode(string xpath, XmlNamespaceManager nsmgr)
选择匹配XPath表达式的第一个XmlNode。XPath表达式中的任何前缀都使用提供的XmlNamespaceManager进行解析。
如下面的示例代码所示:
public string SearchNode()
{
StringBuilder str=new StringBuilder();
string xml=Server.MapPath("MyBook.xml");
XmlDocument doc=new XmlDocument();
doc.Load(xml);
XmlNodeList list=
doc.SelectNodes("/MyBook/book[starts-with(press,'人')]");
foreach(XmlNode node in list)
{
str.Append(node.Attributes["name"].InnerText);
}
return str.ToString();
}
示例代码页面结果输出“易学C#”。
11.3.3 XPathNavigator
System. Xml.XPath命名空间中的XPathNavigator类是一个抽象类,它将定位和编辑XML信息项的游标模型定义为XQuery 1.0和XPath 2.0数据模型的实例。
XPathNavigator对象根据实现IXPathNavigable接口的类((XathDocument和XmlDocument类)创建。由XPathDocument对象创建的XPathNavigator对象为只读对象,而由XmlDocument对象创建的XPathNavigator对象可以进行编辑。XPathNavigator对象的只读或可编辑状态是使用XPathNavigator类的CanEdit属性决定的。
除此之外,XPathNavigator类与XmlDocument类的处理方式相似。它同样把所有的XML文档信息加载到内存中并允许在节点之间移动。它们之间的关键区别在于XPathNavigator类使用基于游标的方式允许使用MoveToNext之类的方法在XML数据之间移动,并且XPathNavigator类每次只能够定位到一个节点。
下面的SearchNode方法演示了如何在XML文档中进行查询。首先,通过XmlDocument的CreateNavigator方法创建一个XPathNavigator对象;然后再使用Select方法通过XPath语句进行查询,并且将查询结果赋给XPathNodeIterator对象。其中,XPathNodeIterator类在一组选中的节点上提供迭代器,它的MoveNext方法表示移动下一个节点,而Current属性表示当前节点。如下面的示例代码所示:
public string SearchNode(string search)
{
string xml=Server.MapPath("MyBook.xml");
XmlDocument doc=new XmlDocument();
doc.Load(xml);
XPathNavigator pn=doc.CreateNavigator();
StringBuilder str=new StringBuilder();
//查询MyBook的子元素book中name属性值为search的所有节点
XPathNodeIterator iter=pn.Select("MyBook/book[@name='"
+search+"']");
str.Append("<br>"+search+":<br>");
while(iter.MoveNext())
{
//迭代所有子代节点
XPathNodeIterator citer=
iter.Current.SelectDescendants(XPathNodeType.Element,
false);
while(citer.MoveNext())
{
str.Append(citer.Current.Name+":");
str.Append(citer.Current.Value+"<br>");
}
}
return str.ToString();
}
现在,就可以调用这个SearchNode方法来进行XML查询,如查询“易学C#”下的节点元素。如下面的代码所示:
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(SearchNode("易学C#"));
}
示例运行结果如图11-8所示。
当然,也同样可以利用XPathNavigator来编辑节点。但需要注意的是,在编辑之前,必须要检查CanEdit属性是否为true,然后再使用InsertAfter方法来插入新节点。如下面的示例代码所示:
图 11-8 查询示例运行结果
public void InsertNode()
{
string xml=Server.MapPath("MyBook.xml");
XmlDocument doc=new XmlDocument();
doc.Load(xml);
XPathNavigator pn=doc.CreateNavigator();
//判断是否可编辑
if(pn.CanEdit)
{
XPathNodeIterator iter=pn.Select("MyBook/book/press");
while(iter.MoveNext())
{
//在当前节点之后插入新节点
iter.Current.InsertAfter("<author>马伟</author>");
}
}
doc.Save(xml);
}
在上面的代码中,通过语句iter.Current.InsertAfter("<author>马伟</author>")在press节点的后面插入了一个author节点。示例运行结果如图11-9所示。
可以说XPathNavigator功能非常强大,除了上面的这些基本操作之外,还可以使用Evaluate方法来计算表达式的值。如下面的示例代码所示:
public string TotalPrice()
{
StringBuilder str=new StringBuilder();
string xml=Server.MapPath("MyBook.xml");
XmlDocument doc=new XmlDocument();
doc.Load(xml);
XPathNavigator pn=doc.CreateNavigator();
XPathNodeIterator iter=pn.Select("MyBook/book");
str.Append("<br>Price:<br>");
while(iter.MoveNext())
{
XPathNodeIterator citer=
iter.Current.SelectDescendants(XPathNodeType.Element,
false);
while(citer.MoveNext())
{
if(citer.Current.Name=="price")
{
str.Append(citer.Current.Name+":");
str.Append(citer.Current.Value+"<br>");
}
}
}
//统计图书总价
str.Append("Total price="
+pn.Evaluate("sum(MyBook/book/price)"));
return str.ToString();
}
示例运行结果如图11-10所示。
图 11-9 编辑示例运行结果
图 11-10 统计示例运行结果