Работа с XML в .NET

ОГЛАВЛЕНИЕ

XML это язык разметки, с помощью которого можно описать произвольные данные. На основе XML можно организовать хранение информации и ее обмен, который не зависит от ни от конкретных приложений, ни от платформы, на которой они исполняются. На основе XML построены web-службы. XML широко применяется в web-приложениях для отделения данных от их отображения. Его стандарт утвержден World Wide Web Consortium (W3C) в 1998 году. Для работы с XML применяются XML-парсеры.

    Существует два основных типа парсеров: Simple API for XML (SAX) и Document Object Model (DOM). SAX основан на курсорах и событиях, возникающих при проходе по узлам XML документа. SAX-прасеру не требуется большого количества памяти для разбора даже больших документов (т.к. ему не нужно загружать в память весь документ), но его существенным ограничением является то, что можно перемещаться по документу только в одном направлении. DOM полностью загружает документ в память и представляет его в виде дерева, поэтому можно произвольно перемещаться по XML-документу.

    Многие составляющие технологии .NET неразрывно связаны с XML. А значит, XML хорошо поддерживается со стороны Framework Class Library. Классы для работы с XML собраны в пространстве имен System.Xml. В .NET поддерживаются следующие технологии

  • XML 1.0
  • пространства имен XML
  • XSD схемы
  • выражения XPath
  • XSL преобразования
  • DOM Level 1 Core, DOM Level 2 Core

   В этой статье рассматриваются приемы работы с XML в .NET на примере небольшого windows-forms приложения для работы с заказами товаров. Данные о заказах и товарах хранятся в сущностных классах Order и Good. Приложение позволяет создавать новые заказы и добавлять в них товары, сохранять и загружать данные о заказах из XML файла и преобразовывать структуру XML-файла для передачи другой организации. Каждый заказ имеет дату и адрес доставки, а каждый товар - название и стоимость. Заказы и товары в заказах на форме приложения отображаются в виде дерева.


Создание XML-документов

    Для создания новых XML-документов применяется класс XmlTextWriter. Он обеспечивает быстрое небуферизованное создание XML-документов  и их запись в файлы, потоки, на консоль и пр. Рассмотрим функцию для сохранения в файл данных о заказах.

private void menuItemSave_Click(object sender, System.EventArgs e)
{
    SaveFileDialog saveDlg = new SaveFileDialog();
    saveDlg.FileName = "заказы";
    saveDlg.DefaultExt = "xml";
    saveDlg.Filter = "Файлы XML (*.xml)|*.xml";
    if (saveDlg.ShowDialog() != DialogResult.OK)
        return;

    XmlTextWriter writer = null;

    try
    {
         writer = new XmlTextWriter(saveDlg.FileName, System.Text.Encoding.Unicode);

         writer.WriteStartDocument();
         writer.WriteStartElement("Заказы");

         // сохраняем заказы
         foreach (Order order in orders)
         {
              writer.WriteStartElement("Заказ");
              writer.WriteAttributeString("Адрес", order.Address);
              writer.WriteAttributeString("Дата", order.Date.ToShortDateString());

              // сохраняем товар
              foreach (Good good in order.Goods)
              {
                   writer.WriteStartElement("Товар");
                   writer.WriteAttributeString("Название", good.Name);
                   writer.WriteAttributeString("Цена", good.Price.ToString());
                   writer.WriteEndElement();
              }

              writer.WriteEndElement();
         }

         writer.WriteEndElement();
         writer.WriteEndDocument();
    }
    catch (Exception ex)
    {
         MessageBox.Show("Ошибка: " + ex.Message);
    }
         finally
    {
         if (writer != null)
              writer.Close();
    }
}

Создавая объект XmlTextWriter в его конструктор мы передали имя файла, в который будут сохраняться XML-данные и кодировку. Перегруженная версия конструктора принимает вместо имени файла объект Stream. Таким образом, в конструктор можно передать, например, объект MemoryStream, а потом сохранить XML-данные в БД. Вызов метода WriteStartDocument() записывает строку с объявлением версии XML и типом кодировки. В нашем случаем это будет
<?xml version="1.0" encoding="utf-16" ?> .

Пары функций WriteStartElement() и WriteEndElement() записывают начало и конец тега с указанным названием. В массиве orders хранятся объекты Order, инкапсулирующие заказы. В цикле мы перебираем все заказы и для каждого заказа вызываем метод WriteStartElement с параметром "Заказ". Для создания корректного XML-документа каждому вызову функции WriteStartElement() должен соответствовать вызов WriteEndElement(). Перегруженные версии функции WriteStartElement() позволяют указать для тега пространство имен и префикс. Для указания атрибута тега применяется функция WriteAttributeString(). С помощью перегруженных версий функции также можно указать префикс и пространство имен для атрибута. Функция WriteEndDocument() применяется для защиты от случайных ошибок создания XML-документа - она закрывает все открытые теги и атрибуты и переводит writer в начальное состояние. Сохраним следующие заказы.

 

В результате у нас получается документ такого вида

<?xml version="1.0" encoding="utf-16" ?>
<Заказы>
    <Заказ Адрес="Уфа" Дата="21.04.2004">
        <Товар Название="Товар_А" Цена="100" />
        <Товар Название="Товар_Б" Цена="150" />
        <Товар Название="Товар_В" Цена="370" />
     </Заказ>
    <Заказ Адрес="Москва" Дата="24.04.2004">
        <Товар Название="Товар_Г" Цена="400" />
    </Заказ>
    <Заказ Адрес="Омск" Дата="28.04.2004">
        <Товар Название="Товар_Д" Цена="255" />
    </Заказ>
</Заказы>

    Класс XmlTextWriter предоставляет большое количество методов для создания XML-документов. Например, функция WriteElementString() создает элемент, содержащий одно текстовое значение, такое как <Дата>01.05.04</Дата>. Функция WriteBinHex() массив байтов в шеснадцатиричном виде, а функция WriteComment() вставляет комментарий.


Чтение документов с XmlTextReader

Класс XmlTextReader обеспечивает быстрое однонаправленное чтение потока XML-данных. Данные могут быть получены из файла, объекта потока Stream или объекта TextReader. XmlTextReader обычно применяется если нужно считать XML документ и получить из него данные. Так как XmlTextReader не загружает весь документ в память, он является наилучшим выбором при обработке больших XML файлов - логов, дампов БД и пр.

Прочитаем данные о заказах с помощью класса XmlTextReader из XML-файла, созданного нашей программой в предыдущем разделе.

OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Файлы XML (*.xml)|*.xml"; if (dlg.ShowDialog() ! = DialogResult.OK)
    return;

XmlTextReader reader = null;
orders.Clear();

try
{
    reader = new XmlTextReader(dlg.FileName);
    reader.WhitespaceHandling = WhitespaceHandling.None; // пропускаем пустые узлы

    while (reader.Read())
        if (reader.NodeType == XmlNodeType.Element)
            if (reader.Name == "Заказ")
            {
                 Order order = new Order(reader.GetAttribute("Адрес"), DateTime.Parse(reader.GetAttribute("Дата"))); 

                 // получаем товары в заказе
                 while (reader.Read() && reader.Name == "Товар")
                     order.AddGood(reader.GetAttribute("Название"), float.Parse(reader.GetAttribute("Цена")));
                 orders.Add(order);
            }

            ShowOrders();
}
catch (Exception ex)
{
    MessageBox.Show("Ошибка: " + ex.Message);
}
finally
{
    if (reader != null)
        reader.Close();
}

XML-данные, которые читает XmlTextReader, берутся из файла, выбранного пользователем в диалоге. Для подавления пустых строк мы устанавливаем значение None для свойства WhitespaceHandling. Метод Read() производит чтение из потока следующего узла XML-документа. Он возвращает true если удалось считать узел. Обязательно нужно вызвать метод Read перед первым обращением к данным, т.к. в момент инициализации XmlTextReader не содержит никаких данных.

Имя тега возвращает свойство Name. В момент, когда имя текущего тега "Заказ", мы создаем новый объект Order. Дату и адрес заказа мы получаем с помощью метода GetAttribute. Вложенные в заказ товары получаем перебирая теги в цикле while до тех пор, пока имя текущего тега "Товар". Важно то, что если запрашиваемый атрибут не найден, то возвращается пустая строка. При этом проверка того, является ли этот атрибут обязательным для текущего тега не производится. При чтении файла bad.xml, в одном из заказов не указан адрес. Однако, программа успешно считывает файл и выводит такие данные. Проверку допустимости XML-документа осуществляет класс XmlValidatingReader, о котором будем говорить ниже.

 

Тип узла можно определить по значению свойства NodeType - оно возвращает значение из перечисления XmlNodeType. Типом узла может быть элемент документа, атрибут, комментарий и т.д. Значение узла зависит от типа тега и возвращатся свойством Value. Например, для текстового тега возвращается текстовое значение, находящееся в теге, а для комментария - текст комментария.

Для идентификации текущего положения в документе существуют методы Depth, возвращающий уровень вложенности текущего тега, и LineNumber и LinePosition, возвращающие строку и позицию текущего тега в документе. Так как XmlTextReader часто применяется для получения значений только определенных узлов XML-документа, то для ускорения процесса поиска узлов можно применять метод Skip, который пропускает дочерние узлы текущего узла документа.


Чтение документов с XmlValidatingReader

XmlValidatingReader обеспечивает проверку допустимости XML-документов на основе трех схем: XML Schema definition language (XSD), XML-Data Reduced (XDR) schema и DTD. XmlValidatingReader наследован от класса XmlReader и используется объекты класса XmlTextReader для получения XML-документа. Тип схемы устанавливается значением свойства ValidationType. Если же передать этому свойству значение ValidationType.None, то проверка производится не будет. По умолчанию значение свойства ValidationType равно ValidationType.Auto и тип схемы определяется автоматически.

В нашем примере мы используем схему XSD. Для ее создания мы загрузили в Visual Studio .NET XML файл с заказами, выбрали в контекстном меню пункты Create Schema, отметили как обязательные атрибуты дату и адрес для заказа и цену и название для товара и убрали значение из атрибута для целевого пространства имен. В итоге получился следующий XSD-файл.

<?xml version="1.0" ?>
<xs:schema id="Заказы" xmlns:mstns="http://tempuri.org/заказы.xsd"
xmlns="http://tempuri.org/заказы.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
attributeFormDefault="qualified" elementFormDefault="qualified">
  <xs:element name="Заказы" msdata:IsDataSet="true" msdata:Locale="ru-RU" msdata:EnforceConstraints="False">
    <xs:complexType>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="Заказ">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="Товар" minOccurs="0" maxOccurs="unbounded">
                  <xs:complexType>
                    <xs:attribute name="Название" form="unqualified" type="xs:string" use="required" />
                    <xs:attribute name="Цена" form="unqualified" type="xs:string" use="required" />
                  </xs:complexType>
                </xs:element>
              </xs:sequence>
              <xs:attribute name="Адрес" form="unqualified" type="xs:string" use="required" />
              <xs:attribute name="Дата" form="unqualified" type="xs:string" use="required" />
            </xs:complexType>
          </xs:element>
        </xs:choice>
     </xs:complexType>
  </xs:element>
</xs:schema>

Рассмотрим функцию, выполняющую проверку допустимости XML-документа с заказами на основе XSD-схемы.

OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Файлы XML (*.xml)|*.xml";

if (dlg.ShowDialog() != DialogResult.OK)
    return;

string file = dlg.FileName;

// получаем имя файла со схемой
dlg.Filter = "Файлы XSD (*.xsd)|*.xsd";

if (dlg.ShowDialog() != DialogResult.OK)
    return;

string schema = dlg.FileName;
XmlValidatingReader readerVal = null;
orders.Clear();

try
{
    XmlTextReader reader = new XmlTextReader(file);
    reader.WhitespaceHandling = WhitespaceHandling.None; // пропускаем пустые узлы
    readerVal = new XmlValidatingReader(reader);
    readerVal.Schemas.Add("", schema);
    readerVal.ValidationType = ValidationType.Schema;
    readerVal.ValidationEventHandler += new ValidationEventHandler(ValidationEventHandle);

    while (readerVal.Read())
        if (readerVal.NodeType == XmlNodeType.Element)
            if (readerVal.Name == "Заказ")
            {
                 Order order = new Order(readerVal.GetAttribute("Адрес"), DateTime.Parse(readerVal.GetAttribute("Дата")));
                 // получаем товары в заказе
                 while (readerVal.Read() && readerVal.Name == "Товар")
                      order.AddGood(readerVal.GetAttribute("Название"), float.Parse(readerVal.GetAttribute("Цена")));
                 orders.Add(order);
            }

     ShowOrders();
}
catch (XmlException ex)
{
    MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
    MessageBox.Show("Ошибка: " + ex.Message);
}
finally
{
    if (readerVal != null)
        readerVal.Close();
}

Вначале создаем объект XmlTextReader и передаем ему имя файла, указанное пользователем. Затем создаем XmlValidatingReader, который и будет отвечать за проверку допустимости документа. Затем добавляем XSD-схему из файла, указанного пользователем. Первым параметром в функции Add идет пространство имен (у нас передана пустая строка). Если же в вашем случае задано пространство имен, то его нужно определять из XSD-файла, получив значение атрибута targetNamespace у тега schema. Затем мы указываем тип схемы и определяем обработчик ValidationEventHandle события, который будет вызываться в случае возникновения ошибок или предупреждений. Тип события (ошибка или предупреждение) можно определить с помощью значения ValidationEventArgs.Severity, передаваемого обработчику. Также с помощью атрибутов можно получить дополнительную информацию об ошибке (ValidationEventArgs.Exception) и текстовое описание ошибки (alidationEventArgs.Message). Если мы не определим обработчик, то в случае определения недопустимости XML-документа будет возникать исключение XmlException, а предупреждения будут игнорироваться. На рисунке приведен диалог с сообщением об ошибке.

 

Как и в случае в XmlTextReader мы в цикле вызываем метод Read для перебора всех узлов документа. Сам процес разбора документа так же ни чем не будет отличать от работы с XmlTextReader.


Чтение документов с XmlDocument

Класс XmlDocument реализует W3C Document Object Model (DOM) Level 1 Core и Core DOM Level 2. XmlDocument наиболее полезен в том случае, если нужно загрузить XML-документ в память для того, чтобы изменить атрибуты узлов, добавить или удалить новые элементы. DOM представляет XML-документ как дерево, хранящееся в памяти, с элементом-документом, являющимся корнем. Например, вот такой XML-документ, созданный нами ранее 

<Заказы>
    <Заказ Адрес="Уфа" Дата="21.04.2004">
        <Товар Название="Товар_А" Цена="100" />
        <Товар Название="Товар_Б" Цена="150" />
        <Товар Название="Товар_В" Цена="370" />
     </Заказ>
    <Заказ Адрес="Москва" Дата="24.04.2004">
        <Товар Название="Товар_Г" Цена="400" />
    </Заказ>
    <Заказ Адрес="Омск" Дата="28.04.2004">
        <Товар Название="Товар_Д" Цена="255" />
    </Заказ>
</Заказы>

будет представлен в виде такого дерева

 

Каждый узел в таком дереве является объектом класса XmlNode. Узел имеет только одного родителя и может иметь несколько атрибутов и дочерних узлов.

В нашем приложении XmlDocument применяется для загрузки данных о заказах из XML-файла. Рассмотрим эту функцию.

private void menuItemOpenDocument_Click(object sender, System.EventArgs e)
{
    OpenFileDialog dlg = new OpenFileDialog();
    dlg.Filter = "Файлы XML (*.xml)|*.xml";
    if (dlg.ShowDialog() != DialogResult.OK)
        return;

    XmlDocument doc = new XmlDocument();
    orders.Clear();

    try
    {
        doc.Load(dlg.FileName);
        // получаем все заказы
        XmlNodeList ordersList = doc.DocumentElement.ChildNodes;

        foreach (XmlNode nodeOrder in ordersList)
        {
             Order order = new Order(nodeOrder.Attributes["Адрес"].Value, DateTime.Parse(nodeOrder.Attributes["Дата"].Value));
             // получаем все товары из заказа
             XmlNodeList goodsList = nodeOrder.ChildNodes;

             foreach (XmlNode nodeGood in goodsList)
                 order.AddGood(nodeGood.Attributes["Название"].Value, float.Parse(nodeGood.Attributes["Цена"].Value));
             orders.Add(order);
        }

        ShowOrders();
    }
    catch (Exception ex)
    {
        MessageBox.Show("Ошибка: " + ex.Message);
    }

Функция Load() загружает XML-документ, выбранный пользователем. В случае возникновения ошибки при чтении файла или разборе XML-документа выбрасывается исключение XmlException и документ остается пустым. Есть и перегруженная версия функции, принимающая в качестве параметра объект Stream. Для получения корневого элемента документа существует свойство DocumentElement, возвращающий объект XmlElement если документ не пуст или null в противном случае. Для получения всем потомков узла существует свойство ChildNodes. В нашем примере мы получили корневой элемент документа, соответствующий тегу "Заказы", и перебрали дочерние элементы - теги "Заказ". Для получения значений атрибутов тега было использовано свойство Attributes. В нашем случае мы получали значение атрибутаа по имени, но можно было получить его по порядковому номеру или имени и пространству имен. Тег для товара в заказе является дочерним по отношению к тегу заказа, поэтому для получения всех товаров из заказа мы так же воспользовались свойством ChildNodes.

Кроме перебора всех узлов XML-документа существуют и другие способы нахождения нужной информации. Можно найти нужны узлы XML-документа, воспользовавшись выражением XPath (методы SelectSingleNode и SelectNodes) - этот способ мы рассмотрим далее. Или можно получить список XmlNodeList всех тегов определенного имени. Для получения дат всех заказов можно получить все теги "Заказ", и получить значение их атрибута "Дата".

XmlNodeList orders = doc.GetElementsByTagName("Заказ");
foreach (XmlNode node in orders)
    Console.WriteLine(elemList[i].Attributes["Дата"]); }

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

Класс XmlDocument обычно используется для изменения существующих XML-документ. Однако, можно загрузить XML-документ из текстовой строки методом LoadXML класса XmlDocumentа, а затем добавить в него новые узлы и сохранить в файл. При этом мы используем загруженный документ как шаблон, хотя можно создавать документ и с нуля. Приведем пример использования функции LoadXML .

XmlDocument doc = new XmlDocument();
doc.LoadXml("<Заказы></Заказы>");
XmlNode order = doc.CreateElement("Заказ");

XmlAttribute attrDate = doc.CreateAttribute("Дата");
attrDate.Value = DateTime.Today.ToShortDayString();
order.Attributes.Append(attrDate);

XmlAttribute attrAdr = doc.CreateAttribute("Адрес");
attrAdr.Value = "Уфа";
order.Attributes.Append(attrAdr);

doc.DocumentElement.AppendChild(order);

doc.Save("заказы.xml");

Вначале мы создаем пустой список заказов, вызывая LoadXml. Затем создаем узел для заказа и добавляем к нему атрибуты для даты и адреса методом CreateAttribute. Затем добавляем узел для заказа к корневому элементу документа функцией AppendChild и сохраняем документ в файл.


XSL-преобразование документа

Extensible Stylesheet Language (XSL) - язык для преобразования XML-данных. Преобразования XSLT иногда называют стилем (stylesheet). С помощью XSL легко разделять данные и их представление. Данные в этом случае хранятся в XML-файлах, а XSL содержит шаблон HTML страницы, определяющий то, как будут отображаться данные, и XSL-инструкции, указывающие как данные будут размещаться в шаблоне. XSL позволяет использовать одну схему для большого класса документов, имеющих одинаковую структуру. XSL-преобразования могут осуществляться программно (например, с помощью класса XslTransform) или браузером на клиенте, путем включения тега ?xml-stylesheet, указывающего на XSL-файл.

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

OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Файлы XML (*.xml)|*.xml";

if (dlg.ShowDialog() != DialogResult.OK)
    return;

string file = dlg.FileName;

// получаем xsl файл
dlg.Filter = "Файлы XSL (*.xslt)|*.xslt";
if (dlg.ShowDialog() != DialogResult.OK)
    return;

string fileXsl = dlg.FileName;

// получаем имя выходного файла
SaveFileDialog dlgSave = new SaveFileDialog();
dlgSave.FileName = "transform";
dlgSave.DefaultExt = "xml";
dlgSave.Filter = "Файлы XML (*.xml)|*.xml";

if (dlgSave.ShowDialog() != DialogResult.OK)
    return;

string fileOut = dlgSave.FileName;

XmlTextWriter writer = null;

try
{
    XPathDocument doc = new XPathDocument(file);
    XslTransform xsl = new XslTransform();
    writer = new XmlTextWriter(fileOut, System.Text.Encoding.Unicode);
    xsl.Load(fileXsl);
    xsl.Transform(doc, null, writer, (XmlResolver)null);
}
catch (Exception ex)
{
    MessageBox.Show("Ошибка: " + ex.Message);
}
finally
{
    if (writer != null)
        writer.Close();
}

Пользователь указывает имя входного и выходного XML-файлов и XSL-файла. В выходной файл информация записывается с помощью XmlTextWriter, о котором говорилось ранее. Объект XslTransform загружает XSL-файл методом Load и выполняет преобразование, сохраняя данные  в writer. Вторым параметром передаются параметры XsltArgumentList, применяемые при преобразовании. Четвертым параметром передается объект XmlResolver, нужный для разрешений ссылок на внешние ресурсы.

Мы использовали следующим XSL-файл, в котором описаны соответствия между тегами на русском и английском языке. 

<?xml version="1.0" encoding="windows-1251" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Заказы">
  <Orders>
    <xsl:apply-templates select="Заказ" />
  </Orders>
</xsl:template>
<xsl:template match="Заказ">
  <Order>
    <xsl:attribute name="Date">
    <xsl:value-of select="@Дата" />
    </xsl:attribute>
    <xsl:attribute name="Address">
    <xsl:value-of select="@Адрес" />
    </xsl:attribute>
    <xsl:apply-templates select="Товар" />
  </Order>
</xsl:template>
<xsl:template match="Товар">
  <Good>
    <xsl:attribute name="Price">
    <xsl:value-of select="@Цена" />
    </xsl:attribute>
    <xsl:attribute name="Name">
    <xsl:value-of select="@Название" />
    </xsl:attribute>
  </Good>
</xsl:template>
</xsl:stylesheet>

В результате преобразования в выходном документе все теги изменились, а структура осталось прежней.

<Orders> 
  <Order Date="21.04.2004" Address="Уфа">
    <Good Price="100" Name="Товар_А"></Good>
    <Good Price="150" Name="Товар_Б"></Good>
    <Good Price="370" Name="Товар_В"></Good>
  </Order>
  <Order Date="24.04.2004" Address="Москва">
    <Good Price="400" Name="Товар_Г"></Good>
  </Order>
  <Order Date="28.04.2004" Address="Омск">
    <Good Price="255" Name="Товар_Д"></Good>
  </Order>
</Orders>

Поиск по документу с XPath

XML Path Language (XPath) - язык, применяемый для получения наборов узлов XML-документа и адресации частей XML-документа. Он обеспечивает компактный, эффективный, логичный синтаксис для ссылок на узлы документа, тесно связан с XSLT и предоставляет стандартные средства для работы со строками, числами и булевскими значениями.

Пути в XPath формируются из названий узлов, разделенных слешами. Путь к товару будет описан следующим выражением.

Заказы/Заказ/Товар

Для идентификации элемента во всех местах документа нужно использовать два слеша в начале выражения:

//Товар 

Для указания неизвестных элементов в пути можно воспользоваться символом *. Например, можно задать вот такой путь для поиска руководителей отделов

Отделы/*/Руководитель

Для фильтрации элементов документа нужно использовать предикаты - выражения, заключенные в квадратные скобки. В предикатах могут участвовать атрибуты тегов и значения вложенных тегов. При этом, перед атрибутами тегов, участвующих в фильтрации, ставиться амперсанд @, а перед вложенными тегами - нет. Предикаты можно объединять ключевыми словами or и and. Для нахождения заказов из Москвы или Уфы нужно использовать такое выражение:

Заказы/Заказ[@Адрес = "Уфа" or @Адрес = "Москва"]

Для работы с XPath в .NET используются классы XPathNavigator и XPathNodeIterator. XPathNavigator выполняет XPath запросы, а перемещение по узлам, полученным в результате запроса осуществляется с помощью XPathNodeIterator. С помощью XPathNavigator можно перемещаться по документу в произвольном направлении. Для перехода к родительскому узлу есть метод MoveToParent, а для переход к первому дочернему узлу - MoveToFirstChild. Свойство HasChildren возвращает true если у текущего узла есть дочерние. Для перебора атрибутов текущего узла есть методы MoveToFirstAttribute, MoveToNextAttribute. В нашей программе мы используем XPath для поиска товаров, стоимостью более 300 рублей, и нахождения их суммарной стоимости. Рассмотрим эту функцию. 

OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Файлы XML (*.xml)|*.xml"; if (dlg.ShowDialog() ! = DialogResult.OK)
    return;

XmlDocument doc = new XmlDocument();
orders.Clear();

try
{
    doc.Load(dlg.FileName);

    // находим стоимость всех товаров
    XPathNavigator nav = doc.CreateNavigator();
    XPathNodeIterator it = (XPathNodeIterator)nav.Evaluate("Заказы/Заказ/Товар[@Цена > 300]");

    StringBuilder sb = new StringBuilder();
    sb.Append("Дорогие товары:");
    float total = 0;

    while (it.MoveNext())
    {
         sb.AppendFormat("\n{0}", it.Current.GetAttribute("Цена", ""));
         total += float.Parse(it.Current.GetAttribute("Цена", ""));
    }

    sb.AppendFormat("\n--------------------\nИтого: {0} рублей", total);

    // получаем все заказы
    XmlNodeList ordersList = doc.DocumentElement.ChildNodes;

    foreach (XmlNode nodeOrder in ordersList)
    {
        Order order = new Order(nodeOrder.Attributes["Адрес"].Value, DateTime.Parse(nodeOrder.Attributes["Дата"].Value));

        // получаем все товары из заказа
        XmlNodeList goodsList = nodeOrder.ChildNodes;

        foreach (XmlNode nodeGood in goodsList)
            order.AddGood(nodeGood.Attributes["Название"].Value, float.Parse(nodeGood.Attributes["Цена"].Value));

        orders.Add(order);
    }

    ShowOrders();

    // выводим адреса заказов
    MessageBox.Show(sb.ToString());
}
catch (Exception ex)
{
    MessageBox.Show("Ошибка: " + ex.Message);
}

На первом этапе мы создаем XmlDocument и загружаем XML-файл, указанный пользователем. Объект XPathNavigator создается вызовом метода CreateNavigator. Мы выполнили XPath-запрос с помощью метода Evalate, выполняющим произвольный запрос. Результатом может быть число, строка, булевское значение или набор узлов, как в нашем случае. Этот метод мы выбрали только для примера, т.к. для получения именно набора узлов лучше пользоваться методом Select. Он также принимает строку с XPath выражением, но сразу возвращает объект XPathNodeIterator. В нашем примере мы искали все товары, цена которых более 300 рублей. Для перебора всех узлов, найденных методом Evalate, вызываем метод MoveNext() и для каждого узла получаем значение атрибута Цена. Все найденные цены суммируются, а из значения сохраняются в текстовом виде в StringBuilder.

 

Кондратьев Денис