Книга: C# для профессионалов. Том II

XPath и XslTransform

XPath и XslTransform

Мы рассмотрим XPath и XslTransform вместе, хотя они являются отдельными пространствами имен на платформе. XPath содержится в System.Xml.XPath, a XslTransform находится в System.Xml.Xsl. Причина совместного рассмотрения состоит в том, что XPath, в частности класс XPathNavigator, предоставляет ориентированный на производительность способ выполнения XSLTransform в .NET. Для начала рассмотрим XPath, а затем его использование в классах System.Xsl.

XPath

Пространство имен XPath создается для скорости. Оно позволяет только читать документы XML без возможностей редактирования. XPath создается для поверхностного выполнения быстрых итераций и выбора в документе XML. Функциональность XPath представляется классом XPathNavigator. Этот класс может использоваться вместо XmlDocument, XmlDataDocument и XPathDocument. Если требуются средства редактирования, то следует выбрать XmlDocument; при работе с ADO.NET будет использоваться класс XmlDataDocument (мы увидим его позже в этой главе). Если имеет значение скорость, то применяйте в качестве хранилища XPathDocument. Можно расширить XPathNavigator для таких вещей, как файловая система или реестр в качестве хранилища. В следующей таблице перечислены классы XPath с кратким описанием назначения каждого класса:

Имя класса Описание
XPathDocument Представление всего документа XML. Только для чтения.
XPathNavigator Предоставляет навигационные возможности для XPathDocument.
XPathNodeIterator Обеспечивает итерацию по множеству узлов. Является эквивалентом для множества узлов в Xpath.
XPathExpression Компилированное выражение Xpath. Используется SelectNodes, SelectSingleNodes, Evaluate и Matches.
XPathException Класс исключений XPath.

XPathDocument не предлагает никакой функциональности класса XmlDocument. Он имеет четыре перегружаемые версии, позволяющие открывать документ XML из файла или строки пути доступа, объекта TextReader, объекта XmlReader или объекта на основе Stream.

Загрузим документ books.xml и поработаем с ним, чтобы можно было понять, как действует навигация. Чтобы использовать эти примеры, необходимо добавить ссылки на пространства имен System.Xml.Xsl и System.Xml.XPath следующим образом:

using System.Xml.XPath;
using System.Xml.Xsl;

Для данного примера воспользуемся файлом bookspath.xml. Он аналогичен books.xml, за исключением того, что добавлены дополнительные книги. Вот код формы, который находится в папке XPathXSLSample1:

private void button1_Click(object sender, System.EventArgs e) {
 // изменить в соответствии с используемой структурой путей доступа
 XPathDocument doc=new XPathDocument("......booksxpath.xml");
 // создать XPathNavigator
 XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator();
 // создать XPathNodeIterator узлов книг
 // который имеют значение атрибута genre, совпадающее с novel
 XPathNodeIterator iter=nav.Select("/bookstore/book[@genre='novel']");
 while(iter.MoveNext()) {
  LoadBook(iter.Current);
 }
}
private void LoadBook(XPathNavigator lstNav) {
 // Нам передали XPathNavigator определенного узла book,
 // мы выберем всех прямых потомков и
 // загрузим окно списка с именами и значениями
 XPathNodeIterator iterBook=lstNav.SelectDescendants(XPathNodeType.Element, false);
 while(iterBook.MoveNext())
listBox1.Items.Add(iterBook.Current.Name + ": " + iterBook.Current.Value);
}

Здесь сначала создается XPathDocument, передавая строку файла и пути доступа документа, который будет открыт. В следующей строке кода создается XPathNavigator:

XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator();

Отметим, что здесь происходит преобразование типа интерфейса IXPathNavigable в только что созданный XPathNavigator, что вызывает метод CreateNavigator. После создания объекта XPathNavigator можно начать навигацию в документе.

Этот пример показывает, как применяются методы Select для получения множества узлов, которые имеют novel в качестве значения атрибута genre. Затем мы используем цикл MoveNext() для итераций по всем novels в списке книг.

Для загрузки данных в listbox используется свойство XPathNodeIterator.Current. При этом создается новый объект XPathNavigator на основе узла, на который указывает XPathNodeIterator. В данном случае создается XPathNavigator для одного узла book (книги) в документе. LoadBook создает другой XPathNodeIterator, вызывая иной тип метода выбора — метод SelectDescendants. Это даст нам XPathNodeIterator всех узлов-потомков и потомков узлов-потомков узла book (книга), переданного в метод LoadBook. Мы делаем другой цикл MoveNext() на этом XPathNodeIterator и загружаем окно списка именами и значениями элементов.

XPathNavigator содержит все методы для перемещения и выбора элементов, которые могут понадобиться. Приведем некоторые из методов перемещения:

Имя метода Описание
MoveTo Получает в качестве параметра XPathNavigator. Делает текущей позицию, которая указана в XPathNavigator.
MoveToAttribute Перемещает к именованному атрибуту. Получает имя атрибута и пространство имен как параметры.
MoveToFirstAttribute Перемещает к первому атрибуту текущего элемента. Возвращает true, если выполняется успешно.
MoveToNextAttribute Перемещает к следующему атрибуту текущего элемента. Возвращает true, если выполняется успешно.
MoveToFirst Перемещает к первому sibling текущего узла. Возвращает true, если выполняется успешно, в противном случае возвращает false.
MoveToLast Перемещает к последнему sibling текущего узла. Возвращает true, если выполняется успешно.
MoveToNext Перемещает к следующему sibling текущего узла. Возвращает true, если выполняется успешно.
MoveToPrevious Перемещает к предыдущему sibling текущего узла. Возвращает true, если выполняется успешно.
MoveToFirstChild Перемещает к первому потомку текущего элемента. Возвращает true, если выполняется успешно.
MoveToId Перемещает к элементу с идентификатором ID, предоставленным в виде параметра. Должна существовать схема документа и данные элемента типа ID.
MoveToParent Перемещает к предку текущего узла. Возвращает true, если выполняется успешно.
MoveToRoot Перемещает к корневому узлу документа.

Существует также несколько методов Select выбора подмножества узлов для работы. Все методы Select возвращают объект XPathNodeIterator. XPathNodeIterator можно считать эквивалентом NodeList или NodeSet в XPath. Этот объект имеет три свойства и два метода:

Clone — создает новую копию себя

Count — число узлов в объекте XPathNodeIterator

Current — возвращает XPathNavigator, указывающий на текущий узел

CurrentPosition — возвращает целое число, соответствующее текущей позиции

MoveNext — перемещает в следующий узел, соответствующий выражению Xpath, которое создало XPathNodeIterator

Можно использовать также существующие методы SelectAncestors и SelectChildren. Они возвращают XPathNodelterator. В то время, как Select получает выражение XPath в качестве параметра, другие методы выбора получают в качестве параметра XPathNodeType. В рассматриваемом примере мы выбираем все узлы XPathNodeType.Element.

Вот как выглядит экран после выполнения кода. Обратите внимание, что все перечисленные книги являются романами (novel).


Для добавления стоимости книг XPathNavigator содержит метод Evaluate. Evaluate имеет три перегружаемые версии. Первая из них содержит строку, которая является вызовом функции XPath. Вторая перегружаемая версия Evaluate использует в качестве параметра объект XPathExpression, третья — XPathExpression и XPathNodeIterator. Сделаем следующие изменения в примере (эту версию кода можно найти в XPathXSLSample2):

private void button1_Click(object sender, System.EventArgs e) {
 //изменить в соответствии со структурой путей доступа
 XPathDocument doc=new XPathDocument("......booksxpath.XML");
 //создать XPathNavigator
 XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator();
 //создать XPathNodeIterator узлов book,
 // которые имеют novel значением атрибута genre
 XPathNodeIterator iter=nav.Select("/bookstore/book[@genre="novel']");
 while(iter.MoveNext()) {
  LoadBook(iter.Current.Clone());
 }
 // добавим разделительную линию и вычислим сумму
 listBox1.Items.Add("========================");
 listBox1.Items.Add("Total Cost = "
  + nav.Evaluate("sum(/bookstore/book[@genre='novel']/price)"));
}

При этом вывод изменится следующим образом:


XslTransform

Пространство имен System.Xml.Xsl содержит классы XSL, применяемые .NET. XslTransform может использоваться с любым хранилищем, которое реализует интерфейс IXPathNavigable. В настоящее время на платформе .NET это: XmlDocument, XmlDataDocument и XPathDocument. Так же как и в случае XPath, воспользуйтесь тем хранилищем, которое подходит лучшим образом. Если планируется создание заказного хранилища, такого как файловая система, и желательно иметь возможность выполнять преобразования, не забудьте реализовать в классе интерфейс IXPathNavigable.

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

В первом примере, который мы рассмотрим, берется документ books.xml и преобразуется в простой документ HTML для вывода. (Этот код можно найти в папке XPathXSLSample3.) Необходимо будет добавить следующие операторы using:

using System.IO;
using System.Xml.Xsl;
using System.Xml.XPath;

Вот код, выполняющий преобразование:

private void button1_Click(object sender System.EventArgs e) {
 //создать новый XPathDocument
 XPathDocument doc=new XPathDocument("......booksxpath.XML");
 // создать новый XslTransForm
 XslTransform transForm=new XslTransform();
 transForm.Load("......books.xsl");
 // этот FileStream будет нашим выводом
 FileStream fs=new FileStream("......booklist.html", FileMode.Create);
 // Создать Navigator
 XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator();
 // Выполнить преобразование. Файл вывода создается здесь.
 transForm.Transform(nav, null, fs);
}

Сделать это преобразование проще почти невозможно. Сначала создается объект на основе XPathDocument и объект на основе XslTransform. Затем файл bookspath.xml загружается в doc, a books.xsl в transForm. В этом примере для записи нового документа HTML на диск создается объект FileStream.

Если бы это было приложение ASP.NET, мы использовали бы объект TextWriter и передавали бы его в объект HttpResponse. Если бы мы преобразовывали в другой документ XML, то применялся бы объект на основе XmlWriter. После того как объекты XPathDocument и XslTransform будут готовы, мы создаем XPathNavigator на doc и передаем nav и этот stream в метод Transform объекта transForm. XslTransform имеет несколько перегружаемых версий, получающих комбинации навигаторов, XsltArgumentList (подробнее об этом позже) и потоков ввода/вывода. Параметром навигатора может быть XPathNavigator или любой объект, реализующий интерфейс IXPathNavigable. Потоки ввода/вывода могут быть TextWriter, Stream или объектом на основе XmlWriter.

Документ books.xsl является таблицей стилей. Документ выглядит следующим образом:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
  <html>
   <head>
    <title>Price List</title>
   </head>
   <body>
    <table>
     <xsl:apply-templates/>
    </table>
   </body>
  </html>
 </xsl:template>
 <xsl:template match="bookstore">
  <xsl:apply-templates select= "book"/>
 </xsl:template>
 <xsl:template match="book">
  <tr><td>
   <xsl:value-of select="title"/>
  </td><td>
   <xsl:value-of select="price"/>
  </td></tr>
 </xsl:template>
</xsl:stylesheet>

Ранее упоминался объект XsltArgumentList. Это способ, которым можно объект с методами связать с пространством имен. Когда это сделано, можно вызывать методы во время преобразования. Рассмотрим пример, чтобы понять, как это работает (находится в XPathXSLSample4):

private void button1_Click(object sender, System.EventArgs e) {
 // новый XPathDocument
 XPathDocument doc=new XPathDocument("......booksxpath.xml");
 // новый XslTransform
 XslTransform transForm=new XslTransform();
 transForm.Load("......booksarg.xsl");
 // новый XmlTextWriter, так как мы создаем новый документ xml
 XmlWriter xw=new XmlTextWriter(......argSample.xml", null);
 // создать XslArgumentList и новый объект BookUtils
 XsltArgumentList argBook=new XsltArgumentList();
 BookUtils bu=new BookUtils();
 // это сообщает список аргументов BookUtils
 argBook.AddExtensionObject("urn:ProCSharp", bu);
 // новый XPathNavigator
 XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator();
 // выполнить преобразование
 transForm.Transform(nav, argBook, xw);
 xw.Close();
}
// простой тестовый класс
public class BookUtils {
 public BookUtils() {}
 public string ShowText() {
  return "This came from the ShowText method!";
 }
}

Вывод преобразования (argSample.xml) выглядит так:

<?xml version="1.0"?>
<books>
 <discbook>
  <booktitle>The Autobiography of Benjamin Franklin</booktitle>
  <showtext>This came from the ShowText method!</showLext>
 </discbook>
 <discbook>
  <booktitle>The Confidence Man</booktitle>
  <showtext>This came from the ShowText method!</showtext>
 </discbook>
 <discbook>
  <booktitle>The Gorgias</booktitle>
  <showtext>This came from the ShowText method!</showtext>
 </discbook>
 <discbook>
  <booktitle>The Great Cookie Caper</booktitle>
  <showtext>This came from the ShowText method!</showtext>
 </discbook>
 <discbook>
  <booktitle>A Really Great Book</booktitle>
  <showtext>This came from the ShowText method!</showtext>
 </discbook>
</books>

Определим новый класс BookUtils. В этом классе мы имеем один практически бесполезный метод, который возвращает строку "This came from the ShowText method!". Для события button1_Click создаются XPathDocument и XslTransform так же, как это делалось раньше, но с некоторыми исключениями. В этот раз мы собираемся создать документ XML, поэтому используем XMLWriter вместо FileStream. Вот эти изменения:

XsltArgumentList argBook=new XsltArgumentList();
BookUtils bu=new BookUtils();
argBook.AddExtensionObject("urn:ProCSharp", bu);

Именно здесь создается XsltArgumentList. Мы создаем экземпляр объекта BookUtils, и когда вызывается метод AddExtensionObject, ему передается пространство имен расширения и объект, из которого мы хотим вызывать методы. Когда делается вызов Transform, ему передаются XsltArgumentList(argBook) вместе с XPathNavigator и созданный объект XmlWriter. Вот документ booksarg.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:bookutil="urn:ProCSharp">
 <xsl:output method="xml" indent="yes"/>
 <xsl:template match="/">
  <xsl:element name="books">
   <xsl:apply-templates/>
  </xsl:element>
 </xsl:template>
 <xsl:template match="bookstore">
  <xsl:apply-templates select="book"/>
 </xsl:template>
 <xsl:template match="book">
  <xsl:element name="discbook">
   <xsl:element name="booktitle">
    <xsl:value-of select="title"/>
   </xsl:element>
   <xsl:element name="showtext">
    <xsl:value-of select="bookUtil:ShowText()"/>
   </xsl:element>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

Здесь имеются две важные строки. В начале добавляется пространство имен, которое создается при добавлении объекта к XsltArgumentList. Затем применяется стандартный синтаксис использования префикса перед пространством имен XSLT и вызывается метод.

Иначе это можно было бы выполнить с помощью сценария XSLT. В таблицу стилей можно включить код C#, VB и JavaScript. Большим достоинством этого является то, что в отличие от текущих реализаций, сценарий компилируется при вызове Transform.Load; таким образом выполняются уже откомпилированные сценарии, в значительной степени так же, как работает ASP.NET. Давайте выполним предыдущий пример таким способом. Добавим сценарий к таблице стилей. Эти изменения можно увидеть в файле bookscript.xsl:

<xsl:stylesheet version="1.0" xmlns:Xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://wrox.com">
 <msxsl:script language="C#" implements-prefix="user">
  string ShowText() {
   return "This came from the ShowText method!";
  }
 </msxsl:script>
 <xsl:output method="xml" indent="yes"/>
 <xsl:template match="/">
  <xsl:element name="books">
   <xsl:apply-templates/>
  </xsl:element>
 </xsl:template>
 <xsl:template match="bookstore">
  <xsl:apply-templates select="book"/>
 </xsl:template>
 <xsl:template match="book">
  <xsl:element name="discbook">
   <xsl:element name="booktitle">
    <xsl:value-of select="title"/>
   </xsl:element>
   <xsl:element name="showtext">
    <xsl:value-of select="user:ShowText()"/>
   </xsl:element>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

Изменения включают задание пространства имен сценариев, добавление кода (который скопирован из VS.NET IDE) и выполнение вызова в таблице стилей. Вывод выглядит так же, как и в предыдущем примере.

Ключевой момент, о котором необходимо помнить при выполнении преобразований, состоит в том, чтобы не забыть использовать подходящее хранилище; XPathDocument, если не требуется редактирование, XmlDataDocument, если данные получают из ADO.NET, и XmlDocument, если необходимо иметь возможность редактировать данные. Процесс будет таким же, несмотря ни на что.

Оглавление книги

Оглавление статьи/книги

Генерация: 2.614. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз