【问题标题】:Multi-instance XML file to CSV with Headers and Column Mappings带有标题和列映射的多实例 XML 文件到 CSV
【发布时间】:2017-08-25 18:01:14
【问题描述】:

我正在尝试转换一个多实例 XML 文件,其中一个父节点可能有 2 个或更多相同类型的子节点。

基本上,我有这种类型的 XML 文件:

<?xml version='1.0' encoding='UTF-8'?>
<Report_Data>
   <Report_Entry>
      <field1>Record1_field1</field1>
      <field2>Record1_field2</field2>
      <field3>Record1_field3</field3>
      <Report_SubEntry>
         <subField1>Record_1subfield1</subField1>
         <subField2>Record_1subfield2</subField2>
         <subField3>Record_1subfield3</subField3>
      </Report_SubEntry>
      <Report_SubEntry>
         <subField1>Record1_subfield1_subEntry2</subField1>
         <subField2>Record1_subfield2_subEntry2</subField2>
         <subField3>Record1_subfield3_subEntry2</subField3>
      </Report_SubEntry>
   </Report_Entry>
      <Report_Entry>
      <field1>Record2_field1</field1>
      <field2>Record2_field2</field2>
      <field3>Record2_field3</field3>
      <Report_SubEntry>
         <subField1>Record2_subfield1</subField1>
         <subField2>Record2_subfield2</subField2>
         <subField3>Record2_subfield3</subField3>
      </Report_SubEntry>
   </Report_Entry>
 </Report_Data>  

XSLT 函数必须可重用于类似结构的 XML 文件,而不必为所有可能的子条目类型硬编码循环或条件。

这是我的 XSLT:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs this ws">

    <xsl:output method="text" encoding="UTF-8" media-type="text/plain"/>
    <xsl:strip-space elements="*"/>

    <xsl:param name="delimiter" select="'§'" />
    <xsl:param name="quote" select="''" />
    <xsl:param name="break" select="'&#xA;'" />

    <!--
        List of Fields to be included in final CSV.
        !! Use only XPATH node names with prefixes. !!
               If prefixes are to be removed from the header, update the 
    -->

    <xsl:variable name="fieldArray">
        <field>field1</field>
        <field>field2</field>
        <field>field3</field>
        <field>subField1</field>
        <field>subField2</field>
        <field>subField3</field>
    </xsl:variable>



    <xsl:param name="fields" select="document('')/*/xsl:variable[@name='fieldArray']/*" />

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

        <!-- output the header row -->
        <xsl:for-each select="$fields">
            <xsl:if test="position() != 1">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>
            <xsl:value-of select="." />
        </xsl:for-each>

        <!-- output newline -->
        <xsl:text>&#xa;</xsl:text>
        <xsl:apply-templates select="Report_Entry"/>

    </xsl:template>

    <xsl:template match="//Report_Entry">
        <xsl:variable name="currNode" select="." />

        <!-- output the data row -->
        <!-- loop over the field names and find the value of each one in the xml -->

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

            <xsl:if test="position() != 1">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>

            <xsl:value-of select="$quote" />

            <xsl:variable name="child" select="$currNode[*]/*/*[name() = current()]" />
            <xsl:variable name="childSecondLevel" select="$currNode[*]/*/*/*[name() = current()]" />
            <xsl:variable name="childThirdLevel" select="$currNode[*]/*/*/*/*[name() = current()]" />

            <xsl:choose>

                <xsl:when test="count($child) > 0">
                    <xsl:value-of select="$child"/>
                </xsl:when>

                <xsl:when test="count($childSecondLevel) > 0">
                    <xsl:value-of select="$childSecondLevel"/>
                </xsl:when>

                <xsl:when test="count($childThirdLevel) > 0">
                    <xsl:value-of select="$childThirdLevel"/>
                </xsl:when>

                <xsl:otherwise>
                    <xsl:value-of select="$currNode/*[name() = current()]" />
                </xsl:otherwise>

            </xsl:choose>

            <xsl:value-of select="$quote" />

        </xsl:for-each>


        <!-- output newline -->
        <xsl:value-of select="$break" />
    </xsl:template>

</xsl:stylesheet>

我得到的输出是:

field1,field2,field3,subField1,subField2,subField3
Record1_field1,Record1_field2,Record1_field3,Record_1subfield1,Record_1subfield2,Record_1subfield3
Record2_field1,Record2_field2,Record2_field3,Record2_subfield1,Record2_subfield2,Record2_subfield3

但我需要的输出是:

field1,field2,field3,subField1,subField2,subField3
Record1_field1,Record1_field2,Record1_field3,Record_1subfield1,Record_1subfield2,Record_1subfield3
Record1_field1,Record1_field2,Record1_field3,Record_1subfield1_subEntry2,Record_1subfield2_subEntry2,Record_1subfield3_subEntry3
Record2_field1,Record2_field2,Record2_field3,Record2_subfield1,Record2_subfield2,Record2_subfield3

有人对解决这个问题有什么建议吗?

【问题讨论】:

    标签: xml csv xslt


    【解决方案1】:

    下面的怎么样。

    我正在循环遍历Report_SubEntry 而不是Report_Entry,这意味着对于每个Report_SubEntry,您将获得一行。

    <xsl:template match="/">
        <!-- Output header -->
        <xsl:text>f1,f2,f3,sf1,sf2,sf3&#xa;</xsl:text>
    
        <!-- Loop through all children of Report_Entry that have childen themselves -->
        <xsl:for-each select="/Report_Data/*/*[*]">
            <xsl:variable name="strCurrentNode" select="name()" />
    
            <!-- Output parent values -->
            <xsl:for-each select="../*">
                <xsl:if test="name() != $strCurrentNode">
                    <xsl:value-of select="." />
                    <xsl:text>,</xsl:text>
                </xsl:if>
            </xsl:for-each>
    
            <!-- Output child values -->
            <xsl:for-each select="./*">
                <xsl:if test="name() != $strCurrentNode">
                    <xsl:value-of select="." />
    
                    <xsl:if test="position() != last()">
                        <xsl:text>,</xsl:text>
                    </xsl:if>
                </xsl:if>
            </xsl:for-each>
    
            <!-- output newline -->
            <xsl:text>&#xa;</xsl:text>
        </xsl:for-each>
    </xsl:template>
    

    【讨论】:

    • 谢谢!这可以完成工作。实际上,子节点的结构不同,这会取代列。这就是我原始 sn-p 中 fieldArray 的目的 - 即使缺少节点或子节点,也始终正确地在 CSV 中形成列。我将根据您的反馈循环遍历最低节点并获取父值,而不是始终获取子值或兄弟值。谢谢!
    • NP,请记住单击勾号,以便将其标记为已回答。如果您不确定是否总是至少有一个 SubEntry 但想要输出父值,那么您可以循环遍历条目(for-each),检查是否有 Sub_Entry(选择->何时),如果有,循环通过Sub_Entry's(for-each),输出Sub_Entry数据和../Entry数据,如果没有Sub_Entry's(choose->else)则只输出Entry数据。希望这是有道理的,在 cmets 中写起来有点困难。
    猜你喜欢
    • 2021-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 2020-12-16
    相关资源
    最近更新 更多