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相同。

figure_0412_0305

图 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,使你能够按名称或索引访问集合中的特性。

figure_0413_0306

图 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所示。

figure_0413_0307

图 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(删除全部节点)方法来完成。

figure_0415_0308

图 11-6 示例运行结果

figure_0415_0309

图 11-7 示例运行结果

11.3.2 用XPath搜索XmlDocument

XPath表达式使用路径表示法(与URL中使用的路径表示法类似)寻找XML文档的各个部分。表达式计算为生成节点集、布尔值、数字或字符串类型的对象。例如,表达式MyBook/book将返回<MyBook>元素中包含的<book>元素的节点集,前提是此类元素已在源XML文档中声明。此外,XPath表达式还可以包含谓词(筛选表达式)或函数调用。例如MyBook/book[@name="易学C#"]。表11-3列举出了基本的XPath语法。

figure_0415_0310

要在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方法来插入新节点。如下面的示例代码所示:

figure_0417_0311

图 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所示。

figure_0418_0312

图 11-9 编辑示例运行结果

figure_0418_0313

图 11-10 统计示例运行结果