Книга: 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
, если необходимо иметь возможность редактировать данные. Процесс будет таким же, несмотря ни на что.
- Разбор XPath-выражений
- 15.1.3. XPath и другие интерфейсы
- Почему в версии 1.1 .NET Compact Framework не поддерживаются XSLT и XPath?
- Шаги расположения XPath, часть 1: оси
- Шаги расположения XPath, часть 2: условия узлов
- Логические функции XPath
- Функции XPath для работы со строками
- XPath 2.0
- Глава 7 Работа с XPath
- Изучаем XPath
- Числа XPath
- Применение технологии XPATH