|
|
|
Описание для автора не найдено
|
|
|
|
|
|
|
|
Эффективные техники редактирования больших XML-файлов
Автор: Деа Обасаньо (Dare Obasanjo), Корпорация Microsoft
(http://www.microsoft.com/)
Перевод: Шатохина Надежда(sna@uneta.org), UNETA
(http://www.uneta.org/)
Апрель 2004
Обзор: Деа Обасаньо показывает две
техники для эффективного обновления или модифицирования больших XML-файлов,
таких как файл журнала и копии баз данных.
Введение
Использование техник включения XML
Связывание XmlReader с XmlWriter
Благодарность
Введение
Поскольку XML обрел популярность как формат представления больших источников
информации, у разработчиков появилась проблема редактирования больших
XML-файлов. Это особенно касается приложений, которые обрабатывают большие файлы
журналов и должны постоянно добавлять информацию в эти файлы. Самый простой путь
редактирования XML-файла — загрузить его в XmlDocument,
модифицировать этот документ в памяти и затем опять сохранить на диск. Однако
это означает, что весь XML-документ должен быть загружен в память, что может
быть невозможным из-за размера документа и требований приложения к объему и
конфигурации памяти.
В этой статье показаны альтернативные подходы к редактированию XML-документа,
которые не используют его загрузку в экземпляр
XmlDocument.
Использование техник включения XML
Первый предлагаемый мною подход более всего будет полезен для внесения
дополнительных значений в XML-файл журнала. Обычно разработчики сталкиваются с
такой проблемой: необходима возможность просто дополнять файл журнала новыми
элементами, а не загружать для этого весь документ в память. Из-за формальной
строгости правил XML зачастую трудно использовать традиционные средства для
добавления элементов в XML-файл журнала таким способом, который не делает файл
журнала завершенным, из-за их плохой сформированности.
Первая техника, которую я покажу, ориентирована на ситуации, когда необходимо
иметь возможность быстро добавлять элементы в XML-документ. Этот подход включает
создание двух файлов. Первый файл — правильный XML-файл, второй — фрагмент XML.
Правильный XML-файл включает фрагмент XML, используя или внешнюю
сущность, объявленную в DTD, или применяя элемент xi:include. Таким образом, файл, включающий фрагмент 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 и может быть эффективно
обновлен с помощью обычных методов ввода/вывода файла. Следующий код показывает,
как можно ввести элемент в 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} accessed 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, но он страдает
от той же проблемы, что и подход с XmlDocument, поскольку весь
документ должен находиться в памяти. Также разработчикам, плохо знакомым с XSLT,
необходимо время на понимание того, как правильно использовать подходящий
шаблон.
Одно из решений проблемы обработки очень большого XML-документа — прочитать
XML с помощью XmlReader и переписать после прочтения с помощью
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");
//создаем XmlTextReader используя созданную XmlNameTable
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 (localhost)
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, еще интересно отметить,
что он использует NameTable для улучшения производительности
сравнения текста при проверке имен тэгов элементов в методе
ReadEvent(). Преимущества использования этого подхода для
проверки имен тэгов элементов в XmlReader приведены в разделе
Object Comparison Using XmlNameTable with XmlReader
(http://msdn.microsoft.com/xml/default.aspx?pull=/library/en-us/cpguide/html/cpconObjectComparisonUsingXmlNameTableWithXmlReader.asp)
документации MSDN.
Благодарность
Благодарю Мартина Гаджина (Martin Gudgin), который вдохновил меня на эту
статью, предложив связывание XmlReader с
XmlWriter в качестве решения проблемы редактирования больших
XML-файлов журнала.
Никакая часть настоящей статьи не может быть воспроизведена или
передана в какой бы то ни было форме и какими бы то ни было средствами, будь то
электронные или механические, если на то нет письменного разрешения владельцев
авторских прав.
Материал, изложенный в данной статье, многократно
проверен. Но, поскольку вероятность технических ошибок все равно существует,
сообщество не может гарантировать абсолютную точность и правильность приводимых
сведений. В связи с этим сообщество не несет ответственности за возможные
ошибки, связанные с использованием статьи.
|
|
|
|
|
|
|