Как работать с массивами в разных циклах в XSLT
У меня проблема с обработкой следующего XML-кода:
<?xml version="1.0" encoding="UTF-8"?>
<searchresult>
<head>
<heading>
<title>Column1</title>
<dataType>TEXT</dataType>
</heading>
<heading>
<title>Column2</title>
<dataType>DATE</dataType>
<dataFormat>SHORT_DATE</dataFormat>
</heading>
</head>
<data>
<row>
<column>
<value>Hello</value>
<column>
<column>
<value>2012-07-12</value>
<column>
</row>
<row>
<column>
<value>Good bye</value>
<column>
<column>
<value>2012-07-13</value>
<column>
</row>
</data>
</searchresult>
Мне нужно преобразовать этот xml в EXCEL-совместимый файл (я использую urn: schemas-microsoft-com: office: office, urn: schemas-microsoft-com: office: excel и urn: schemas-microsoft-com: office: пространства имен электронных таблиц для Это)
Проблема в том, что я не знаю, как применить информацию из элементов заголовка / заголовка dataType + dataFormat (если доступно) к строке / столбцу / значению. Это поможет Excel определить тип данных внутри его ячеек. Очевидно, что мне нужно сохранить порядок так. Количество столбцов и их метаданных является динамическим, и каждый XML может отличаться.
Мне нужно получить что-то вроде этого:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Workbook --several namespaces here-->
<Worksheet ss:Name="SearchResult">
<Table x:FullRows="1" x:FullColumns="1">
<Row ss:Height="12.75">
<Cell>
<Data ss:Type="String">Column1</Data>
</Cell>
<Cell>
<Data ss:Type="String">Column2</Data>
</Cell>
</Row>
<Row ss:Height="12.75">
<Cell>
<Data ss:Type="String">Hello : TEXT</Data>
</Cell>
<Cell>
<Data ss:Type="Date">2012-07-12 : DATE - SHORT_DATE</Data>
</Cell>
</Row>
<Row ss:Height="12.75">
<Cell>
<Data ss:Type="String">Good bye : TEXT</Data>
</Cell>
<Cell>
<Data ss:Type="Date">2012-07-12 : DATE - SHORT_DATE</Data>
</Cell>
</Row>
</Table>
</Worksheet>
</Workbook>
Я несколько раз пытался создать что-то полезное и работающее, но все мои попытки провалились. Текущая версия здесь:
<xsl:template match="searchresult">
<Worksheet>
--some unimportant script--
<Table x:FullColumns="1" x:FullRows="1">
<xsl:apply-templates select="head" />
<xsl:apply-templates select="elements/row"/>
</Table>
</Worksheet>
</xsl:template>
<xsl:template match="head">
<Row>
<xsl:for-each select="*">
<!-- resolve data-type and remember it as variable -->
<xsl:variable name="concat('dataType', position())" select="dataType">
<xsl:choose>
<xsl:when test="TEXT">
<xsl:value-of select=".">String</xsl:value-of>
</xsl:when>
<xsl:when test="DATE">
<xsl:value-of select=".">DateTime</xsl:value-of>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="concat('dataFormat', position())" select="dataFormatter" >
<!-- create style IDs for different formats -->
</xsl:variable>
<Cell>
<Data ss:Type="String">
<xsl:value-of select="title/." />
</Data>
</Cell>
</xsl:for-each>
</Row>
</xsl:template>
<xsl:template match="elements/row/column">
<xsl:for-each select="values">
<Cell>
<!-- resolve order within loop and pick correct data-type variable -->
<xsl:variable name="type" select="concat('$dataType', position())" />
<xsl:variable name="format" select="concat('$dataFormat', position())" />
<Data ss:Type="$type">
<xsl:value-of select="concat(normalize-space(.),' : ', $type)"/>
<!-- check if data format is set -->
<xsl:if test="//TODO">
<xsl:value-of select="concat(' - ', $format)" />
</xsl:if>
</Data>
</Cell>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Эта версия бесполезна, потому что я не могу использовать в качестве имени переменной любое значение переменной, это должно быть постоянное значение. Разбор целых данных работает как-то, но когда я попытался реализовать тип данных и формат данных, он сломался.
Редактировать: информация о типе данных и формате данных помещается в элемент head, который содержит всю информацию о столбцах и их заголовках. Столбцы обрабатываются в отдельном шаблоне и не связаны напрямую с определениями столбцов из элемента head. Связь поддерживается только через порядок элементов. Мне нужно обработать тип данных и возможный формат данных (который является необязательным) для каждой строки и каждой ячейки (для правильного столбца), а не только для заголовков.
1 ответ
Вы можете использовать клавишу для поиска элементов заголовка по их положению
<xsl:key name="headings" match="heading" use="count(preceding-sibling::heading)" />
Затем, предполагая, что вы размещены на элементе столбца, вы получите связанный тип данных, основанный на позиции, например,
<xsl:variable
name="dataType"
select="key('headings', count(preceding-sibling::column))/dataType" />
т.е. для первого элемента столбца в строке вы должны найти первый элемент заголовка и получить тип данных.
Некоторые другие замечания по поводу вашего XSLT. Во-первых, имена динамических переменных, такие как следующие, не допускаются
<xsl:variable name="concat('dataType', position())" select="dataType">
Вам также не разрешено иметь непустое содержимое в переменной, если вы используете атрибут select.
Во-вторых, если вы хотите использовать значения переменных в ваших выходных атрибутах, вам нужно использовать шаблоны значений атрибутов. Вместо этого...
<Data ss:Type="$type">
Ты сделаешь это
<Data ss:Type="{$type}">
Кроме того, вы должны отдавать предпочтение xsl: apply-templates вместо xsl: for-each, поскольку они поощряют повторное использование кода, сокращают вложенный код и больше соответствуют духу XSLT.
Во всяком случае, вот полный XSLT (обратите внимание, с использованием вымышленных пространств имен)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:x="x" xmlns:ss="ss">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="headings" match="heading" use="count(preceding-sibling::heading)"/>
<xsl:template match="searchresult">
<Worksheet>
<Table x:FullColumns="1" x:FullRows="1">
<xsl:apply-templates select="head"/>
<xsl:apply-templates select="data/row"/>
</Table></Worksheet>
</xsl:template>
<xsl:template match="head">
<Row>
<xsl:apply-templates select="heading"/>
</Row>
</xsl:template>
<xsl:template match="heading">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="title"/>
</Data>
</Cell>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="column"/>
</row>
</xsl:template>
<xsl:template match="column">
<Cell>
<xsl:variable name="dataType" select="key('headings', count(preceding-sibling::column))/dataType"/>
<xsl:variable name="type">
<xsl:choose>
<xsl:when test="$dataType = 'TEXT'">
<xsl:text>String</xsl:text>
</xsl:when>
<xsl:when test="$dataType = 'DATE'">
<xsl:text>Date</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="dataFormat" select="key('headings', count(preceding-sibling::column))/dataFormat"/>
<Data ss:Type="{$type}">
<xsl:value-of select="concat(normalize-space(.),' : ', $type)"/>
<xsl:if test="$dataFormat">
<xsl:value-of select="concat(' - ', $dataFormat)"/>
</xsl:if>
</Data>
</Cell>
</xsl:template>
</xsl:stylesheet>
При применении к вашему примеру XML, выводится следующее
<Worksheet xmlns:x="x" xmlns:ss="ss">
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<Cell>
<Data ss:Type="String">Column1</Data>
</Cell>
<Cell>
<Data ss:Type="String">Column2</Data>
</Cell>
</Row>
<row>
<Cell>
<Data ss:Type="String">Hello : String</Data>
</Cell>
<Cell>
<Data ss:Type="Date">2012-07-12 : Date - SHORT_DATE</Data>
</Cell>
</row>
<row>
<Cell>
<Data ss:Type="String">Good bye : String</Data>
</Cell>
<Cell>
<Data ss:Type="Date">2012-07-13 : Date - SHORT_DATE</Data>
</Cell>
</row>
</Table>
</Worksheet>