修改大型 XML 文件的有效方法
發表于:2007-06-30來源:作者:點擊數:
標簽:
引言 隨著 XML 成為大型信息源的常用表示格式, 開發 人員編輯大型 XML 文件時開始遇到問題。對于處理大型日志文件以及經常需要為這些文件追加信息的應用程序,尤其如此。編輯 XML 文件最直接的方法是,將其加載到 XmlDocument 中,在內存中修改文檔,然后將
引言
隨著 XML 成為大型信息源的常用表示格式,
開發人員編輯大型 XML 文件時開始遇到問題。對于處理大型日志文件以及經常需要為這些文件追加信息的應用程序,尤其如此。編輯 XML 文件最直接的方法是,將其加載到 XmlDocument 中,在內存中修改文檔,然后將其保存回磁盤。但是,這樣做意味著要將整個 XML 文檔加載到內存中,由于文檔太大或應用程序需要的內存不夠,這種方法可能會行不通。
這篇論文說明了修改 XML 文檔的一些可供選擇的方法,這些方法不涉及將文檔加載到 XmlDocument 實例中的內容。
使用 XML 包含方法
建議的第一種方法對于向 XML 日志文件追加值最為有用。開發人員面臨的常見問題是需要一種能夠將新條目簡單地追加到日志文件中而不用加載文檔的方法。因為 XML 具有良好結構規則,所以使用傳統方式(這種方法會因為日志文件格式不正確而結束日志文件)來向 XML 日志文件追加條目通常是非常困難的。
要說明的第一種方法是針對這樣的情形,即目的是能夠將條目快速地追加到 XML 文檔中。這種方法包括創建兩個文件。第一個文件是格式正確的 XML 文件,第二個是 XML 片段。格式正確的 XML 文件包括 XML 片段,XML 片段使用 DTD 中聲明的 external entity 或者使用 xi:include element 。使用包含文件,通過在進行處理過程中簡單地追加到 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 方法有效地進行更新。下面的代碼說明了如何通過將條目追加到文本文件的結尾來將它添加到 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();
}
}
一旦條目被追加到文本文件中,使用傳統的 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} a
clearcase/" target="_blank" >ccessed at {1}", file, date);
}
}
}
上面的代碼導致了下面的輸出:
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 文件執行更復雜的操作。例如,要篩選日志文件中的每一個條目,而這些條目在存檔到日志文件前不符合某些特殊標準。要完成此任務的一種方法是將 XML 文件加載到 XmlDocument 中,然后通過 XPath 選擇感興趣的事件。但是,這樣做涉及將整個文檔加載到內存中,如果文檔太大,則這種做法會受到限制。另一種選擇方法為了這種任務會涉及使用 XSLT,但是由于整個 XML 文檔需要保存到內存中,這種方法會和 XmlDocument 方法一樣遇到相同的問題。另外,由于開發人員不熟悉 XSLT,了解如何正確使用模板匹配時會遇到較大的困難。
要解決如何處理大型 XML 文檔問題的一種方法是使用 XmlReader 讀取 XML,讀取的同時使用 XmlWriter 將其寫出。使用這種方法,整個文檔不會同時存入內存中,對 XML 可以進行更精確的更改而不只是追加元素。下面的代碼示例讀取前面部分的 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(); // 使用結尾標記
}else if(reader.Name == referrerKey){
referrer = reader.ReadString();
}
}//if
}//while
}
public static void Main(string[] args){
string ip, httpMethod, file, date, referrer;
//使用用于進行比較的字符串設置 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.Signific
ant;
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(本地主機)中的事件
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> 的結尾標記
} while(vr.NodeType == XmlNodeType.Element);
Console.WriteLine("Done");
vr.Close();
xw.Close();
}
}
上面的代碼示例在寫入到 logfile-archive.xml 文件中時會導致下面的輸出:
<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() 方法檢查元素標記名稱時使用 NameTable 提高了文本比較的
性能。在 XmlReader 中使用這種方法檢查元素的標記名稱的優點在如下的 MSDN 文檔主題中進行了概述:Object Comparison Using XmlNameTable with XmlReader(英文)。
原文轉自:http://www.anti-gravitydesign.com