Главная » XSLT » Создание отчета с несколькими колонками

0

Задача

Требует представить данные в виде таблицы с несколькими колонками.

Решение

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

Прежде чем переходить к изучению этих двух вариантов, напишем общий шаблон для выравнивания текста в колонке фиксированной ширины. Такую ути­литу (см. пример 7.19) можно написать на базе шаблона str:dup из рецепта 2.5.

Пример 7.19. Общий шаблон выравнивания текста text.justify.xslt

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

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

<xsl:template name="text:justify"> <xsl:param name="value" /> <xsl:param name="width" select="10"/> <xsl:param name="align" select=" ‘left’ "/>

<!– Обрезать, если строка слишком длинная –>

<xsl:variable name="output" select="substring($value,1,$width)"/>

<xsl:choose> <xsl:when test="$align = ‘left’"> <xsl:value-of select="$output"/> <xsl:call-template name="str:dup">

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

select="$width – string-length($output)"/> </xsl:call-template> </xsl:when>

<xsl:when test="$align = ‘right’"> <xsl:call-template name="str:dup">

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

select="$width – string-length($output)"/> </xsl:call-template> <xsl:value-of select="$output"/> </xsl:when>

<xsl:when test="$align = ‘center’"> <xsl:call-template name="str:dup">

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

select="floor(($width – string-length($output)) div 2)"/> </xsl:call-template> <xsl:value-of select="$output"/>

<xsl:call-template name="str:dup">

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

select="ceiling(($width – string-length($output)) div 2)"/> </xsl:call-template> </xsl:when>

<xsl:otherwise>INVALID ALIGN</xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Имея такой шаблон, для создания поколонного отчета достаточно задать лишь порядок и разметку колонок. В примерах 7.20 и 7.21 это сделано для файла people.xml, где данные о сотрудниках представлены в виде атрибутов. Аналогично можно было бы поступить для файла people_elem.xml, где данные закодированы в элементах.

Пример 7.20. people-to-columns.xslt

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

<xsl:include href="text.jstify.xslt"/>

<xsl:output method="text" />

<xsl:strip-space elements="*"/>

<xsl:template match="people">

Name             Age Sex          Smoker

<xsl:apply-templates>

<xsl:template match="person">

<xsl:call-template name="text:justify"> <xsl:with-param name="value" select="@name"/> <xsl:with-param name="width" select="2 0"/> <xsl:call-template> <xsl:text>|</xsl:text>

<xsl:call-template name="text:justify"> <xsl:with-param name="value" select="@age"/> <xsl:with-param name="width" select="6"/> <xsl:with-param name="align" select=" ‘right’ "/> <xsl:call-template>

<xsl:text>|</xsl:text>

<xsl:call-template name="text:justify">

<xsl:with-param name="value"  select="@sex"/>

<xsl:with-param name="width"  select="6"/>

<xsl:with-param name="align"  select=" ‘center’ "/> <xsl:call-template> <xsl:text>|</xsl:text>

<xsl:call-template name="text:justify">

<xsl:with-param name="value"  select="@smoker"/>

<xsl:with-param name="width"  select="9"/>

<xsl:with-param name="align"  select=" ‘center’ "/> <xsl:call-template> <xsl:text> </xsl:text> </xsl:template>

</xsl:stylesheet>

Пример 7.21. Результат

Чтобы преобразовать данные на основе позиции в документе, к задаче нужно подойти несколько иначе. Во-первых, решите, сколько должно быть колонок. Можно задать число колонок с помощью параметра, а число строк вычислить, ис­ходя из количества элементов. А можно задать число строк, тогда количество ко­лонок будет переменным. Во-вторых, определите, как позиция элемента будет отображаться на номер колонки. Есть два распространенных способа: сначала по строкам или сначала по колонкам. В первом случае первый элемент помещается в первую колонку, второй – во вторую и так далее, пока колонки не кончатся; в этот момент начинается новая строка. Во втором случае первые (N div число-ко­лонок) элементов помещаются в первую колонку, следующие (N div число-коло­нок) элементов – во вторую и т.д. Иными словами, строки и колонки меняются местами – транспонируются.

В примере 7.22 приведены два шаблона – для вывода отчета по строкам и по колонкам.

Пример 7.22. text.matrix.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:text="http://www.ora.com/XSLTCookbook/namespaces/text" extension-element-prefixes="text">

<xsl:output method="text"/>

<xsl:include href="text.justify.xslt"/>

<xsl:template name="text:row-major">

<xsl:param name="nodes" select="/.."/> <xsl:param name="num-cols" select="2"/> <xsl:param name="width" select="10"/> <xsl:param name="align" select=" ‘left’ "/> <xsl:param name="gutter" select=" ‘ ‘ "/>

<xsl:if test="$nodes">

<xsl:call-template name="text:row"> <xsl:with-param name="nodes"

select="$nodes[position() &lt; = $num-cols]"/> <xsl:with-param name="width" select="$width"/> <xsl:with-param name="align" select="$align"/> <xsl:with-param name="gutter" select="$gutter"/> </xsl:call-template>

<!– обработать остальные строки –> <xsl:call-template name="text:row-major"> <xsl:with-param name="nodes"

select="$nodes[position() > $num-cols]"/> <xsl:with-param name="num-cols" select="$num-cols"/> <xsl:with-param name="width" select="$width"/> <xsl:with-param name="align" select="$align"/> <xsl:with-param name="gutter" select="$gutter"/> </xsl:call-template> </xsl:if>

</xsl:template>

<xsl:template name="text:col-major">

<xsl:param name="nodes" select="/.."/> <xsl:param name="num-cols" select="2"/> <xsl:param name="width" select="10"/> <xsl:param name="align" select=" ‘left’ "/> <xsl:param name="gutter" select=" ‘ ‘ "/>

<xsl:if test="$nodes">

<xsl:call-template name="text:row"> <xsl:with-param name="nodes"

select="$nodes[(position() – 1) mod

ceiling(last() div $num-cols) = 0]"/> <xsl:with-param name="width" select="$width"/> <xsl:with-param name="align" select="$align"/> <xsl:with-param name="gutter" select="$gutter"/> </xsl:call-template>

<!– Обработать остальные колонки –> <xsl:call-template name="text:col-major"> <xsl:with-param name="nodes"

select="$nodes[(position() – 1) mod

ceiling(last() div $num-cols) != 0]"/> <xsl:with-param name="num-cols" select="$num-cols"/> <xsl:with-param name="width" select="$width"/> <xsl:with-param name="align" select="$align"/> <xsl:with-param name="gutter" select="$gutter"/> </xsl:call-template> </xsl:if>

</xsl:template>

<xsl:template name="text:row">

<xsl:param name="nodes" select="/.."/> <xsl:param name="width" select="10"/> <xsl:param name="align" select=" ‘left’ "/> <xsl:param name="gutter" select=" ‘ ‘ "/>

<xsl:for-each select="$nodes">

<xsl:call-template name="text:justify">

<xsl:with-param name="value" select="."/> <xsl:with-param name="width" select="$width"/> <xsl:with-param name="align" select="$align"/> </xsl:call-template> <xsl:value-of select="$gutter"/> </xsl:for-each>

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

</xsl:template>

</xsl:stylesheet>

Этими шаблонами можно воспользоваться, как показано в примерах 7.23 – 7.25. Пример 7.23. Входные данные

<numbers>

<number>10</number> <number>3.5</number> <number>4.4 4</number> <number>77.777 7</number> <number>-8</number> <number>1</number> <number>4 4 4</number> <number>1.12 34</number> <number>7.7 7</number> <number>3.1415 92 7</number> <number>10</number> <number>9</number> <number>8</number> <number>7</number> <number>6 6 6</number> <number>555 5</number> <number>-4 4 4 4 4 4 4</number> <number>22.33</number> <number>18</number> <number>36.54</number> <number>4 3</number>

<number>9 9 9 9 9</number> <number>9 9 9 9 9 9</number> <number>9 9 9 9 9 9 9</number> <number>32</number> <number>64</number> <number>-64.0 0 01</number> </numbers>

Пример 7.24. Таблица стилей

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

<xsl:output method="text" />

<xsl:include href="text.matrix.xslt"/>

<xsl:template match="numbers">

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

<xsl:text/>

<xsl:call-template name="text:row-major">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="align" select=" ‘right’ "/> <xsl:with-param name="num-cols" select="5"/> <xsl:with-param name="gutter" select=" ‘ | ‘ "/> </xsl:call-template>

Пять колонок чисел, выведенных по колонкам: <xsl:text/>

<xsl:call-template name="text:col-major">

<xsl:with-param name="nodes" select="number"/> <xsl:with-param name="align" select=" ‘right’ "/> <xsl:with-param name="num-cols" select="5"/> <xsl:with-param name="gutter" select=" ‘ | ‘ "/> </xsl:call-template>

</xsl:template>

</xsl:stylesheet>

Пример 7.25. Результат

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

10 |          3.5 |         4.44 |      77.7777 |            -8 |

1 |          4 4 4 |     1.12 34 |         7.77 |3.1415927 |

10   |             9 |             8 |             7 |           666 |

5555   |      -4444444 |         22.33 |            18 |         3 6.54 |

43   |         99999 |        999999 |9999999 |      32 |

64   |      -64.0001 |

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

10   |           444 |             8 |            18 |            32 |

3.5   |       1.12 34 |             7 |         36.54 |           64 |

4.44   |          7.77 |         6 6 6 |            43 | -64.0001 |

77.7777           | 3.1415927     | 5555          | 99999     |

-8   |            10 |      -4444444 |        999999 |

1   |             9 |         22.33 |       9999999  |

XSLT 2.0

В XSLT 2.0 можно превратить шаблон text:justify в функцию и сделать его более компактным за счет новых средств XPath 2.0 – это, пожалуй, основное улучше­ние, которого можно добиться. С помощью функции string-join в сочетании с выражением for создадим функцию dup, которая будет вставлять нужное для вы­равнивания число пробелов. Функцию text:justify можно перегрузить, чтобы добиться эффекта параметра по умолчанию, определяющего способ выравнивания:

<xsl:function name="text:dup" as="xs:string"> <xsl:param name="input" as="xs:string"/> <xsl:param name="count" as="xs:integer"/>

<xsl:sequence select="string-join(for $i in 1 to $count return $input, ”)"/> </xsl:function>

<xsl:function name="text:justify" as="xs:string"> <xsl:param name="value" as="xs:string"/> <xsl:param name="width" as="xs:integer" />

<xsl:sequence select="text:justify($value, $width, ‘left’)"/> </xsl:function>

<xsl:function name="text:justify" as="xs:string"> <xsl:param name="value" as="xs:string"/> <xsl:param name="width" as="xs:integer" /> <xsl:param name="align" as="xs:string" />

<!– Обрезать, если строка слишком длинная –> <xsl:variable name="output"

select="substring($value,1,$width)" as="xs:string"/> <xsl:variable name="offset"

select="$width – string-length($output)" as="xs:integer"/>

<xsl:choose>

<xsl:when test="$align = ‘left’">

<xsl:value-of select="concat($output, text:dup(‘ ‘, $offset))"/> </xsl:when>

<xsl:when test="$align = ‘right’">

<xsl:value-of select="concat(text:dup(‘ ‘, $offset), $output)"/> </xsl:when>

<xsl:when test="$align = ‘center’">

<xsl:variable name="before" select="$offset idiv 2"/> <xsl:variable name="after" select="$before + $offset mod 2"/> <xsl:value-of select="concat(text:dup(‘ ‘, $before),

$output,text:dup(‘ ‘, $after))"/>

</xsl:when>

<xsl:otherwise>INVALID ALIGN</xsl:otherwise> </xsl:choose> </xsl:function>

Обсуждение

Задача преобразования данных, закодированных в виде элементов или атри­бутов, в форму таблицы с несколькими колонками структурно похожа на задачу пре­образования в формат с разделителями полей, которая обсуждалась в рецепте 7.2. Основное различие в том, что во втором случае данные готовятся для машинной обработки, а в первом – для человека. В некоторых отношениях человек более привередлив, чем машина, особенно когда речь идет о выравнивании и других способах оформления, облегчающих визуальное восприятие. Можно было бы применить тот же таблично управляемый подход, что и для формата с разделите­лями полей, но для правильного форматирования придется задать больше инфор­мации о каждой колонке. В примерах 7.26 – 7.28 приведено такое решение для случая, когда данные кодируются в атрибутах.

Пример 7.26. generic-attr-to-columns.xslt

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

<xsl:include href="text.justify.xslt"/>

<xsl:param name="gutter" select=" ‘ ‘ "/> <xsl:output method="text"/> <xsl:strip-space elements="*"/> <xsl:variable name="columns" select="/.."/>

<xsl:for-each select="$columns">

<xsl:call-template name="text:justify" >

<xsl:with-param name="value" select="@name"/> <xsl:with-param name="width" select="@width"/> <xsl:with-param name="align" select=" ‘left’ "/> </xsl:call-template> <xsl:value-of select="$gutter"/> </xsl:for-each> <xsl:text>&#xa;</xsl:text> <xsl:for-each select="$columns"> <xsl:call-template name="str:dup">

<xsl:with-param name="input" select=" ‘-‘ "/> <xsl:with-param name="count" select="@width"/> </xsl:call-template> <xsl:call-template name="str:dup">

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

<xsl:with-param name="count" select="string-length($gutter)"/> </xsl:call-template> </xsl:for-each> <xsl:text>&#xa;</xsl:text> <xsl:apply-templates/> </xsl:template>

<xsl:template match="/*/*">

<xsl:variable name="row" select="."/>

<xsl:for-each select="$columns"> <xsl:variable name="value"> <xsl:apply-templates

select="$row/@*[local-name(.)=current()/@attr]" mode="text:map-col-value"/> </xsl:variable>

<xsl:call-template name="text:justify" >

<xsl:with-param name="value" select="$value"/> <xsl:with-param name="width" select="@width"/> <xsl:with-param name="align" select="@align"/> </xsl:call-template> <xsl:value-of select="$gutter"/> </xsl:for-each>

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

</xsl:template>

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

Пример 7.27. people-to-cols-using-generic.xslt

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

<xsl:import href="generic-attr-to-columns.xslt"/>

<!– Определяем отображение атрибутов на колонки –> <xsl:variable name="columns" select="document(”)/*/text:column"/>

<text:column name="Name" width="20" align="left" attr="name"/> <text:column name="Age" width="6" align="right" attr="age"/> <text:column name="Gender" width="6" align="left" attr="sex"/> <text:column name="Smoker" width="6" align="left" attr="smoker"/>

<!– Обрабатываем специальные отображения атрибутов –>

<xsl:template match="@sex" mode="text:map-col-value"> <xsl:choose>

<xsl:when test=".=’m”">male</xsl:when> <xsl:when test=".=’f’">female</xsl:when> <xsl:otherwise>ошибка</xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Пример 7.28. Результат (параметр gutter = " |")

Name       | Age     | Gender | Smoker |

Al Zehtooney     |   33        | male |       no      |

Brad York  |     38  | male     | yes  |

Charles Xavier   |   32        | male |       no      |

David Willimas   |   33        | male |       no      |

Edward Ulster    |   33        | male |       yes     |

Frank Townsend   |   35        | male |       no      |

Greg Sutter      |   40        | male |       no      |

Harry Rogers     |   37        | male |       no      |

John Quincy      |   43        | male |       yes     |

Kent Peterson    |   31        | male |       no      |

Larry Newell  |     23  | male     | no       |

Max Milton    |     22  | male     | no       |

Norman Lamagna      |   30         | male     |       no      |

Ollie Kensinton     |   44         | male     |       no      |

John Frank    |     24  | male     | no       |

Mary Williams |     33  | female   | no       |

Jane Frank    |     38  | female   | yes      |

Jo Peterson   |     32  | female   | no       |

Angie Frost   |     33  | female   | no       |

Betty Bates   |     33  | female   | no       |

Connie Date   |     35  | female   | no       |

Donna Finster |     20  | female   | no       |

Esther Gates  |     37  | female   | no       |

Fanny Hill    |     33  | female   | yes      |

Geta Iota     |     27  | female   | no       |

Hillary Johnson     |   22         | female   |       no      |

Ingrid Kent   |     21  | female   | no       |

Jill Larson   |     20  | female   | no       |

Kim Mulrooney |     41  | female   | no       |

Lisa Nevins   |     21  | female   | no       |

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

По теме:

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