XSLT

Холзнер Стивен

Глава 5

Принятие решений и сортировка данных

 

 

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

Однако эти элементы не предоставляют такой точности, как в языках программирования. Поэтому я также представлю в этой главе расширения XSLT, в том числе элемент рабочего проекта XSLT 1.1 . Этот элемент был предназначен для упрощения применения Java и JavaScript с процессором XSLT. (Для чтения этой книги нет необходимости владеть Java или JavaScript, но если вы знаете эти языки, вам будет приятно удостовериться, что некоторые процессоры XSLT дают возможность использовать их при преобразованиях XML.) Нечто похожее на этот элемент обязательно появится в XSLT 2.0. При помощи расширений вы можете расширять спецификацию XSLT, добавляя в XSLT новые элементы и функции какого-либо производителя или свои собственные.

Кроме того, в этой главе мы также рассмотрим, как перенумеровать элементы в документе, что делать в случае, когда ваш процессор XSLT не поддерживает определенное расширение, и многое другое. Я начну с наиболее часто используемого элемента из рассматриваемых в данной главе: .

 

Элемент <xsl:if>

При помощи элемента осуществляются проверки условия и принимаются действия на основе результата проверки. Он во многом похож на оператор if в языках программирования. У элемента один атрибут:

• test (обязательный). Устанавливается в значение логического (Boolean, true/false) условия, которое вы хотите проверить.

Элемент заключает в себе тело шаблона.

Вот как это работает: вы включаете тело шаблона внутрь элемента , проверяющего какое-то выражение. Если это выражение истинно, тело шаблона используется, если ложно — игнорируется:

 

Можно проверять любое выражение XPath. Для преобразования его в значения true/false в элементе применяйте следующие правила:

• если выражение вычисляется в набор узлов, оно трактуется как true, когда набор узлов содержит хотя бы один узел;

• выражение-строка считается true, если строка не пуста;

• фрагмент результирующего дерева трактуется как true, если содержит узлы;

• если результат выражения — число, он считается true, когда отличен от нуля.

Элемент во многом похож на оператор if-then в языках программирования. Однако не существует оператора для формирования конструкций if-then-else — для этого служит элемент .

В листинге 5.1 я перечисляю планеты в planets.xml одну за другой и добавляю горизонтальное правило HTML, элемент


(horizontal rule), после последнего элемента — но только после последнего. При помощи это можно сделать так.

Листинг 5.1. Применение <xsl:if>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

     </p> <p class="paragraph">      Planets </p> <p class="paragraph">     

   

   

   

  

  

 

 

  

  

   is planet number from the sun.

 

 


 

Вот результат — как видите, элемент


появляется только после последней перечисленной планеты:

 

   </p> <p class="paragraph">    Planets </p> <p class="paragraph">  

 

 

  

   Mercury is planet number 1 from the sun.

 

  

   Venus is planet number 2 from the sun.

 

  

   Earth is planet number 3 from the sun.

 

  


 

Рассмотрим еще один пример — преобразование XML-XML, в котором перечисляются планеты из planets.xml. Однако я хочу, чтобы выводилось не просто «The first three planets are: Mercury Venus Earth» (первые три планеты: Меркурий Венера Земля), a «The first three planets are: Mercury, Venus, and Earth». Необходимые знаки пунктуации можно добавить, определяя текущий элемент при помощи функции position и проверяя позицию при помощи (листинг 5.2).

Листинг 5.2. Второй пример применения <xsl:if>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

  

  ,

  and

  .

 

Вот результат:

  </p> <p class="paragraph">   The Planets </p> <p class="paragraph">  

 

  The first three planets are: Mercury, Venus, and Earth

 

Как видите, я смог добавить правильные знаки пунктуации, определяя место в документе при помощи .

При помощи можно также обнаруживать ошибки во время преобразования. Например, при помощи можно вывести сообщение, есть ли в planets.xml элемент (листинг 5.3).

Листинг 5.3. Обнаружение ошибок при помощи <xsl:if>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

 

   

    Each planet must have a name!

   

  

 

  .

  and

  .

 

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

 

  Mercury

  .0553

  58.65

  1516

  .983

  43.4

 

 

 

  .815

  116.75

  3716

  .943

  66.8

 

 .

 .

 .

Вот как происходит обработка примера в Xalan:

C:\planets>java org.apache.xalan.xslt.Process -IN planets.xml -XSL errors.xsl -OUT new.xml

file:///C:/XSL/w.xsl: Line 18: Column 38: Each planet must have a name!

XSLT Error (javax.xml.transform.TransformerException): Stylesheet directed termination

Если вы знакомы с конструкцией if в языках программирования, вы знаете, что инструкция if обычно сопровождается инструкцией else, которая выполняется при ложности условия в if. Но в XSLT нет элемента . Для задания альтернативных ветвей выполнения XSLT служит элемент .

 

Элементы <xsl:choose>, <xsl:when> и <xsl:otherwise>

Элемент похож на оператор Java switch, который позволяет сравнивать значение условия с несколькими возможными вариантами.

У элемента нет атрибутов. Он содержит один или более элементов и (не обязательно) один элемент , который в случае применения должен стоять последним.

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

 

 

 

 

 

 

 

 

 

 

 

 

В предыдущем разделе для осуществления этого преобразования нам потребовалось три элемента :

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

 

   

    Each planet must have a name!

   

  

 

  ,

     .

 

Теперь то же самое можно сделать при помощи единственного элемента :

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

 

   

    Each planet must have a name!

   

  

 

  

   .

   .

   .

 

 

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

• test (обязательный). Принимает логическое (Boolean) значение (true/false) проверяемого условия.

Элемент содержит тело шаблона.

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

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

 

   

    Each planet must have a name!

   

  

 

  

   ,

   and

   .

   .

   .

  

 

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

У элемента нет атрибутов, и он содержит тело шаблона. Вот как это выглядит в листинге 5.4.

Листинг 5.4. Применение <xsl:choose>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

    The first three planets are:

   

  

 

 

 

   

    Each planet must have a name!

   

  

 

 

   ,

   and

   .

 

 

Вот как это работает; этот код дает тот же результат, что и код, проверяющий позицию элементов при помощи :

  </p> <p class="paragraph">   The Planets </p> <p class="paragraph">  

 

  The first three planets are: Mercury, Venus, and Earth.

 

Вот еще один пример преобразования XML-XML. В этом случае я преобразую planets.xml в новый XML-документ, сохраняя только название каждой планеты и добавляя описание:

  </p> <p class="paragraph">   The Planets </p> <p class="paragraph">  

 

  

   Mercury

   Hottest

  

  

   Venus

   Hot

  

  

   Earth

   OK

  

 

Это преобразование можно реализовать, выбирая значение каждого элемента , то есть заключенный в нем текст (заметьте, что такого рода строки в XSLT учитывают регистр) (листинг 5.5).

Листинг 5.5. Второй пример <xsl:choose>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

    </p> <p class="paragraph">     The Planets </p> <p class="paragraph">    

   

   

   

  

 

 

 

   

    Each planet must have a name!

   

  

  

   

   

   

   

    

     Hottest

     Hot

     OK

    

   

  

 

Вот и все.

Предположим теперь, что нам нужно добавить в каждый элемент атрибут COLOR:

 

  Mercury

  .0553

  58.65

  1516

  .983

  43.4

 

 

  Venus

  .815

  116.75

  3716

  .943

  66.8

 

 

  Earth

  1

  1

  2107

  1

  128.4

 

Отобразить названия различных планет при помощи элемента , отформатированные по-разному при помощи тегов HTML <В>, и в зависимости от значения атрибута COLOR, можно следующим образом (листинг 5.6).

Листинг 5.6. Форматирование при помощи <xsl:choose>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

     </p> <p class="paragraph">      Planets </p> <p class="paragraph">     

   

   

   

   

  

 

 

  

  

    <В>

    

   

   

  

    

    

    

   

  

    

    

   

   

   

   

                              

   

   

  

 

Вот результирующий документ:

 

   </p> <p class="paragraph">    Planets </p> <p class="paragraph">   

 

 

  Mercury

  Venus

  Earth

 

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

 

Элемент <xsl:for-each>

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

<XSL:FOR-EACH> ПРОТИВ <XSL:APPLY-TEMPLATES>

Вы могли заметить, что это описание практически такое же, как и у элемента <xsl:apply-templates>, и я сравню элементы <xsl:for-each> и <xsl:apply-templates> через несколько страниц.

У элемента один атрибут:

• select (обязательный). Принимает значение выражения XPath, возвращающее набор узлов, который нужно обработать в цикле.

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

В теле шаблона функция position возвращает позицию текущего узла в наборе узлов, a last возвращает число узлов в наборе. Если не используется, узлы обрабатываются в порядке документа (в порядке, в котором они перечислены в документе); если же используется элемент , набор узлов будет сначала отсортирован в порядке, заданном этим элементом.

Предположим, нам нужно отформатировать все названия планет, заключив их в элементы HTML <Р>, — это можно сделать следующим образом:

 <Р>

 

 

Но что делать, если у некоторых планет по два названия, как, например:

 Mercury

 Closest planet to the sun

 .0553

 58.65

 1516

 .983

 43.4

Это проблема, поскольку атрибут select элемента сам по себе выберет только первый элемент . Чтобы пройти в цикле все возможные варианты, вместо него следует применить элемент (листинг 5.7).

Листинг 5.7. Применение <xsl:for-each>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

  

 

 

  

   

   

   

  

 

Эта таблица стилей охватывает все элементы , помещает их значения в элемент <Р> и добавляет их в выходной документ следующим образом:

 

Mercury

 

Closest planet to the sun

 

Venus

 

Earth

Вот еще один пример, впервые появившийся в главе 3, «Создание и применение шаблонов», где при помощи элемента в цикле перебирались все атрибуты элемента:

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

  

   

   

   

 

 

Следующий пример появился в главе 2, «Создание и применение таблиц стилей». Это упрощенная таблица стилей, в которой нельзя использовать какие-либо элементы высокого уровня, то есть нельзя использовать или , однако можно пройти по узлам в цикле при помощи :

 

   </p> <p class="paragraph">    The Planets Table </p> <p class="paragraph">   

 

 

  

   The Planets Table

  

 

  

   

    

    

    

  

  

    

     

    

    

     

    

   

  

Name Mass Radius Day

 

Эта упрощенная таблица стилей форматирует planets.xml в planets.html практически так же хорошо, как и шаблон, использующий , в связи с чем появляется интересный вопрос: когда следует для прохода по узлам применять , а когда ?

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

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

Листинг 5.8. Второй пример <xsl:for-each>

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

  

    

    

      

      

      

     

    

   

  

 

И вот результат:

 

  Mercury

  .0553

  58.65

  1516

  .983

  43.4

 

 

  Venus

  .815

  116.75

  3716

  .943

  66.8

 

 

  Earth

  1

  1

  2107

  1

  128.4

 

 

Сортирующие элементы

 

При помощи элемента можно сортировать узлы. Этот элемент устанавливает порядок обработки узлов для и . В следующем списке перечислены атрибуты :

• select (необязательный). Принимает значение выражения XPath, возвращающего набор узлов для сортировки. По умолчанию — «string(.)»;

• order (необязательный). Задает порядок сортировки, устанавливается в «ascending» (по возрастанию) или «descending» (по убыванию);

• case-order (необязательный). Определяет, будут ли буквы в верхнем регистре располагаться перед буквами в нижнем регистре. Устанавливается в «upper-first» (сначала верхний) или «lower-first» (сначала нижний);

• lang (необязательный). Задает язык, чьи соглашения о сортировке будут применяться. Устанавливается в код языка, допустимый в атрибуте xml:lang;

• data-type (необязательный). Определяет, будет ли сортировка вестись в алфавитном или числовом порядке. Устанавливается в «text» (текст), «number» (число) или в QName.

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

В листинге 5.9 я только отсортирую элементы в planets.xml в возрастающем алфавитном порядке по их именам, используя в упрощенной таблице стилей.

Листинг 5.9. Сортировка данных

 

   </p> <p class="paragraph">    The Sorted Planets Table </p> <p class="paragraph">   

 

 

  

   The Sorted Planets Table

  

 

  

   

    

   

    

  

  

    

    

    

    

    

     

    

   

  

Name Mass Radius Day

 

А вот результат. Обратите внимание на то, что планеты действительно отсортированы как Earth, Mercury и затем Venus:

 

  </p> <p class="paragraph">    The Sorted Planets Table </p> <p class="paragraph">   

 

 

  

   The Sorted Planets Table

  

 

   

   

    

    

    

   

   

   

    

    

    

   

   

   

    

    

    

   

   

   

    

    

    

   

  

Name Mass Radius Day
Earth 1 2107 1
Mercury .0553 1516 58.65
Venus .815 3716 116.75

 

Вид документа показан на рис. 5.1.

Рис. 5.1. Сортировка при помощи упрощенного шаблона

При помощи атрибута select можно указать, что нужно сортировать. Например, таким образом можно отсортировать планеты по плотности (листинг 5.10).

Листинг 5.10. Сортировка планет по плотности

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

     </p> <p class="paragraph">      Planets </p> <p class="paragraph">     

   

   

   

Planets sorted by density

    

     

     

      

      

      

     

    

     

     

    

Planet Mass Day Density

  

  

 

 

  

   

  

   

   

  

 

Вот результаты этого преобразования:

 

   </p> <p class="paragraph">    Planets </p> <p class="paragraph">   

 

 

  

   Planets sorted by density

 

  

  

   

    

    

    

   

  

   

    

   

    

   

  

   

    

    

    

   

   

    

   

    

    

  

  

Planet Mass Day Density
Venus .815 116.75 .943
Mercury .0553 58.65 .983
Earth 1 1 1

 

По умолчанию производит сортировку в алфавитном порядке, что означает, что «10» располагается перед «2». Для осуществления сортировки в числовом порядке установите атрибут типа данных в «number»:

Убывающий порядок сортировки задается установкой у элемента атрибута order в «descending». Можно также сортировать по значениям атрибута, например:

 

НОВОЕ В XSLT 2.0

Одним из важных преимуществ XSLT 2.0 является поддержка схем XML, и W3C планирует внести в XSLT 2.0 возможность сортировки по любому типу данных, определенному в схеме документа, так же, как сейчас можно сортировать по строкам или числам.

 

Сортировка по нескольким критериям

Стоит отметить, что при сортировке можно применять несколько критериев — для этого просто примените несколько элементов . Первый элемент  отсортирует данные по основному критерию, следующий элемент — по следующему основному критерию и т.д. Вот, например, как внутри элемента можно отсортировать планеты сначала по расстоянию, а затем по плотности (при этом планеты с одинаковым расстоянием от Солнца будут отсортированы по плотности):

 

 

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

 

Элемент <xsl:number>

 

При помощи элемента узлу в результирующем документе присваивается очередной номер. Например, можно перенумеровать абзацы в контракте или строфы в поэме. Можно даже перенумеровать части документа несколькими уровнями, как, например, «Paragraph 3.2.5.1.» и т.п.

Элемент обладает следующими атрибутами:

• level (необязательный). Определяет, как будут присваиваться последовательные числа. Устанавливается в «single» (один), «multiple» (несколько) или «any» (любой). Значение по умолчанию — «single»;

• count (необязательный). Определяет, какие узлы нужно подсчитывать. Устанавливается в образец;

• from (необязательный). Определяет точку начала отсчета. Устанавливается в образец;

• value (необязательный). Форматируемое число;

• format (необязательный). Определяет формат вывода. Устанавливается в шаблон значений атрибута, возвращающий строку форматирования;

• lang (необязательный). Определяет язык, чьи соглашения следует использовать для нумерации. Устанавливается в код языка, который можно применять в атрибуте xml:lang;

• letter-value (необязательный). Позволяет выбрать различные схемы нумерации. устанавливается в «alphabetical» (алфавитная) или «traditional» (обычная);

• grouping-separator (необязательный). Символ для разделения групп разрядов — например, запятая. Устанавливается в шаблон значений атрибутов, возвращающий единственный символ;

• grouping-size (необязательный). Количество разрядов в каждой группе — определяет место применения разделителя групп разрядов. Устанавливается в шаблон значений атрибутов, возвращающий число. 

СОВЕТ ПО НУМЕРАЦИИ

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

Существует три основных способа нумерации, в зависимости от установки атрибута уровня (level): «single», «multiple» или «any». В следующих разделах мы по очереди рассмотрим каждую из этих схем, начав с одноуровневой нумерации, которая установлена по умолчанию.

 

Одноуровневая нумерация

Одноуровневая нумерация — это простая нумерация, когда перенумеровываются узлы-братья на одном уровне. Этот тип нумерации установлен по умолчанию. В листинге 5.11 при помощи одноуровневой нумерации перенумеровываются планеты в planets.xml.

Листинг 5.11. Одноуровневая нумерация

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

     </p> <p class="paragraph">      The Planets Table </p> <p class="paragraph">     

   

   

    

     The Planets Table

    

   

     

     

      

      

      

     

    

    

Name Mass Radius Day

   

  

 

 

  

   .

  

   

  

  

 

 

  

  

  

 

 .

 .

 .

 

 

  

  

 

А вот результат:

 

   </p> <p class="paragraph">    The Planets Table </p> <p class="paragraph">   

 

 

  

   The Planets Table

  

 

   

   

    

    

    

   

  

   

   

    

    

   

  

   

    

    

    

   

  

   

   

    

    

   

  

Name Mass Radius Day
1. Mercury .0553 (Earth = 1) 1516 miles 58.65 days
2. Venus .815 (Earth = 1) 3716 miles 116.75 days
3. Earth 1 (Earth = 1) 2107 miles 1 days

 

Этот результат показан на рис. 5.2.

Рис. 5.2. Одноуровневая нумерация элементов 

По умолчанию при нумерации используются числа, но есть и другие возможности. Например, если бы я использовал , планетам были бы присвоены буквы a, b и с:

 

   </p> <p class="paragraph">    The Planets Table </p> <p class="paragraph">   

 

 

  

   The Planets Table

  

 

   

   

    

    

    

   

  

   

   

    

    

   

  

   

   

    

   

   .

   .

   .

Ниже приведены возможные в атрибуте format лексемы и соответствующие им виды нумерации:

• 1 порождает последовательность 1, 2, 3…;

• 01 порождает последовательность 01, 02, 03…;

• другие числа Unicode в других системах счисления порождают последовательности аналогично двум предыдущим правилам;

• а порождает последовательность a, b, с…, аа, ab…;

• А порождает последовательность А, В, С…, АА, АВ…;

• i порождает последовательность i, ii, iii, iv…, ix, x, xi, xii…;

• I порождает последовательность I, II, III, IV…, IX, X, XI, XII…

 

Нумерация на произвольных уровнях

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

Вот как примерно это будет выглядеть, если в planets.xml элементы встречаются на различных уровнях:

 

   </p> <p class="paragraph">    <NAME>Planets Table</NAME> </p> <p class="paragraph">   

  

   Mercury

   .0553

   58.65

   1516

   .983

   43.4

  

 

   Venus

   .815

   116.75

   3716

   .943

   66.8

  

  .

  .

  .

Чтобы сосчитать общее число элементов , в таблице стилей можно установить атрибут level в «any» (листинг 5.12).

Листинг 5.12. Нумерация на произвольных уровнях

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

  

   

     </p> <p class="paragraph">      The Planets Table </p> <p class="paragraph">     

   

   

    

     The Planets Table

    

   

    

Name Mass Radius Day
a. Mercury .0553 (Earth = 1) 1516 miles 58.65 days
b. Venus .815 (Earth = 1) 3716 miles 116.75 days

     

     

      

      

      

    

    

   

Name Mass Radius Day

   

  

 

 

  

  

   

   

   

  

 

 

 

 

 

  .

 

 

  

  

  

 

 .

 .

 .

 

  

  

  

 

Получаем результат (заметьте, что текст каждого элемента , независимо от его уровня в документе, пронумерован):

 

   </p> <p class="paragraph">    The Planets Table </p> <p class="paragraph">   

 

 

  

   The Planets Table

  

  1. Planets Table

  

   

   

    

    

    

   

  

   

   

    

    

   

  

   

    

    

    

   

  

   

    

    

    

   

 

Name Mass Radius Day
2. Mercury .0553 (Earth = 1) 1516 miles 58.65 days
3. Venus .815 (Earth = 1) 3716 miles 116.75 days
4. Earth 1 (Earth = 1) 2107 miles 1 days

 

При помощи атрибута from можно указать, с какого узла-предка начинать отсчет; например, если установить узел-предок в элемент так:

то процессор XSLT осуществит обратный просмотр только до первого предка и начнет нумерацию с этой точки документа.

 

Многоуровневая нумерация

Элемент также поддерживает многоуровневую нумерацию — такую как 3.1.2.5 и т. п. Для работы с ней нужно установить атрибут level в «multiple». При помощи атрибута count можно указать, узлы какого типа вы хотите нумеровать, установив этот атрибут в образец, например: "PART|CHAPTER|PARAGRAPH". При обработке элементов процессор XSLT нумерует узлы в соответствии с иерархией документа.

В примере я нумерую каждый уровень в иерархии элементов planets.xml, установив атрибут count в «*» для выбора всех элементов. Можно также указать формат нумерации при помощи атрибута format. При многоуровневой нумерации атрибут format задает формат для различных уровней, например «1.1.1.» задает нумерацию 1., 2., … и т.д. для узлов верхнего уровня, 1.1., 1.2., … и т.д. для узлов уровнем ниже и 1.2.1., 1.2.2., … и т. д. для следующего уровня вниз. Вот как выглядит таблица стилей для этого примера в листинге 5.13.

Листинг 5.13. Многоуровневая нумерация

<хsl:stylesheet version="1.0"

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 

 

  

  

   

  

 

Вот результат преобразования planets.xml в новый XML-документ, в котором перенумерованы все уровни элементов в соответствии с иерархией документа:

1.

 1.1.

  1.1.1. Mercury

  1.1.2. .0553

  1.1.3. 58.65

  1.1.4. 1516

  1.1.5. .983

  1.1.6. 43.4

 

 1.2.

  1.2.1. Venus

  1.2.2. .815

  1.2.3. 116.75

  1.2.4. 3716

  1.2.5. .943

  1.2.6 66.8

 

 1.3.

  1.3.1. Earth

  1.3.2. 1

  1.3.3. 1

  1.3.4. 2107

  1.3.5. 1

  1.3.6. 128.4

 

На этом мы завершаем рассмотрение нумерации документов и переходим к последней теме этой главы — расширяемости XSLT.

 

Расширяемость XSLT

Несмотря на кажущуюся сложность XSLT, он во многих отношениях ограничен по сравнению с языками программирования, и в процессорах XSLT сразу же начали появляться расширения XSLT. Например, Saxon представил элемент , реализуя в XSLT стандартный для программирования цикл while (до тех пор, пока). Xalan представил такие элементы, как , для поддержки вывода нескольких документов. А процессор MSXML3 от Microsoft позволяет писать функции на языках таких сценариев, как JavaScript, и затем вызывать их и выполнять их код.

Можно представить, с каким беспокойством на это смотрит W3C. Его работа, в принципе, заключается в стандартизации работы таких языков, как XSLT, но производители постоянно представляли свои собственные, нестандартные расширения в виде новых элементов и функций. С другой стороны, W3C не может предугадать все новые элементы и функции, поэтому консорциум начал работать над стандартизацией способов включения функций расширения и элементов в XSLT. Расширения должны удовлетворять ряду общих правил:

• расширения должны использовать пространства имен во избежание конфликтов с элементами XSL;

• процессор XSLT должен быть в состоянии распознать применение расширения — и в случае ошибки расширения реагировать хорошо определенным способом;

• таблица стилей должна быть в состоянии проверить, доступно ли определенное расширение, и если нет, вернуться назад.

НОВОЕ В XSLT 2.0

Легко представить сложности W3C даже с этими общими правилами, и комитет XSLT 2.0 собирается исследовать возможность реализации всех расширений на «чистом» XSLT, вообще не прибегая к каким-либо внешним языкам программирования.

W3C разрешил расширения двух видов, главным образом, потому, что они уже были приняты де-факто — функции расширения и элементы расширения. Хотя они пользуются популярностью, это весьма неясная область, поскольку различные производители представили разные способы их реализации.

В XSLT 1.0 проверить доступность функции расширения можно при помощи функции function-available, а доступность элемента расширения — при помощи функции element-available.

XSLT 2.0 готовится определить стандартные средства связывания XSLT с элементами расширения и, вероятно, они будут гораздо лучше проработаны, чем имеющиеся сейчас.

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

ИНИЦИАТИВА EXSLT

Теперь, после того, как механизмы расширения в рабочем проекте XSLT 1.1 были отложены до XSLT 2.0, роль других разнообразных попыток стандартизации расширений XSLT значительно повысилась. Познакомьтесь, например, с EXSLT на www.exslt.org. EXSLT — это инициатива открытого сообщества, работающего над стандартизацией расширений XSLT.

 

Функции расширения

 

В XSLT 1.0 W3C определил способ отделения функций расширения от встроенных функций, установив требование, чтобы для обращения к функциям расширения использовались имена с заданным пространством имен, как в starpowder:calculate(). В XSLT 1.0 также имеется функция function-available() для проверки наличия функции по ее имени.

В рабочем проекту XSLT 1.1 на функции расширения были наложены некоторые дополнительные ограничения:

• функции расширения должны работать как встроенные функции;

• для Java и ECMAScript должны быть реализованы привязки к языку;

• механизм должен позволять естественное расширение для поддержки других языков в будущем;

• для реализации переносимой привязки функции расширения для любого конкретного языка не должен быть нужен процессор;

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

• должны быть разрешены как встроенные реализации функций расширения, так и внешние;

• в функции расширения должно быть возможно передавать аргументы всех типов данных XPath;

• функции расширения должны иметь возможность возвращать в качестве результата все типы данных XPath;

• функции расширения должны иметь возможность создавать и возвращать наборы узлов фрагментов XML;

• должна иметься возможность включать или импортировать функции расширения из другой таблицы стилей;

• при неоднозначности выбора реализации функции расширения процессор должен выдать ошибку и прекратить работу;

• процессор должен преобразовывать аргументы способом, согласованным со встроенными функциями;

• функции расширения должны быть способны вернуть объект любого типа основного языка;

• должна существовать возможность передать в функцию расширения объект любого типа основного языка.

Вплоть до недавнего времени процессоры XSLT полностью самостоятельно определяли способ реализации функций расширения. Например, в Saxon и Xalan существует возможность непосредственно выполнять код Java, если определить пространство имен, задающее класс Java в качестве последней части URI. Я поступил так в следующем случае, определив пространство имен Date, соответствующее классу Java Date:

 xmlns:xsl=http://www.w3.org/1999/XSL/Transform

 xmlns:Date="http://www.saxon.com/java/java.util.Date">

 .

 .

 .

После этого я могу воспользоваться такими функциями класса Date Java, как toString и new, для того чтобы заключить текущую дату в элементы заголовка <Н1> HTML в выходном документе (листинг 5.14).

Листинг 5.14. Применение функций Date Java

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

 xmlns:Date="http://www.saxon.com/java/java.util.Date">

 

  

  

     </p> <p class="paragraph">      The Planets Table </p> <p class="paragraph">     

   

   

    

     The Planets Table

    

   

    

    

    

    

    

     

     

     

     

    

Name Mass Radius Day

   

  

 

 

  

  

   

   

   

  

 

 

 

 

 

  

 

 

 

 

Результат применения этой функции приведён на рис. 5.3.

Рис. 5.3. Применение функции расширения 

Вот работоспособная схема и заодно веский повод включить Java в XSLT. Тем не менее, в XSLT 1.1 был представлен элемент , который, по всей видимости, будет включен и в XSLT 2.0.

 

Элемент <xsl:script>

Элемент был определен в рабочем проекте XSLT 1.1, он предоставляет хорошо определенный способ связи функций расширения с таблицами стилей XSLT. Это элемент верхнего уровня, обладающий следующими атрибутами:

• implements-prefix (необязательный). Задает имя пространства имен функции расширения, которую реализует этот элемент. Принимает значение NCNAME;

• language (необязательный). Задает язык, используемый функцией расширения. Устанавливается в «ecmascript» (стандарт JavaScript), «javascript», «java» или QNAME, не являющееся NCNAME;

• src (необязательный). Предоставляет URI, в котором реализована функция расширения. Например, это может быть класс Java;

• archive (необязательный). Задает архивы, которые необходимо загрузить перед запуском функции расширения, если они есть. Принимает значения списка URI, разделенного символами-разделителями.

Элемент содержит символьные данные (Microsoft использует раздел CDATA), реализующие функцию или функции расширения.

Как теперь связать функцию, определенную в элементе , с вашей таблицей стилей XSLT? Сначала создайте в своей таблице стилей элемент как элемент верхнего уровня, затем поместите в него функции, которые вы хотите определить. В приведенном ниже примере я определяю две функции JavaScript, makeMoney (сделать деньги) и makeMoreMoney (сделать еще больше денег), в элементе , реализующем пространство имен расширений «starpowder»:

function makeMoney(e) {

 .

 .

 .

}

function makeMoreMoney(e) {

 .

 .

 .

}

В зависимости от вашего процессора XSLT, может оказаться хорошим решением заключить такого рода сценарии в раздел CDATA:

 

  function makeMoney(e) {

   .

   .

   .

  }

  function makeMoreMoney(e) {

   .

   .

   .

  }

 ]]>

Теперь при помощи пространства имен «starpowder» можно указать, что вызывается функция расширения:

 

Вот и все (если ваш процессор XSLT это поддерживает). Если вместо сценария вы хотите указать класс Java, воспользуйтесь атрибутом src:

 

РАБОТА С ВНЕШНИМИ РЕСУРСАМИ

Атрибут src также используется, если есть архив подпрограмм JavaScript, как, например, src="archives.js".

Из всех известных мне процессоров XSLT элемент реализует только процессор Microsoft MSXML3. Информация о работе со сценариями для написания функций расширения для Internet Explorer приведена на web-узле Microsoft (в данный момент это страница по адресу http://msdn.microsoft.com/xml/xslguide/script-overview.asp, но, кажется, Microsoft меняет структуру web-узла каждые два дня или около того).

Следующий пример демонстрирует работу с Internet Explorer. Я создал функцию JavaScript для преобразования данных о радиусе планет из planets.xml, приведенных в милях, в километры и последующем выводе этих данных в километрах.

Как обсуждалось в главе 2 в разделе «Преобразование документов XML при помощи Internet. Explorer», для просмотра XML-документа, использующего таблицу стилей XSL, в Internet Explorer, версии 5.5 и младше в документ необходимо внести некоторые изменения (если только вы не установили последний разборщик MSXML или не используете недавно появившуюся версию браузера 6.0, хотя и в этом случае нужно применять «text/xsl»). Для начала в таблице стилей XSL используйте тип MIME «text/xsl», а не «text/xml». Я также задал URI для таблицы стилей «kilometers.хsl» следующим образом (листинг 5.15).

Листинг 5.15. Установка использования kilometers.xsl для planets.xml в Internet Explorer

 

  Mercury .0553

  58.65

  1516

  .983

  43.4

 

 

  Venus

  .815

  116.75

  3716

  .943

  66.8

 

 .

 .

 .

Для преобразования таблицы стилей kilometers.xsl для работы в IE 5.5 или младше я воспользовался пространством имен XSL, которое использует IE, и добавил элемент , показав, что я собираюсь писать сценарии на JavaScript. Заметьте, однако, что элемент в Internet Explorer не поддерживает атрибут префикса реализации, поэтому я не могу связать функции, определенные в пространстве имен:

 

  .

  .

  .

 

 .

 .

 .

В соответствии с требованиями Internet Explorer, код должен быть заключен в раздел CDATA. Здесь я определил функцию milesToKilometers, которая принимает узел, читает текст узла в свойстве text и преобразует текст в число миль при помощи функции JavaScript parseInt. Далее я умножаю число миль на 1,6, чтобы получить километры, и возвращаю результат:

 

  

   function milesToKilometers(e) {

    miles = parseInt(e.text);

    return miles * 1.6;

   }

  ]]>

 

 .

 .

 .

Поскольку пока в Internet Explorer нельзя связать пространство имен с функцией расширения, для их вызова используется специальный элемент Microsoft . Ниже показано, как это выглядит в таблице стилей kilometers.xsl, где я передаю в функцию milesToKilometers текущий узел для преобразования миль в километры. Поскольку IE 5.5 и младше не поддерживают правила по умолчанию (хотя версия 6.0, вышедшая одновременно с подписанием этой книги в печать, поддерживает их, и вам не нужно ничего менять), для этих браузеров я предоставил правило для корневого узла (листинг 5.16).

Листинг 5.16. kilometers.xsl

 

  

   function milesToKilometers(e) {

    miles = parseInt(e.text);

    return miles * 1.6;

   }

  ]]>

 

 

  

  

    </p> <p class="paragraph">      The Planets Table </p> <p class="paragraph">     

   

   

    

     The Planets Table

   

   

     

     

      

      

      

     

    

    

Name Mass Radius Day

   

  

 

 

 

 

 

  

  

   

   

   

  

 

 

  milesToKilometers(this)

 

Вот и все, результат этого преобразования приведен на рис. 5.4.

Рис. 5.4. Применение функции расширения в Internet Explorer

Со временем производители будут поставлять все больше и больше функций расширения. Как можно определить, доступна ли заданная функция расширения? Для этого служит функция function-available.

 

Применение функции function-available

Функция XSLT 1.0 function-available служит для проверки доступности функции. В следующем примере я хочу воспользоваться функцией расширения starpowder:calculate для математических вычислений, а если она недоступна, я отправляю в результирующий документ текст «Sorry, can't do math today.» (Извините, сегодня математические вычисления не работают.), хотя можно, конечно, прекратить обработку и вывести сообщение об ошибке при помощи элемента :

 

 

 

 

  Sorry, can't do math today.

 

 

Внешние объекты

В рабочем проекте XSLT 1.1 для поддержки функций расширения появился новый тип данных — внешний объект (external object). Переменной XSLT, о которой пойдет речь в главе 9, может быть присвоен внешний объект — так же, как и один из четырех типов данных XPath, поддерживаемых в XSLT (строка, число, логическое значение, набор узлов). Внешний объект представляет объект, который создается внешним языком программирования, возвращается функцией расширения и не может быть преобразован в один из четырех типов данных XPath. Тип данных «external object» был добавлен в XSLT для того, чтобы предоставить вам безопасную «оболочку» для таких данных. Пока еще никто не реализовал поддержку внешних объектов, но это ожидается в скором времени.

 

Элементы расширения

 

Элементы расширения — это элементы, добавленные в XSLT пользователем или производителем. В рабочем проекте XSLT 1.1 для элементов расширения был установлен ряд правил, и в XSLT 2.0 предполагается более широкая их поддержка. 

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

Для определения пространства имен расширений применяется атрибут extension-element-prefixes в элементе , или атрибут xsl:extension-element-prefixes в элементе буквального результата или элементе расширения.

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

 

  Mercury

  .0553

  58.65

  1516

  .983

  43.4

 

 .

 .

 .

Теперь в таблице стилей XSLT, которую я назвал redirect.xsl, я определяю пространство имен «redirect» так, чтобы оно соответствовало классу Java, который поддерживает ее в Xalan: org.apache.xalan.lib.Redirect. Я также устанавливаю атрибут extension-element-prefixes элемента в значение пространства имен «redirect»:

 version="1.0"

 xmlns:lxslt=http://xml.apache.org/xslt"

 xmlns:redirect="org.apache.xalan.lib.Redirect"

 extension-element-prefixes="redirect">

 .

 .

 .

В этот момент мне ничто не мешает применить элемент расширения для записи вывода в новый файл (в отличие от указанного в командной строке). Например, для того, чтобы отправить в другой файл отформатированное содержимое элемента , я могу получить имя создаваемого файла из атрибута file элемента и записать данные в этот новый файл:

 version="1.0"

 xmlns:lxslt="http://xml.apache.org/xslt"

 xmlns:redirect="org.apache.xalan.lib.Redirect"

 extension-element-prefixes="redirect">

 

 

 

 

 

 

 

 

 

   

   

   

  

 

 

  

  

  

 

Операция завершена; вот как это может выглядеть при использовании Xalan в Windows:

C:planets>java org.apache.xalan.xslt.Process -IN planets.xml -XSL redirect.xsl -OUT new.xml

При этом будет создан файл redirected.xml, который выглядит следующим образом:

 

  Mercury

  .0553

  58.65

  1516

  .983

  43.4

 

 

  Venus

  .815

  116.75

  3716

  .943

  66.8

 

 .

 .

 .

 

Применение функции element-available

Для проверки доступности элемента служит функция XSLT 1.0 element-available. В следующем примере я проверяю наличие элемента с названием :

 

 

 

 

  Sorry, can't do math today.

 

Есть еще один способ обработать случай отсутствия элемента расширения — элемент .

 

Элемент <xsl:fallback>

При помощи элемента XSLT 1.0 можно указать, что следует делать в случае отсутствия элемента расширения. Этот элемент заключается в элемент расширения и используется в случае его недоступности.

У элемента нет атрибутов, он содержит тело шаблона.

В следующем примере я создам элемент внутри элемента из предыдущего примера. В случае отсутствия элемента элемент прекратит обработку и выдаст сообщение:

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

 version="1.0"

 xmlns:lxslt="http://xml.apache.org/xslt"

 xmlns:redirect="org.apache.xalan.lib.Redirect"

 extension-element-prefixes="redirect">

 

 

 

 

 

 

 

 

  

   

   

   

   

   

     Could not create multiple output documents.

    

  

 

 

 

  

  

  

 

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