Главная » XSLT » Отображение иерархии

0

Задача

Требуется вывести текст с отступами или аннотациями, чтобы показать иерархическую структуру исходного XML-документа.

Решение

Самый очевидный способ представить иерархию – сформировать с помощью пробелов отступы различной ширины. В примерах 7.29 и 7.30 приведена обобщенная таблица стилей, в которой приняты разумные допущения о преобразовании инфор­мации, хранящейся во входном документе, в иерархически организованный текст.

Пример 7.29. text.hierarchy.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings">

<xsl:include href="../strings/str.dup.xslt"/>

<xsl:include href="../strings/str.replace.xslt"/>

<xsl:output method="text"/>

<!– По умолчанию ширина отступа на каждом уровне — два пробела –>

<xsl:param name="indent" select=" ‘ ‘ "/>

<xsl:template match="*">

<xsl:param name="level" select="count(./ancestor::*)"/>

<!– Сделать отступ для этого элемента –> <xsl:call-template name="str:dup" >

<xsl:with-param name="input" select="$indent"/> <xsl:with-param name="count" select="$level"/> </xsl:call-template>

<!– Обработать имя элемента. По умолчанию выводится локальное имя —> <xsl:apply-templates select="." mode="name">

<xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<!– Начинаем обрабатывать атрибуты. По умолчанию выводится ‘(‘ –> <xsl:apply-templates select="." mode="begin-attributes">

<xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<!– Обработать атрибуты. По умолчанию выводятся в формате имя="значение". –> <xsl:apply-templates select="@*">

<xsl:with-param name="element" select="."/> <xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<!– Заканчиваем обработку атрибутов. По умолчанию выводится ‘)’ –> <xsl:apply-templates select="." mode="end-attributes">

<xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<!– Обработать значение элемента. –>

<!– По умолчанию значение листового элемента выводится в новой строке с отступом — >

<xsl:apply-templates select="." mode="value">

<xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<xsl:apply-templates select="." mode="line-break">

<xsl:with-param name="level" select="$level"/> </xsl:apply-templates>

<xsl:apply-templates select="*">

<xsl:with-param name="level" select="$level + 1"/> </xsl:apply-templates>

</xsl:template>

<!– Обработка имен элементов по умолчанию. –> <xsl:template match="*" mode="name">[<xsl:value-of

select="local-name(.)"/></xsl:template>

<!– Обработка начала атрибутов по умолчанию. –> <xsl:template match="*" mode="begin-attributes">

<xsl:if test="@*"><xsl:text> </xsl:text></xsl:if> </xsl:template>

<!– Обработка атрибутов по умолчанию. –> <xsl:template match="@*">

<xsl:value-of select="local-name(.)"/>="<xsl:value-of

select="."/>"<xsl:text/>

<xsl:if test="position() != last()">

<xsl:text> </xsl:text> </xsl:if> </xsl:template>

<!– Обработка конца атрибутов по умолчанию. –> <xsl:template match="*" mode="end-attributes">]</xsl:template>

<!– Обработка значений элементов по умолчанию. –> <xsl:template match="*" mode="value"> <xsl:param name="level"/>

<!– Выводим только значения листовых элементов –> <xsl:if test="not(*)">

<xsl:variable name="indent-str">

<xsl:call-template name="str:dup" > <xsl:with-param name="input" select="$indent"/> <xsl:with-param name="count" select="$level"/> </xsl:call-template> </xsl:variable>

<xsl:text>&#xa;</xsl:text>

<xsl:value-of select="$indent-str"/>

<xsl:with-param name="input" select="."/>

<xsl:with-param name="search-string" select=" ‘&#xa;’ "/> <xsl:with-param name="replace-string"

select="concat(‘&#xa;’,$indent-str)"/> </xsl:call-template> </xsl:if> </xsl:template>

<xsl:template match="*" mode="line-break">

<xsl:text>&#xa;</xsl:text> </xsl:template>

</xsl:stylesheet>

Пример 7.30. Результат обработки файла ExpenseReport.xml

[ExpenseReport statementNum="12 3"] [Employee] [Name]

Salvatore Mangano [SSN]

999-99-9999

[Dept]

XSLT Hacking [EmpNo] 1

[Position] Cook

[Manager]

Big Boss O’Reilly [PayPeriod] [From] 1/1/02 [To]

1/31/02 [Expenses] [Expense] [Date] 12/20/01 [Account] 12345 [Desc]

Goofing off instead of going to confrence. [Lodging] 500.00

[Transport] 50.00 [Fuel] 0

[Meals] 300.00 [Phone] 100

[Entertainment] 1000.00 [Other] 300.00 [Expense] [Date] 12/20/01 [Account] 12345 [Desc]

On the beach [Lodging] 500.00 [Transport] 50.00 [Fuel] 0

[Meals] 200.00 [Phone] 20

[Entertainment] 300.00 [Other] 100.00

XSLT 2.0

Если используется XSLT 2.0, то показанный выше код можно улучшить, вос­пользовавшись встроенной в XPath 2.0 функцией replace и показанной в рецеп­те 7.3 функцией dup.

Обсуждение

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

Пример 7.31. Модификация таблицы стилей для вывода отчета о расходах

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:import href="text.hierarchy.xslt"/>

<!– Игнopиpoвaть aтpибyты –> <xsl:template match="@*"/>

<xsl:template match="*" mode="begin-attributes"/> <xsl:template match="*" mode="end-attributes"/>

<xsl:template match="*" mode="name">

<!– Выводить локальное имя элемента –> <xsl:value-of select="local-name(.)"/>

<!– И, если он листовый, то еще двоеточие и пpoбeл –> <xsl:if test="not(*)">: </xsl:if> </xsl:template>

<xsl:template match="*" mode="value"> <xsl:if test="not(*)">

<xsl:value-of select="."/> </xsl:if> </xsl:template>

</xsl:stylesheet>

Пример 7.32. Результат применения модифицированной таблицы стилей

ExpenseReport Employee

Name: Salvatore Mangano SSN: 999-99-9999 Dept: XSLT Hacking EmpNo: 1 Position: Cook Manager: Big Boss O’Reilly PayPeriod

From: 1/1/02 To: 1/31/02 Expenses Expense

Date: 12/20/01 Account: 12345

Desc: Goofing off instead of going to confrence. Lodging: 500.00

Transport: 50.00 Fuel: 0 Meals: 300.00 Phone: 100

Entertainment: 1000.00 Other: 300.00 Expense

Date: 12/20/01 Account: 12345 Desc: On the beach Lodging: 500.00 Transport: 50.00 Fuel: 0 Meals: 200.00 Phone: 20

Entertainment: 300.00 Other: 100.00

А, быть может, вам придется по душе формат, показанный в примерах 7.33 и 7.34, на который меня натолкнула Джени Теннисон.

Пример 7.33. tree-control.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:import href="text.hierarchy.xslt"/>

<!– Игнорировать атрибуты –> <xsl:template match="@*"/>

<xsl:template match="*" mode="begin-attributes"/> <xsl:template match="*" mode="end-attributes"/>

<xsl:template match="*"   mode="name">

<!– Выводить локальное имя элемента –> <xsl:text>[</xsl:text>

<xsl:value-of select="local-name(.)"/>

<!– И, если он листовый, то еще двоеточие и пробел –> <xsl:text>] </xsl:text> </xsl:template>

<xsl:template match="*" mode="value"> <xsl:if test="not(*)">

<xsl:value-of select="."/> </xsl:if> </xsl:template>

<xsl:template match="*" mode="indent"> <xsl:for-each select="ancestor::*"> <xsl:choose>

<xsl:when test="following-sibling::*"> | </xsl:when> <xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise> </xsl:choose> </xsl:for-each> <xsl:choose>

<xsl:when test="*"> o-</xsl:when>

<xsl:when test="following-sibling::*"> +-</xsl:when> <xsl:otherwise> ‘-</xsl:otherwise> </xsl:choose> </xsl:template>

<xsl:template match="*" mode="line-break">

<xsl:text>&#xa;</xsl:text> </xsl:template>

</xsl:stylesheet>

Пример 7.34. Форматирование результата в виде дерева

o-[ExpenseReport] o-[Employee]

| +—[Name] Salvatore Mangano | +-[SSN] 999-99-9999 | +-[Dept] XSLT Hacking | +-[EmpNo] 1 | +— [Position] Cook | ‘-[Manager] Big Boss O’Reilly o-[PayPeriod] | +— [From] 1/1/02 | ‘-[To] 1/31/02 o-[Expenses] o-[Expense] | +— [Date] 12/20/01 | +-[Account] 12345

| +—[Desc] Goofing off instead of going to confrence.

| +— [Lodging] 500.00

| +— [Transport] 50.00

| +— [Fuel] 0

| +— [Meals] 300.00

| +— [Phone] 100

| +— [Entertainment] 1000.00

| ‘-[Other] 300.00 o-[Expense]

+— [Date] 12/20/01 +-[Account] 12345 +—[Desc] On the beach +— [Lodging] 500.00 +— [Transport] 50.00 +— [Fuel] 0 +— [Meals] 200.00 +— [Phone] 20

+— [Entertainment] 300.00 ‘-[Other] 100.00

Можно развить эту идею и создать таблицу стилей, которая будет импортиро­вать tree-controlxslt и принимать глобальный параметр, содержащий список имен элементов, для которых ветви дерева следует сворачивать. Свернутые ветви бу­дут обозначаться префиксом x. См. примеры 7.35 и 7.36.

Пример 7.35. Таблица стилей, создающая дерево со свернутыми ветвями

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="tree-control.xslt"/> <xsl:param name="collapse"/>

<xsl:variable name="collapse-test" select="concat(‘ ‘,$collapse,’ ‘)"/>

<xsl:template match="*"  mode="name">

<xsl:if test="not(ancestor::*[contains($collapse-test,

concat(‘ ‘,local-name(.),’ ‘))])"> <xsl:apply-imports/> </xsl:if> </xsl:template>

<xsl:template match="*" mode="value">

<xsl:if test="not(ancestor::*[contains($collapse-test,

concat(‘ ‘,local-name(.),’ ‘))])"> <xsl:apply-imports/> </xsl:if> </xsl:template>

<xsl:template match="*" mode="line-break">

<xsl:if test="not(ancestor::*[contains($collapse-test,

concat(‘ ‘,local-name(.),’ ‘))])"> <xsl:apply-imports/>

</xsl:if> </xsl:template>

<xsl:template match="*" mode="indent"> <xsl:choose>

<xsl:when test="self::*[contains($collapse-test, concat(‘ ‘,local-name(.),’ ‘))]"> <xsl:for-each select="ancestor::*">

<xsl:text> </xsl:text> </xsl:for-each> <xsl:text> x-</xsl:text> </xsl:when>

<xsl:when test="ancestor::*[contains($collapse-test, concat(‘ ‘,local-name(.),’ ‘))]"/> <xsl:otherwise>

<xsl:apply-imports/> </xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Пример 7.35. Результат вывода, когда $collapse="Employee PayPeriod"

o-[ExpenseReport] x-[Employee] x-[PayPeriod] o-[Expenses] o-[Expense] | +— [Date] 12/20/01 | +-[Account] 12345

| +—[Desc] Goofing off instead of going to confrence.

| +— [Lodging] 500.00

| +— [Transport] 50.00

| +— [Fuel] 0

| +— [Meals] 300.00

| +— [Phone] 100

| +— [Entertainment] 1000.00

| ‘-[Other] 300.00

o-[Expense]

+— [Date] 12/20/01 +-[Account] 12345 +— [Desc] On the beach +— [Lodging] 500.00

+— [Transport] 50.00 +— [Fuel] 0 +— [Meals] 200.00 +— [Phone] 20

+— [Entertainment] 300.00 ‘-[Other] 100.00

В общем, нет конца разнообразию древовидных представлений, которые можно создать путем переопределения базовой таблицы стилей. На объектно-ориентиро­ванном жаргоне этот паттерн называется Template Method. Смысл его в том, что в базовом классе создается скелет алгоритма, а в подклассах переопределяются не­которые его шаги, оставляя общую структуру алгоритма неизменной. В случае XSLT роль подклассов отводится импортирующим таблицам стилей. Приводя этот пример, я не хотел убедить вас, что вывод дерева – трудное дело, вовсе нет. Истин­ная красота этого кода в том, что он позволяет повторно использовать одну и ту же конструкцию, уделяя внимание лишь тем аспектам, которые вы хотите изменить.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии