【问题标题】:Converting unusual XML data to CSV through XSLT通过 XSLT 将异常 XML 数据转换为 CSV
【发布时间】:2017-04-28 00:14:34
【问题描述】:
<?xml version="1.0" encoding="UTF-8"?>
<FirstTag version="1.0" createTime="15:59:59" DATE="20161209">
  <SecondTag Name="House01">
    <a>
        <Furniture FURN_ID="FUR00001" FURN_AMT="2" price="10000"/>
        <Furniture FURN_ID="FUR00002" FURN_AMT="1" price="20000"/>
    </a>
    <b>
        <Furniture FURN_ID="FUR00003" FURN_AMT="2" price="30000"/>
        <Furniture FURN_ID="FUR00004" FURN_AMT="1" price="40000"/>
    </b>
    <c>
        <Furniture FURN_ID="FUR00005" FURN_AMT="2" price="50000"/>
        <Furniture FURN_ID="FUR00006" FURN_AMT="1" price="60000"/>
    </c>
    <d>
        <Furniture FURN_ID="FUR00007" FURN_AMT="1" price="70000"/>
        <Furniture FURN_ID="FUR00008" FURN_AMT="1" price="80000"/>
    </d>
    <e>
        <Furniture FURN_ID="FUR00009" FURN_AMT="1" price="90000"/>
        <Furniture FURN_ID="FUR00010" FURN_AMT="1" price="100000"/>
    </e>
    <f>
        <Furniture FURN_ID="FUR00011" FURN_AMT="1" price="110000"/>
        <Furniture FURN_ID="FUR00012" FURN_AMT="2" price="120000"/>
        <Furniture FURN_ID="FUR00013" FURN_AMT="2" price="120000"/>
    </f>
  </SecondTag>
</FirstTag>

以上是我从 Java 程序生成的简单 xml(带有节点值)。关键是,我想将此 xml 数据发送到另一个应用程序,其中已经有来自 UI/批处理过程的 csv 加载功能。我听说过 XSLT,但从未使用过它,尝试了一些教程,但在将所有值放入 csv 时感到困惑。

这是它在 csv 中的样子(开始,成功后需要做一些计算):

在这个例子中,在一所房子 (HOUSE01) 中,我想输出不同房间的所有家具(即 a 是房间 1,b 是房间 2,c 是房间 3,等等)。

我一直在尝试构建 XSLT,下面是 XSLT:

<?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:variable name="delimiter" select="','" />

  <!-- define an array containing the fields we are interested in -->
  <xsl:variable name="fieldArray">
    <field>Name</field>
    <field>a</field>
    <field>b</field>
    <field>c</field>
    <field>d</field>
    <field>e</field>
    <field>f</field>
  </xsl:variable>
  <xsl:param name="fields" select="document('')/*/xsl:variable[@name='fieldArray']/*" />

  <xsl:template match="/">

    <!-- 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>
</xsl:text>

    <xsl:apply-templates select="/*/*"/>
  </xsl:template>

  <xsl:template match="a">
    <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="$currNode/*[name() = current()]/@FURN_ID" />
<!--       <xsl:value-of select="$currNode/*[name() = current()]" /> -->
    </xsl:for-each>

<!--     output newline -->
    <xsl:text>
</xsl:text>
  </xsl:template>
</xsl:stylesheet>

我正在使用来自另一个页面的一些参考,并且可以构建一些简单的 XSLT 来将 XML 转换为 CSV,但是,我需要一些指导来解决我的主要 XML 问题。以后我可以在循环内获得节点值后,我想将每个房间的每件家具的总价格相加。

预期的最终 csv 结果:

Name,a,b,c,d,e,f
House01,40000,100000,160000,150000,190000,350000

谢谢。

Getting the value of an attribute in XML

【问题讨论】:

  • 家具节点是否会在没有最小或最大限制的房间内变化?
  • 是的。但每个房间只有 1 个数据,即每件家具价格的总和..即一个房间有 4 件家具,csv 中的输出将是这 4 件家具的总金额..
  • 你的底部结果总和比你原来的要容易甚至。顶部的逗号分隔值是什么?等等...为什么 &lt;a&gt; 总和是 40000 而不是 30000,接下来是 70000,接下来是 110000?
  • @Parfait sum(@FURN_AMT * @price),即a = 2 * 10000 + 1 * 20000 = 40000
  • f 的结果是 590000,而不是 350000

标签: java xml csv xslt text-files


【解决方案1】:

此 XSLT 将提供您指定的输出。见demo

更新:我错过了输出中的 a 值。

<?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:template match="/">
    <xsl:text>Name,a,b,c,d,e,f
</xsl:text>
    <xsl:apply-templates select="FirstTag/SecondTag/a/Furniture"/>
  </xsl:template>

  <xsl:template match="Furniture">
    <xsl:variable name="pos" select="position()"/>
    <xsl:value-of select="../../@Name"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="@FURN_ID"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="../../b/Furniture[position()=$pos]/@FURN_ID"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="../../c/Furniture[position()=$pos]/@FURN_ID"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="../../d/Furniture[position()=$pos]/@FURN_ID"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="../../e/Furniture[position()=$pos]/@FURN_ID"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="../../f/Furniture[position()=$pos]/@FURN_ID"/>
    <xsl:text>
</xsl:text>
  </xsl:template>

</xsl:stylesheet>

【讨论】:

  • 如果家具 f 有超过 2 件家具,我如何获得房间 f 中所有家具的总价格?刚刚实现输出 xml 每个房间只有 2 件家具,如果有超过 2 件家具在一个房间里它不会被包含在循环中,请参阅编辑
  • 你现在期望什么输出? 3个单独的行,第3行有空白字段?还是单个摘要行?摘要行要简单得多,所以如果这是您想要的,为什么要选择没有数值的中间行呢?请再次编辑问题并澄清您想要什么。
  • 嗨,更新了问题(见最后一部分,预期的最终输出)
  • 现在,这与原始问题的结果完全不同。感谢您浪费我们的时间来帮助您。下次,请在问问题之前弄清楚你想要什么。有关更新的问题,请参阅how to apply group by on xslt elements
  • @Nico 首先提出一个简单问题的问题在于,它可能走错了路。这称为XY Problem。让我引用该链接的开头:XY 问题是询问您尝试的解决方案,而不是您的实际问题。 [...] 这可能会导致试图帮助您解决问题的人 [像我这样] 感到沮丧,因为当您提出问题时,您需要帮助的解决方案可能已经与您要解决的问题有任何明显的联系。
【解决方案2】:

第二个(最终).csv 可以按如下方式生成:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8" />

<xsl:template match="/FirstTag">
    <!-- first pass -->
    <xsl:variable name="values-rtf">
        <xsl:for-each select="SecondTag/*">
            <xsl:copy>
                <xsl:for-each select="Furniture">
                    <value>
                        <xsl:value-of select="@FURN_AMT * @price"/>
                    </value>
                </xsl:for-each>
            </xsl:copy>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="values" select="exsl:node-set($values-rtf)/*" />
    <!-- header -->
    <xsl:text>Name,</xsl:text>
    <xsl:for-each select="$values">
        <xsl:value-of select="name()"/>
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    <!-- summary -->
    <xsl:value-of select="SecondTag/@Name"/>
    <xsl:text>,</xsl:text>
    <xsl:for-each select="$values">
        <xsl:value-of select="sum(value)"/>
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

这假设您仅限于 XSLT 1.0;在 XSLT 2.0 中,这可以一次性完成。

请注意,我假设输入 XML 将包含一个单个“房子”(SecondTag),以及可变数量的“房间”(ab、@987654325 @, 等等。)。否则不清楚 .csv 的标头应该是什么。


我不确定您是否还需要临时 .csv - 在任何情况下,创建它所需的逻辑都不清楚(为什么输出中缺少 FUR00013?)。

【讨论】:

  • 我正在根据@Andreas 的建议编辑问题。最终输出已在我的帖子中更新。
  • @NicoPratama 你澄清的越多,它就越不清楚。在我看来,您应该先弄清楚自己需要什么,然后再请其他人花时间提供帮助。
  • 简而言之,我需要房屋名称和家具 ID 作为 csv 的主键。所以每个房屋的每个家具都有自己的行。即 house01 +家具01,house01 + 家具02、房子01 + 家具03、房子02 + 家具01,等等……
  • @NicoPratama 这将是一个微不足道的问题 - 你在哪里坚持?
  • @michael.hor275k 更新了我正在工作的 xsl,我是否需要 xsl 分组才能达到我想要的最终输出(已编辑)?
猜你喜欢
  • 2014-09-20
  • 1970-01-01
  • 2019-08-03
  • 1970-01-01
  • 1970-01-01
  • 2019-02-02
  • 2014-07-20
  • 1970-01-01
  • 2017-05-23
相关资源
最近更新 更多