Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Эффективные техники редактирования больших 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-файлов журнала.

    Никакая часть настоящей статьи не может быть воспроизведена или передана в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, если на то нет письменного разрешения владельцев авторских прав.

    Материал, изложенный в данной статье, многократно проверен. Но, поскольку вероятность технических ошибок все равно существует, сообщество не может гарантировать абсолютную точность и правильность приводимых сведений. В связи с этим сообщество не несет ответственности за возможные ошибки, связанные с использованием статьи.
     
         

       
       
         
      VBNet рекомендует