Главная » XSLT » Экспорт XML в файл с разделителями полей

0

Задача

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

Решение

Многие приложения умеют импортировать данные с разделителями полей. Самый распространенный формат такого рода называется CSV (Comma Separated Values – значения, разделенные запятыми). Большинство электронных таблиц и баз данных могут обрабатывать этот формат и подобные ему. Отобразить XML на файл с разделителями полей может быть и очень просто, и довольно сложно; все зависит от структуры исходного документа. В этом разделе мы начнем с про­стых случаев и постепенно перейдем к более сложным.

Создание CSV-файла из плоского документа, в котором данные закодированы в атрибутах

В этом случае элементы отображаются на строки, а атрибуты – на колонки. Эта задача тривиальна. Таблица стилей, показанная в примере 7.7, преобразует документ people.xml из примера 7.6 в файл, представленный в примере 7.8.

Пример 7.6. Файл people.xml

<?xml version="1.0" encoding="UTF-8"?>

<people>

<person name="Al Zehtooney" age="33" sex="m" smoker="no"/> <person name="Brad York" age="38" sex="m" smoker="yes"/> <person name="Charles Xavier" age="32" sex="m" smoker="no"/> <person name="David Willimas" age="33" sex="m" smoker="no"/> <person name="Edward Ulster" age="33" sex="m" smoker="yes"/> <person name="Frank Townsend" age="35" sex="m" smoker="no"/> <person name="Greg Sutter" age="40" sex="m" smoker="no"/> <person name="Harry Rogers" age="37" sex="m" smoker="no"/> <person name="John Quincy" age="43" sex="m" smoker="yes"/> <person name="Kent Peterson" age="31" sex="m" smoker="no"/> <person name="Larry Newell" age="23" sex="m" smoker="no"/> <person name="Max Milton" age="22" sex="m" smoker="no"/> <person name="Norman Lamagna" age="30" sex="m" smoker="no"/> <person name="Ollie Kensington" age="44" sex="m" smoker="no"/> <person name="John Frank" age="24" sex="m" smoker="no"/> <person name="Mary Williams" age="33" sex="f" smoker="no"/> <person name="Jane Frank" age="38" sex="f" smoker="yes"/> <person name="Jo Peterson" age="32" sex="f" smoker="no"/> <person name="Angie Frost" age="33" sex="f" smoker="no"/> <person name="Betty Bates" age="33" sex="f" smoker="no"/> <person name="Connie Date" age="35" sex="f" smoker="no"/> <person name="Donna Finster" age="20" sex="f" smoker="no"/> <person name="Esther Gates" age="37" sex="f" smoker="no"/> <person name="Fanny Hill" age="33" sex="f" smoker="yes"/>

<person name="Geta Iota" age="27" sex="f" smoker="no"/> <person name="Hillary Johnson" age="22" sex="f" smoker="no"/> <person name="Ingrid Kent" age="21" sex="f" smoker="no"/> <person name="Jill Larson" age="20" sex="f" smoker="no"/> <person name="Kim Mulrooney" age="41" sex="f" smoker="no"/> <person name="Lisa Nevins" age="21" sex="f" smoker="no"/> </people>

Пример 7.7. Простое, но зависящее от конкретного входного документа преобразование в формат CSV

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:strip-space elements="*"/>

<xsl:template match="person">

<xsl:value-of select="@name"/>,<xsl:text/> <xsl:value-of select="@age"/>,<xsl:text/> <xsl:value-of select="@sex"/>,<xsl:text/> <xsl:value-of select="@smoker"/> <xsl:text>&#xa;</xsl:text> </xsl:template>

</xsl:stylesheet>

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

Al Zehtooney,33,m,no Brad York,38,m,yes Charles Xavier,32,m,no David Willimas,33,m,no Edward Ulster,33,m,yes Frank Townsend,35,m,no Greg Sutter,4 0,m,no

Решение-то простое, но хотелось бы написать обобщенную таблицу стилей, которую было бы легко настроить на любое преобразование такого вида. В приме­рах 7.9 – 7.10 приведено подобное решение и показано, как его можно применить к файлу people.xml.

Пример 7.9. generic-attr-to-csv.xslt

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

<xsl:param name="delimiter" select=" ‘,’ "/>

<xsl:output method="text" />

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

<xsl:template match="/">

<xsl:for-each select="$columns"> <xsl:value-of select="@name"/> <xsl:if test="position() != last()">

<xsl:value-of select="$delimiter/> </xsl:if> </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:apply-templates select="$row/@*[local-name(.)=current()/@attr]"

mode="csv:map-value"/>

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

<xsl:value-of select="$delimiter"/> </xsl:if> </xsl:for-each>

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

</xsl:template>

<xsl:template match="@*" mode="map-value">

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

</xsl:stylesheet>

Пример 7.10. Применение обобщенного решения к файлу people.xml

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

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

<!– Определяем отображение атрибутов на колонки –>

<xsl:variable name="columns" select="document(”)/*/csv:column"/>

<csv:column name="Name" attr="name"/> <csv:column name="Age" attr="age"/> <csv:column name="Gender" attr="sex"/> <csv:column name="Smoker" attr="smoker"/>

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

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

<xsl:when test=".=’m’">male</xsl:when> <xsl:when test=".=’f’">female</xsl:when> <xsl:otherwise>error</xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Это таблично управляемое решение. В общей таблице стилей generic-attr-to- csvxslt используется переменная, содержащая элементы csv:column, которые определены в импортирующей таблице, где нужно лишь перечислить элементы csv:column в том порядке, в котором они должны следовать в колонках выход­ного файла. Элементы csv:column определяют отображение между именован­ной колонкой и именем атрибута во входном XML-документе. Дополнительно импортирующая таблица стилей может транслировать значения некоторых ат­рибутов с помощью шаблона, соответствующего конкретному атрибуту. Этот шаб­лон запускается в режиме csv:map-value. В данном случае такой шаблон преоб­разует сокращенные обозначения пола в атрибуте @sex в полные слова. Часто используемые отображения такого рода можно поместить в третью таблицу стилей и тоже импортировать. Это решение удобно, потому что позволяет человеку, едва знакомому с XSLT, определить новое отображение XML в CSV. Дополнительный бонус в том, что в обобщенной таблице стилей определен параметр верхнего уров­ня, который позволяет заменить запятую в качестве разделителя другим символом.

Создание CSV-файла из плоского документа, в котором данные закодированы в элементах

Имеется плоский XML-файл, в котором элементы верхнего уровня отобража­ются на строки, а их дочерние элементы – на колонки.

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

Пример 7.11. Документ, в котором люди описываются с помощью элементов

<people> <person>

<name>Al Zehtooney</name> <age>33</age> <sex>m</sex> <smoker>no</smoker> </person> <person>

<name>Brad York</name> <age>38</age> <sex>m</sex> <smoker>yes</smoker> </person> <person>

<name>Charles Xavier</name> <age>32</age> <sex>m</sex> <smoker>no</smoker> </person> <person>

<name>David Willimas</name> <age>33</age> <sex>m</sex> <smoker>no</smoker> </person>

</people>

Пример 7.12. generic-elem-to-csv.xslt

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

<xsl:param name="delimiter" select=" ‘,’ "/>

<xsl:output method="text" />

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

<xsl:template match="/">

<xsl:for-each select="$columns"> <xsl:value-of select="@name"/> <xsl:if test="position() != last()">

<xsl:value-of select="$delimiter"/> </xsl:if> </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:apply-templates

select="$row/*[local-name(.)=current()/@elem]" mode="csv:map-value"/> <xsl:if test="position() != last()">

<xsl:value-of select="$delimiter"/> </xsl:if> </xsl:for-each>

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

</xsl:template>

<xsl:template match="node()" mode="map-value">

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

Пример 7.13. people-elem-to-csv.xslt

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

<xsl:import href="generic-elem-to-csv.xslt"/>

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

<csv:column name="Name" elem="name"/> <csv:column name="Age" elem="age"/> <csv:column name="Gender" elem="sex"/> <csv:column name="Smoker" elem="smoker"/>

</xsl:stylesheet>

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

Name,Age,Gender,Smoker Al Zehtooney,33,m,no Brad York,38,m,yes

Charles Xavier,32,m,no

David Willimas,33,m,no

Более сложные отображения

В следующем примере нам предстоит выполнить отображение произвольной комбинации атрибутов и элементов на строки и колонки. Здесь порядок докумен­та не так однозначно соответствует порядку строк и колонок. Кроме того, отобра­жение может быть разреженным, то есть в CSV-файле оказывается много пустых значений.

Рассмотрим, к примеру, следующий XML-документ, представляющий отчет о расходах сотрудника, которого в ближайшем будущем ожидает увольнение.

<ExpenseReport statementNum="12 3"> <Employee>

<Name>Salvatore Mangano</Name> <SSN>999-99-99 99</SSN> <Dept>XSLT Hacking</Dept> <EmpNo>1</EmpNo> <Position>Cook</Position> <Mangager>Big Boss O’Reilly</Mangager> </Employee> <PayPeriod>

<From>1/1/0 2</From> <To>1/31/02</To> </PayPeriod> <Expenses> <Expense>

<Date>12/20/01</Date> <Account>12 345</Account>

<Desc>Goofing off instead of going to confrence.</Desc>

<Lodging>5 0 0.0 0</Lodging>

<Transport>50.00</Transport>

<Fuel>0</Fuel>

<Meals>300.00</Meals>

<Phone>10 0</Phone>

<Entertainment>1000.00</Entertainment> <0ther>3 0 0.0 0</0ther> </Expense> <Expense>

<Date>12/20/01</Date> <Account>12 345</Account> <Desc>0n the beach</Desc> <Lodging>5 0 0.0 0</Lodging> <Transport>50.00</Transport>

<Fuel>0</Fuel> <Meals>2 0 0.0 0</Meals> <Phone>2 0</Phone>

<Entertainment>30 0.0 0</Entertainment> <0ther>10 0.0 0</0ther> </Expense> </Expenses> </ExpenseReport>

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

Рис. 7.1. Электронная таблица, содержащая отчет о расходах

Чтобы данные попали в нужные ячейки и осталось лишь применить стили, необходимо сгенерировать такой CSV-файл:

,,,,,,,,,,,,Statement No.,123, ,,,,,,,,,,,Expense Statement, ,,,Employee,,,,,,,,,Pay Period,

,,,Name,Salvatore Mangano,,Emp #,1,,,,,From,1/1/02, ,,,SSN,999-99-9999,,Position,Cook, ,,,Department,XSLT Hacking,,,,,,,,To,1/31/02,

,,,Date,Account,Description,Lodging,Transport,Fuel,Meals, Phone,Entertainment,Other,Total,

,,,12/2 0/01,12 345,Goofing off instead of going to

confrence.,500.00,50.00,0,300.

00,100,1000.00,300.00,

,,,12/20/01,12345,0n the

beach,500.00,50.00,0,200.00,20,300.00,100.00,Sub Total,

,,,Approved,,Notes,,,,,,,Advances,

,,,,,,,,,,,,Total,

Как видите, отображению XML на данные с разделителями не хватает едино­образия, из-за которого решения предыдущих примеров были такими простыми. Я не хочу сказать, что для нужного преобразования нельзя создать таблицу сти­лей. Просто, если подойти к задаче прямолинейно, то таблица получится сложной и не пригодной для повторного использования.

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

Взглянув на проблему с этой точки зрения, вы увидите, что задача преобразо­вания XML в электронную таблицу на самом деле сводится к задаче распределе­ния содержимого документа по ячейкам таблицы. Тогда в качестве промежуточ­ной формы можно выбрать такую, где каждой ячейке соответствует отдельный элемент. Например, элемент, который должен поместить значение «foo» в ячейку A1 должен быть таким: <cell col="A" row="1" value="foo"/>. Ваша цель – создать таблицу стилей, которая отобразит каждый значимый элемент входного документа на элемент, соответствующий ячейке. Поскольку теперь беспокоиться о порядке уже не нужно, то отображение получается простым:

<xsl:template match="ExpenseReport">

<c:cell col="M" row="3" value="Statement No."/> <c:cell col="N" row="3" value="{@statementNum}"/> <c:cell col="L" row="6" value="Expense Statement"/> <xsl:apply-templates/>

<xsl:variable name="offset" select="count(Expenses/Expense)+18"/> <c:cell col="M" row="{$offset}" value="Sub Total"/> <c:cell col="D" row="{$offset + 1}" value="Approved"/> <c:cell col="F" row="{$offset + 1}" value="Notes"/> <c:cell col="M" row="{$offset + 1}" value="Advances"/>

<c:cell col="M" row="{$offset + 2}" value="Total" </xsl:template>

<xsl:template match="Employee">

<c:cell col="D" row="10" value="Employee"/> <xsl:apply-templates/> </xsl:template>

<xsl:template match="Employee/Name">

<c:cell col="D" row="12" value="Name"/> <c:cell col="E" row="12" value="{.}"/> </xsl:template>

<xsl:template match="Employee/SSN">

<c:cell col="D" row="13" value="SSN"/> <c:cell col="E" row="13" value="{.}"/> </xsl:template>

<xsl:template match="Employee/Dept">

<c:cell col="D" row="14" value="Department"/> <c:cell col="E" row="14" value="{.}"/> </xsl:template>

<xsl:template match="Employee/EmpNo">

<c:cell col="G" row="12" value="Emp #"/> <c:cell col="H" row="12" value="{.}"/> </xsl:template>

<xsl:template match="Employee/Position">

<c:cell col="G" row="13" value="Position"/> <c:cell col="H" row="13" value="{.}"/> </xsl:template>

<xsl:template match="Employee/Manager">

<c:cell col="G" row="14" value="Manager"/> <c:cell col="H" row="14" value="{.}"/> </xsl:template>

<xsl:template match="PayPeriod">

<c:cell col="M" row="10" value="Pay Period"/> <xsl:apply-templates/> </xsl:template>

<xsl:template match="PayPeriod/From">

<c:cell col="M" row="12" value="From"/> <c:cell col="N" row="12" value="{.}"/> </xsl:template>

<xsl:template match="PayPeriod/To">

<c:cell col="M" row="14" value="To"/> <c:cell col="N" row="14" value="{.}"/> </xsl:template>

<xsl:template match="Expenses">

<c:cell col="D" row="16" value="Date"/> <c:cell col="E" row="16" value="Account"/> <c:cell col="F" row="16" value="Description"/> <c:cell col="G" row="16" value="Lodging"/> <c:cell col="H" row="16" value="Transport"/> <c:cell col="I" row="16" value="Fuel"/> <c:cell col="J" row="16" value="Meals"/> <c:cell col="K" row="16" value="Phone"/> <c:cell col="L" row="16" value="Entertainment"/> <c:cell col="M" row="16" value="Other"/> <c:cell col="N" row="16" value="Total"/> <xsl:apply-templates/> </xsl:template>

<xsl:template match="Expenses/Expense"> <xsl:apply-templates>

<xsl:with-param name="row" select="position()+16"/> </xsl:apply-templates> </xsl:template>

<xsl:template match="Expense/Date"> <xsl:param name="row"/>

<c:cell col="D" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Account"> <xsl:param name="row"/>

<c:cell col="E" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Desc"> <xsl:param name="row"/>

<c:cell col="F" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Lodging"> <xsl:param name="row"/>

<c:cell col="G" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Transport"> <xsl:param name="row"/>

<c:cell col="H" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Fuel"> <xsl:param name="row"/>

<c:cell col="I" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Meals"> <xsl:param name="row"/>

<c:cell col="J" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Phone"> <xsl:param name="row"/>

<c:cell col="K" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/Entertainment"> <xsl:param name="row"/>

<c:cell col="L" row="{$row}" value="{.}"/> </xsl:template>

<xsl:template match="Expense/0ther"> <xsl:param name="row"/>

<c:cell col="M" row="{$row}" value="{.}"/> </xsl:template>

У кодирования значений ячеек атрибутами есть важное преимущество – вы получаете возможность воспользоваться шаблонами, отображающими атрибут на значение, что приводит к очень компактной схеме трансляции. В этой таблице стилей производятся преобразования двух видов. Первое – абсолютное. напри­мер, имя работника отображается на ячейку E12. Второе – относительное; каждая статья расходов отображается на некоторую строку относительно строки 16 в за­висимости от позиции в исходном документе.

Применив эту таблицу стилей к исходному документу, получим следующий результат:

<c:cells xmlns:c="http://www.ora.com/XSLTCookbook/namespaces/cells" > <c:cell col="M" row="3" value="Statement No."/> <c:cell col="N" row="3" value="12 3"/> <c:cell col="L" row="6" value="Expense Statement"/> <c:cell col="D" row="10" value="Employee"/> <c:cell col="D" row="12" value="Name"/> <c:cell col="E" row="12" value="Salvatore Mangano"/> <c:cell col="D" row="13" value="SSN"/> <c:cell col="E" row="13" value="9 9 9-9 9-9 9 9 9"/> <c:cell col="D" row="14" value="Department"/> <c:cell col="E" row="14" value="XSLT Hacking"/> <c:cell col="G" row="12" value="Emp #"/> <c:cell col="H" row="12" value="1"/> <c:cell col="G" row="13" value="Position"/> <c:cell col="H" row="13" value="Cook"/> <c:cell col="G" row="14" value="Manager"/> <c:cell col="H" row="14" value="Big Boss O’Reilly"/> <c:cell col="M" row="10" value="Pay Period"/> <c:cell col="M" row="12" value="From"/> <c:cell col="N" row="12" value="1/1/02"/> <c:cell col="M" row="14" value="To"/> <c:cell col="N" row="14" value="1/31/0 2"/> <c:cell col="D" row="16" value="Date"/> <c:cell col="E" row="16" value="Account"/> <c:cell col="F" row="16" value="Description"/> <c:cell col="G" row="16" value="Lodging"/> <c:cell col="H" row="16" value="Transport"/> <c:cell col="I" row="16" value="Fuel"/> <c:cell col="J" row="16" value="Meals"/> <c:cell col="K" row="16" value="Phone"/> <c:cell col="L" row="16" value="Entertainment"/> <c:cell col="M" row="16" value="Other"/> <c:cell col="N" row="16" value="Total"/> <c:cell col="D" row="18" value="12/2 0/01"/> <c:cell col="E" row="18" value="12 34 5"/>

<c:cell col="F" row="18" value="Goofing off instead of going to confrence.

<c:cell col="G" row="18" value="500.00"/>

<c:cell col="H" row="18" value="50.00"/>

<c:cell col="I" row="18" value="0"/>

<c:cell col="J" row="18" value="300.00"/>

<c:cell col="K" row="18" value="10 0"/>

<c:cell col="L" row="18" value="1000.00"/>

<c:cell col="M" row="18" value="300.00"/>

<c:cell col="D" row="20" value="12/2 0/01"/>

<c:cell col="E" row="20" value="12 34 5"/> <c:cell col="F" row="20" value="0n the beach"/> <c:cell col="G" row="20" value="500.00"/> <c:cell col="H" row="20" value="50.00"/> <c:cell col="I" row="20" value="0"/> <c:cell col="J" row="20" value="200.00"/> <c:cell col="K" row="20" value="20"/> <c:cell col="L" row="20" value="300.00"/> <c:cell col="M" row="20" value="100.00"/> <c:cell col="M" row="20" value="Sub Total"/> <c:cell col="D" row="21" value="Approved"/> <c:cell col="F" row="21" value="Notes"/> <c:cell col="M" row="21" value="Advances"/> <c:cell col="M" row="22" value="Total"/> </c:cells>

Конечно, это еще не то, что мы хотим получить. Однако нетрудно видеть, что после сортировки ячеек сначала по @row, а потом по @col преобразование в фор­мат с разделителями полей окажется совсем простым. На самом деле, если вы го­товы прибегнуть к функции расширения node-set, определенной в проекте EXSLT, то получить желаемый результат можно и за один проход. Заметим также, что преобразование элементов cell в последовательность полей, разделенных запятыми, имеет общий характер, поэтому его можно будет использовать повтор­но для других сложных преобразований XML. См. примеры 7.17 и 7.16.

Пример 7.15. Обобщенная таблица стилей cells-to-comma-delimited.xslt

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:c="http://www.ora.com/XSLTCookbook/namespaces/cells" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">

<xsl:output method="text"/>

<!– Oтoбpaжaeм 6уквы, обозначающие колонки, на числа –> <xsl:variable name="columns" select=" ^ABCDEFGHIJKLMNOPQRSTUVWXYZ’ "/>

<xsl:template match="/">

<!– Запоминаем ячейки в пepeмeннoй –> <xsl:variable name="cells"> <xsl:apply-templates/> </xsl:variable>

<xsl:variable name="cells-sorted">

<xsl:for-each select="exsl:node-set($cells)/c:cell"> <xsl:sort select="@row" data-type="number"/> <xsl:sort select="@col" data-type="text"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable>

<xsl:apply-templates select="exsl:node-set($cells-sorted)/c:cell"/>

</xsl:template>

<xsl:template match="c:cell"> <xsl:choose>

<!– Обнаруживаем смену строки –>

<xsl:when test="preceding-sibling::c:cell[1]/@row != @row"> <!– Вычисляем, сколько строк пропустить –> <xsl:variable name="skip-rows"> <xsl:choose>

<xsl:when test="preceding-sibling::c:cell[1]/@row"> <xsl:value-of

select="@row – preceding-sibling::c:cell[1]/@row"/> </xsl:when> <xsl:otherwise>

<xsl:value-of select="@row – 1"/> </xsl:otherwise> </xsl:choose> </xsl:variable>

<xsl:call-template name="skip-rows">

<xsl:with-param name="skip" select="$skip-rows"/> </xsl:call-template>

<xsl:variable name="current-col"

select="string-length(substring-before($columns,@col))"/> <xsl:call-template name="skip-cols">

<xsl:with-param name="skip" select="$current-col – 1"/> </xsl:call-template>

<xsl:value-of select="@value"/>,<xsl:text/> </xsl:when>

<xsl:otherwise>

<!– Вычисляем, сколько колонок пропустить –> <xsl:variable name="skip-cols"> <xsl:variable name="current-col"

select="string-length(substring-before($columns,@col))"/>

<xsl:choose>

<xsl:when test="preceding-sibling::c:cell[1]/@col"> <xsl:variable name="prev-col" select="string-length(substring-before($columns, preceding-sibling::c:cell[1]/@col))"/> <xsl:value-of select="$current-col – $prev-col – 1"/> </xsl:when> <xsl:otherwise>

<xsl:value-of select="$current-col – 1"/> </xsl:otherwise> </xsl:choose> </xsl:variable>

<xsl:call-template name="skip-cols">

<xsl:with-param name="skip" select="$skip-cols"/> </xsl:call-template>

<!– Output the value of the cell and a comma –> <xsl:value-of select="@value"/>,<xsl:text/> </xsl:otherwise> </xsl:choose> </xsl:template>

<!– Вставляем пустые строки, если строки таблицы не соседние –> <xsl:template name="skip-rows"> <xsl:param name="skip"/> <xsl:choose>

<xsl:when test="$skip > 0"> <xsl:text>&#xa;</xsl:text> <xsl:call-template name="skip-rows">

<xsl:with-param name="skip" select="$skip – 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise/> </xsl:choose> </xsl:template>

<!– Вставляем дополнительные запятые, если колонки не соседние –> <xsl:template name="skip-cols"> <xsl:param name="skip"/> <xsl:choose>

<xsl:when test="$skip > 0"> <xsl:text>,</xsl:text> <xsl:call-template name="skip-cols">

<xsl:with-param name="skip" select="$skip – 1"/>

</xsl:call-template> </xsl:when> <xsl:otherwise/> </xsl:choose>

</xsl:template>

</xsl:stylesheet>

Пример 7.16. Таблица стилей expense-to-delimited.xslt для конкретного приложения

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

<xsl:include href="cells-to-comma-delimited.xslt"/>

<xsl:template match="ExpenseReport">

<c:cell col="M" row="3" value="Statement No."/> <c:cell col="N" row="3" value="{@statementNum}"/> <c:cell col="L" row="6" value="Expense Statement"/> <xsl:apply-templates/>

<xsl:variable name="offset" select="count(Expenses/Expense)+18"/> <c:cell col="M" row="{$offset}" value="Sub Total"/> <c:cell col="D" row="{$offset + 1}" value="Approved"/> <c:cell col="F" row="{$offset + 1}" value="Notes"/> <c:cell col="M" row="{$offset + 1}" value="Advances"/> <c:cell col="M" row="{$offset + 2}" value="Total"/> </xsl:template>

<xsl:template match="Employee">

<c:cell col="D" row="10" value="Employee"/> <xsl:apply-templates/> </xsl:template>

<xsl:template match="Employee/Name">

<c:cell col="D" row="12" value="Name"/> <c:cell col="E" row="12" value="{.}"/> </xsl:template>

<!– … –>

<!– Опущенный код такой же, как в исходной таблице стилей выше –>

<!– … –>

В обобщенной таблице стилей cells-to-comma-delimited.xslt ячейки, порожден­ные таблицей, специфичной для приложения, запоминаются в переменной и сор­тируются. Затем эти ячейки преобразуются в файл с полями, разделенными запя­тыми. Для этого каждый элемент cell сравнивается со своим предшественником после сортировки. Если предшественник относится к другой строке, то нужно вывести один или несколько символов новой строки. Если же предшественник принадлежит не соседней колонке, то следует вывести одну или несколько допол­нительных запятых. Необходимо рассмотреть также случай, когда первая строка или первая колонка в строке не являются соответственно первой строкой или ко­лонкой в электронной таблице. Обработав все эти детали, останется только выве­сти значение ячейки и запятую.

XSLT 2.0

Новый атрибут separator элемента xsl:value-of заметно упрощает вы­вод текста с разделителями. Когда элементу xsl:value-of передана последова­тельность, он сериализует ее; при этом, если задан атрибут separator, то после каж­дого члена последовательности, кроме последнего вставляется значение этого атрибута – разделитель. Разделитель может быть литералом или вычисляемым выражением. В следующем примере я воспользовался этой возможностью, чтобы упростить код, который выводит имена колонок. Кроме того, средства XPath 2.0 позволили мне обобщить решение так, чтобы одну и ту же базовую таблицу сти­лей можно было применять к XML-документам, в которых данные закодированы в элементах или атрибутах. Наконец, для кодирования отображения XML на CSV я использую литеральные последовательности, а не внедренный в таблицу стилей XML-код. Все это сделано просто для иллюстрации гибкости XSLT 2.0, без наме­ка на то, что одна техника предпочтительнее другой (см. примеры 7.17 и 7.18).

Пример 7.17. Обобщенная таблица стилей cells-to-comma-delimited.xslt

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2 0 01/XMLSchema" xmlns:fn="http://www.w3.org/2004/10/xpath-functions" xmlns:csv="http://www.ora.com/XSLTCookbook/namespaces/csv">

<xsl:param name="delimiter" select=" ‘,’ "/>

<!– Должно быть пepeoпpeдeлeнo в импopтиpyющeй таблице стилей –> <xsl:variable name="columns" select="()" as="xs:string*"/> <xsl:variable name="nodeNames" select="$columns" as="xs:string*"/>

<xsl:output method="text" />

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

<!– Здесь используется новый атрибут value-of –> <xsl:value-of select="$columns" separator="{$delimiter}"/> <xsl:text>&#xa;</xsl:text>

<xsl:apply-templates mode="csv:map-row"/> </xsl:template>

<xsl:template match="/*/*" mode="csv:map-row" name="csv:map-row">

<xsl:param name="elemOrAttr" select=" ‘elem’ " as="xs:string"/>

<xsl:variable name="row" select="." as="node()"/>

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

<xsl:apply-templates select="if ($elemOrAttr eq ‘elem’)

then $row/*[local-name(.) eq current()] else $row/@*[local-name(.) eq current()]" mode="csv:map-value"/> <xsl:value-of select="if (position() ne last()) then $delimiter else ()"/> </xsl:for-each>

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

</xsl:template>

<xsl:template match="node()" mode="csv:map-value">

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

</xsl:stylesheet>

Пример 7.18. Таблица стилей expense-to-delimited.xslt для конкретного приложения

<?xml version="1.0" encoding="ISO-8 85 9-1"?>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2 0 01/XMLSchema"

xmlns:csv="http://www.ora.com/XSLTCookbook/namespaces/csv"> <xsl:import href="toCSV.xslt"/>

<!– Определяем отображение узлов на колонки –>

<xsl:variable name="columns" select="’Name’, ‘Age’, ‘Gender’, ‘Smoker’" as="xs:string*"/>

<xsl:variable name="nodeNames" select="’name’, ‘age’, ‘sex’, ‘smoker’" as="xs:string*"/>

<!– Переключаем обработку по умолчанию с элементов на атрибуты –> <xsl:template match="/*/*" mode="csv:map-row"> <xsl:call-template name="csv:map-row">

<xsl:with-param name="elem0rAttr" select=" ‘attr’ "/> </xsl:call-template> </xsl:template>

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

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

<xsl:when test=".=’m’">male</xsl:when> <xsl:when test=".=’f’">female</xsl:when> <xsl:otherwise>error</xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Обсуждение

Большинство преобразований XML в формат с разделителями полей для зна­токов XSLT не представляют сложности. Ценность предыдущих примеров в де­монстрации того, как задачу можно разбить на две части: повторно используемую, для написания которой требуется хорошее знание XSLT, и прикладную, которая доступна менее опытным пользователям, если они хорошо понимают принятые соглашения.

Ценность такого подхода заключается в том, что он позволяет выполнять по­лезную работу людям, не владеющим XSLT в полной мере. Предположим, напри­мер, что нужно конвертировать большую базу XML-документов в формат с разде­лителями полей, причем, как обычно, это следовало сделать еще вчера. Показать кому-то, как пользоваться общим решением, куда проще, чем обучить его XSLT в объеме, позволяющем составлять специализированные таблицы стилей.

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

По теме:

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