XML в CSV с использованием XSLT
Я ищу интеллектуальный эффективный XSLT, который конвертирует XML-документ в данные CSV. Следует позаботиться о всех возможных элементах в дочерних узлах. Например, XML выглядит так
<?xml version="1.0" encoding="ISO-8859-1"?>
<sObjects>
<sObject>
<Name>Raagu</Name>
<BillingStreet>Hoskote</BillingStreet>
</sObject>
<sObject>
<Name>Rajath</Name>
<BillingStreet>BTM</BillingStreet>
<age>25</age>
</sObject>
<sObject>
<Name>Sarath</Name>
<BillingStreet>Murgesh</BillingStreet>
<location>Bangalore</location>
</sObject>
</sObjects>
И мой выход CSV должен выглядеть следующим образом
Name,BillingStreet,age,location
Raagu,Hoskote,,
Rajath,BTM,25,
Sarath,Murgesh,,Bangalore
Все строки должны иметь поля для всех ключей в CSV, даже если в XML есть значение для него.
Ниже приведен код XSLT, который я придумал, рассматривая различные примеры здесь.
Это XSLT, который я придумал
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:key name="field" match="sObject/*" use="name()"/>
<xsl:template match="/">
<xsl:for-each select="/*/*/*[generate-id()=generate-id(key('field', name())[1])]">
<xsl:value-of select="name()"/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="/*/sObject">
<xsl:variable name="property" select="." />
<xsl:for-each select="$property/*">
<xsl:variable name="value" select="." />
<xsl:value-of select="$value"/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
<xsl:if test="position() = last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
и распечатать это положить
Name,BillingStreet,age,location
Raagu,Hoskote
Rajath,BTM,25
Sarath,Murgesh,Bangalore
Но я хотел, чтобы все строки содержали значения для этих много раз для всех ключей в первой строке.
Не могли бы вы помочь мне достичь этого с помощью кода XSLT?
1 ответ
Как насчет этого для двухэтапного решения
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:key name="field" match="/*/*/*" use="local-name()"/>
<!-- variable containing the first occurrence of each field -->
<xsl:variable name="allFields"
select="/*/*/*[generate-id()=generate-id(key('field', local-name())[1])]" />
<xsl:template match="/">
<xsl:for-each select="$allFields">
<xsl:value-of select="local-name()" />
<xsl:if test="position() < last()">
<xsl:value-of select="$delimiter" />
</xsl:if>
</xsl:for-each>
<xsl:text> </xsl:text>
<xsl:apply-templates select="*/*" />
</xsl:template>
<xsl:template match="*">
<xsl:variable name="this" select="." />
<xsl:for-each select="$allFields">
<xsl:value-of select="$this/*[local-name() = local-name(current())]" />
<xsl:if test="position() < last()">
<xsl:value-of select="$delimiter" />
</xsl:if>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
Хитрость в том, что allFields
переменная будет содержать один элемент с каждым именем, поэтому этот список узлов мы повторяем для каждой строки, а не просто элементы, которые действительно существуют в этой строке. Поскольку вы говорите, что хотите поддерживать XML в произвольных пространствах имен и т. Д., Я использовал такие шаблоны, как /*/*/*
вместо того, чтобы жестко кодировать какие-либо конкретные имена элементов (/*/*/*
просто соответствует любому элементу, который является внуком элемента документа, независимо от имен элементов), и я использую local-name()
вместо name()
игнорировать любые префиксы пространства имен (это будет относиться к <sObject>
, <sObject xmlns="foo">
а также <f:sObject xmlns:f="foo">
точно так же).