#java #xslt #csv #transformation #xalan
#java #xslt #csv #преобразование #xalan
Вопрос:
Итак, вот проблема, которая беспокоит меня последние несколько дней. Это должно быть довольно просто, но XSLT — это просто такая боль для отладки. Мы используем Xalan 1.0 на Java 1.6
Ввод XML
<?xml version="1.0" encoding="UTF-8"?>
<rfb2>
<rfb2_item>
<VALDATE>2011-10-23</VALDATE>
<FUND_ID>300</FUND_ID>
<SEC_ID>34567</SEC_ID>
</rfb2_item>
<rfb2_item>
<VALDATE>2011-1-09</VALDATE>
<FUND_ID>700</FUND_ID>
<SEC_ID>13587</SEC_ID>
</rfb2_item>
<rfb2_item>
<VALDATE>2011-3-09</VALDATE>
<FUND_ID>200</FUND_ID>
<SEC_ID>999334</SEC_ID>
</rfb2_item>
<rfb2>
Нам нужно преобразовать XML в разделенный запятыми список значений для каждого rfb2_item, чтобы таблица стилей всегда повторяла узлы rfb2_item. Мы используем параметр в таблице стилей, чтобы управлять тем, какие элементы rfb2_item (valdate,fund_id, sec_id) будут выводиться и в каком порядке, например
<xsl:param name="$outputElements" select="'VALDATE,FUND_ID'"/>
..outputs...
2011-10-23,300
2011-1-09,700
2011-3-09,200
<xsl:param name="$outputElements" select="'SEC_ID'"/>
..outputs...
34567
13587
999334
Особый случай, когда, если $outputElements равен ‘*’, просто выводите элементы в том порядке, в котором они отображаются во входном xml
<xsl:param name="$outputElements" select="'*'"/>
..outputs...
2011-10-23,300,34567
2011-1-09,700,13587
2011-3-09,200,999334
Итак, мой вопрос заключается в том, как нам написать шаблон для создания желаемого результата на основе параметра $ outputElements? Рабочий пример был бы отличным…
Комментарии:
1. Вы забыли задать вопрос. Если только вы не хотите, чтобы кто-то написал полный xslt для вас — что они и сделают.
Ответ №1:
Да, FailedDev прав. Кто-нибудь мог бы написать это за вас:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:param name="outputElements" select=" 'FUND_ID,SEC_ID,VALDATE' " />
<xsl:template match="rfb2_item">
<xsl:for-each select="*[contains($outputElements, local-name()) or $outputElements = '*']">
<xsl:sort select="string-length(substring-before($outputElements, local-name(.)))" />
<xsl:value-of select="text()" />
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>amp;#13;amp;#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>
Немного объяснения. xsl:for-each
Будет выбран каждый элемент в текущем, rfb2_item
для которого локальное имя содержится в outputElements
параметре, или для которого outputElements
параметр является *
(который всегда выдавал бы значение true, если это так). Затем они будут отсортированы на основе длины подстроки, которая идет перед этим локальным именем в outputElements
. Поскольку это значение становится выше, когда имя встречается позже в этом параметре, это приводит к упорядочению на основе вашего параметра.
Пример: элемент VALDATE
выдаст FUND_ID,SEC_ID
для substring-before
функции, которая, в свою очередь, выдаст 14 в качестве длины строки. Это больше, чем 8, которые вы получили бы для SEC_ID
, что означает, что VALDATE
значение упорядочено после SEC_ID
.
После xsl:sort
мы просто используем xsl:value-of
для вывода значения элемента. Возможно, вам захочется обрезать там лишние пробелы. Наконец, мы проверяем, не равна ли позиция последнему узлу в текущем контексте (который является позицией xsl:for-each
после сортировки), и если да, выведите запятую. Это позволяет избежать вывода запятой после последнего значения.
Перевод строки, который я вставил с помощью xsl:text
, предполагает соглашение Windows / DOS. Удалите amp;#13;
, если файл должен использовать только символы новой строки для разрывов строк, вместо возврата каретки новая строка.
Обратите внимание, что это не исключает запятых в вашем выводе CSV! Я оставляю это на ваше усмотрение. Было бы интересно изучить возможность использования функций расширения для делегирования этой задачи Java, если это окажется слишком сложным в XSLT / XPath.
Комментарии:
1. Эй, G_H, это здорово, и это работает. Вы только что сняли большой стресс, спасибо. Я добавил <xsl:элементы с разделительным пространством = «*» /> в верхней части таблицы стилей, чтобы удалить все пробелы. У меня действительно было несколько вопросов. Необходима ли сортировка? И комментарии, которые вы сделали по поводу длины строки, меня беспокоят, нужно ли мне учитывать, что если 2 или более элементов имеют одинаковую длину, как в rfb2_item / E1 и rfb2_item / E2?
2. @RaffiM Сортировка выполняется для того, чтобы убедиться, что поля вывода расположены в порядке, определенном вашим параметром, как я полагаю, было одним из требований. Длина строки, которая проверяется, равна длине подстроки, если это параметр , а не содержимое выводимого элемента. Это уловка, чтобы убедиться, что элементы выводятся в порядке, указанном в параметре. В принципе, имя каждого элемента, который должен быть выведен (
FUND_ID
,VALDATE
…), выдаст другое число. Эти числа устанавливают порядок на основе заданного параметра.3. @G_H … это имеет смысл … последний вопрос, мы пытаемся интегрировать ваш код в большую таблицу стилей, и это приводит к странным результатам. Мы знаем, что это работает автономно, но проблема в том, как мы это называем. Если мы вызовем ваш шаблон (используя xsl:call-template) из другого шаблона, который уже сопоставлен с «/ *», что изменится в коде вашего шаблона для получения того же результата?
4. @RaffiM
xsl:call-template
предназначен для именованных шаблонов. Мой использует сопоставление входных данных. Вам нужно либо изменить мой шаблон на именованный и вызвать его сrfb2_item
узлом в качестве контекста, либо использоватьxsl:apply-templates
с выбором, предназначенным для этих узлов.
Ответ №2:
Иногда в подобных ситуациях стоит рассмотреть возможность генерации или модификации XSLT-кода с использованием XSLT. Таким образом вы можете значительно расширить параметризацию — например, контролировать, какие поля выводятся, как они сортируются, группируются ли они, критерии выбора, для которых выбираются строки, и т.д. И т.п.