明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

更改大型 XML 文件的有效果方法

[摘要]引言隨著 XML 成為大型信息源的常用表示格式,開發(fā)人員編輯大型 XML 文件時開始遇到問題。對于處理大型日志文件以及經(jīng)常需要為這些文件追加信息的應(yīng)用程序,尤其如此。編輯 XML 文件最直接的方法是...
引言
隨著 XML 成為大型信息源的常用表示格式,開發(fā)人員編輯大型 XML 文件時開始遇到問題。對于處理大型日志文件以及經(jīng)常需要為這些文件追加信息的應(yīng)用程序,尤其如此。編輯 XML 文件最直接的方法是,將其加載到 XmlDocument 中,在內(nèi)存中修改文檔,然后將其保存回磁盤。但是,這樣做意味著要將整個 XML 文檔加載到內(nèi)存中,由于文檔太大或應(yīng)用程序需要的內(nèi)存不夠,這種方法可能會行不通。

這篇論文說明了修改 XML 文檔的一些可供選擇的方法,這些方法不涉及將文檔加載到 XmlDocument 實例中的內(nèi)容。

使用 XML 包含方法
建議的第一種方法對于向 XML 日志文件追加值最為有用。開發(fā)人員面臨的常見問題是需要一種能夠?qū)⑿聴l目簡單地追加到日志文件中而不用加載文檔的方法。因為 XML 具有良好結(jié)構(gòu)規(guī)則,所以使用傳統(tǒng)方式(這種方法會因為日志文件格式不正確而結(jié)束日志文件)來向 XML 日志文件追加條目通常是非常困難的。

要說明的第一種方法是針對這樣的情形,即目的是能夠?qū)l目快速地追加到 XML 文檔中。這種方法包括創(chuàng)建兩個文件。第一個文件是格式正確的 XML 文件,第二個是 XML 片段。格式正確的 XML 文件包括 XML 片段,XML 片段使用 DTD 中聲明的 external entity 或者使用 xi:include element 。使用包含文件,通過在進(jìn)行處理過程中簡單地追加到 XML 文件,可以有效地更新文件包含 XML 片段的方法。包含文件和被包含文件的示例如下所示:

Logfile.xml:
<?xml version="1.0"?>
<!DOCTYPE logfile [
<!ENTITY events
SYSTEM "logfile-entries.txt">
]>
<logfile>
&events;
</logfile>

Logfile-events.txt:
<event>
<ip>127.0.0.1</ip>
<http_method>GET</http_method>
<file>index.html</file>
<date>2004-04-01T17:35:20.0656808-08:00</date>
</event>
<event>
<ip>127.0.0.1</ip>
<http_method>GET</http_method>
<file>stylesheet.css</file>
<date>2004-04-01T17:35:23.0656120-08:00</date>
<referrer>http://www.example.com/index.html</referrer>
</event>
<event>
<ip>127.0.0.1</ip>
<http_method>GET</http_method>
<file>logo.gif</file>
<date>2004-04-01T17:35:25.238220-08:00</date>
<referrer>http://www.example.com/index.html</referrer>
</event>

logfile-entries.txt 文件包括一個 XML 片段,并且可以使用典型的文件 IO 方法有效地進(jìn)行更新。下面的代碼說明了如何通過將條目追加到文本文件的結(jié)尾來將它添加到 XML 日志文件中。

using System;
using System.IO;
using System.Xml;

public class Test{
public static void Main(string[] args){

StreamWriter sw = File.AppendText("logfile-entries.txt");
XmlTextWriter xtw = new XmlTextWriter(sw);

xtw.WriteStartElement("event");
xtw.WriteElementString("ip", "192.168.0.1");
xtw.WriteElementString("http_method", "POST");
xtw.WriteElementString("file", "comments.aspx");
xtw.WriteElementString("date", "1999-05-05T19:25:13.238220-08:00");

xtw.Close();

}
}

一旦條目被追加到文本文件中,使用傳統(tǒng)的 XML 處理方法,就可以處理 XML 日志文件中的條目。下面的代碼使用 XPath 遍歷了 logfile.xml 中的日志事件,同時列出了它們被訪問時的文件以及被訪問的文件。

using System;
using System.Xml;

public class Test2{

public static void Main(string[] args){

XmlValidatingReader vr =
new XmlValidatingReader(new XmlTextReader("logfile.xml"));
vr.ValidationType = ValidationType.None;
vr.EntityHandling = EntityHandling.ExpandEntities;

XmlDocument doc = new XmlDocument();
doc.Load(vr);

foreach(XmlElement element in doc.SelectNodes("//event")){

string file = element.ChildNodes[2].InnerText;
string date = element.ChildNodes[3].InnerText;

Console.WriteLine("{0} accessed at {1}", file, date);

}
}
}

上面的代碼導(dǎo)致了下面的輸出:

index.html accessed at 2004-04-01T17:35:20.0656808-08:00
stylesheet.css accessed at 2004-04-01T17:35:23.0656120-08:00
logo.gif accessed at 2004-04-01T17:35:25.238220-08:00
comments.aspx accessed at 1999-05-05T19:25:13.238220-08:00


更改 XmlReader 為 XmlWriter
在某些情況下,除了只將元素追加到根元素中外,還需要對 XML 文件執(zhí)行更復(fù)雜的操作。例如,要篩選日志文件中的每一個條目,而這些條目在存檔到日志文件前不符合某些特殊標(biāo)準(zhǔn)。要完成此任務(wù)的一種方法是將 XML 文件加載到 XmlDocument 中,然后通過 XPath 選擇感興趣的事件。但是,這樣做涉及將整個文檔加載到內(nèi)存中,如果文檔太大,則這種做法會受到限制。另一種選擇方法為了這種任務(wù)會涉及使用 XSLT,但是由于整個 XML 文檔需要保存到內(nèi)存中,這種方法會和 XmlDocument 方法一樣遇到相同的問題。另外,由于開發(fā)人員不熟悉 XSLT,了解如何正確使用模板匹配時會遇到較大的困難。

要解決如何處理大型 XML 文檔問題的一種方法是使用 XmlReader 讀取 XML,讀取的同時使用 XmlWriter 將其寫出。使用這種方法,整個文檔不會同時存入內(nèi)存中,對 XML 可以進(jìn)行更精確的更改而不只是追加元素。下面的代碼示例讀取前面部分的 XML 文檔,篩選出所有 ip 元素的值為 "127.0.0.1" 的事件后將其保存為存檔文件。

using System;
using System.Xml;
using System.IO;
using System.Text;
public class Test2{
static string ipKey;
static string httpMethodKey;
static string fileKey;
static string dateKey;
static string referrerKey;

public static void WriteAttributes(XmlReader reader, XmlWriter writer){

if(reader.MoveToFirstAttribute()){
do{
writer.WriteAttributeString(reader.Prefix,
reader.LocalName,
reader.NamespaceURI,
reader.Value);
}while(reader.MoveToNextAttribute());
reader.MoveToElement();
}
}

public static void WriteEvent(XmlWriter writer, string ip,
string httpMethod, string file,
string date, string referrer){

writer.WriteStartElement("event");
writer.WriteElementString("ip", ip);
writer.WriteElementString("http_method", httpMethod);
writer.WriteElementString("file", file);
writer.WriteElementString("date", date);
if(referrer != null) writer.WriteElementString("referrer", referrer);
writer.WriteEndElement();

}

public static void ReadEvent(XmlReader reader, out string ip,
out string httpMethod, out string file,
out string date, out string referrer){

ip = httpMethod = file = date = referrer = null;

while( reader.Read() && reader.NodeType != XmlNodeType.EndElement){

if (reader.NodeType == XmlNodeType.Element) {

if(reader.Name == ipKey){
ip = reader.ReadString();
}else if(reader.Name == httpMethodKey){
httpMethod = reader.ReadString();
}else if(reader.Name == fileKey){
file = reader.ReadString();
}else if(reader.Name == dateKey){
date = reader.ReadString();
// reader.Read(); // 使用結(jié)尾標(biāo)記
}else if(reader.Name == referrerKey){
referrer = reader.ReadString();
}
}//if
}//while
}

public static void Main(string[] args){
string ip, httpMethod, file, date, referrer;
//使用用于進(jìn)行比較的字符串設(shè)置 XmlNameTable
XmlNameTable xnt = new NameTable();
ipKey = xnt.Add("ip");
httpMethodKey = xnt.Add("http_method");
fileKey = xnt.Add("file");
dateKey = xnt.Add("date");
referrerKey = xnt.Add("referrer");

//使用上面的 XmlNameTable 加載 XmlTextReader
XmlTextReader xr = new XmlTextReader("logfile.xml", xnt);
xr.WhitespaceHandling = WhitespaceHandling.Significant;

XmlValidatingReader vr = new XmlValidatingReader(xr);
vr.ValidationType = ValidationType.None;
vr.EntityHandling = EntityHandling.ExpandEntities;


StreamWriter sw =
new StreamWriter ("logfile-archive.xml", false, Encoding.UTF8 );
XmlWriter xw = new XmlTextWriter (sw);

vr.MoveToContent(); // 移到文檔元素
xw.WriteStartElement(vr.Prefix, vr.LocalName, vr.NamespaceURI);
WriteAttributes(vr, xw);

vr.Read(); // 移到文檔元素的第一個 <event> 子元素
// 寫出不是 127.0.0.1(本地主機(jī))中的事件
do
{
ReadEvent(vr, out ip, out httpMethod,
out file, out date, out referrer);
if(!ip.Equals("127.0.0.1")){
WriteEvent(xw,ip, httpMethod, file, date, referrer);
}
vr.Read(); //移到下一個 <event> 元素或 <logfile> 的結(jié)尾標(biāo)記
} while(vr.NodeType == XmlNodeType.Element);

Console.WriteLine("Done");

vr.Close();
xw.Close();
}
}

上面的代碼示例在寫入到 logfile-archive.xml 文件中時會導(dǎo)致下面的輸出:

<logfile>
<event>
<ip>192.168.0.1</ip>
<http_method>POST</http_method>
<file>comments.aspx</file>
<date>1999-05-05T19:25:13.238220-08:00</date>
</event>
</logfile>

除了使用 XmlReader 到 XmlWriter 的鏈之外,上面代碼的另一個有趣方面是,使用 ReadEvent() 方法檢查元素標(biāo)記名稱時使用 NameTable 提高了文本比較的性能。在 XmlReader 中使用這種方法檢查元素的標(biāo)記名稱的優(yōu)點在如下的 MSDN 文檔主題中進(jìn)行了概述:Object Comparison Using XmlNameTable with XmlReader(英文)。