【问题标题】:excel xml to xml - xslt for iterating inner loopexcel xml to xml - 用于迭代内部循环的 xslt
【发布时间】:2013-11-08 08:57:39
【问题描述】:

我正在努力将 excel xml 文件转换为另一种 xml 格式。

以下是源代码的简化摘录:

<Workbook>
  <Worksheet>
    <Table>
      <Row>
        <Cell>
          <Data>Test 1</Data>
        </Cell>
        <Cell>
          <Data>Preconditions for test 1</Data>
        </Cell>
        <Cell>
          <Data>The setup for test 1</Data>
        </Cell>
        <Cell />
      </Row>
      <Row>
        <Cell />
        <Cell>
          <Data>Step 1</Data>
        </Cell>
        <Cell>
          <Data>Todo in step 1</Data>
        </Cell>
        <Cell>
          <Data>Expected result</Data>  <!--omitted if empty-->
        </Cell>
      </Row>
         .
         .
      <Row>
        <Cell />
        <Cell>
          <Data>Step n</Data>
        </Cell>
        <Cell>
          <Data>Todo in step n</Data>
        </Cell>
        <Cell>
          <Data>Expected result</Data>  <!--omitted if empty-->
        </Cell>
      </Row>
         .
         .
 -----------------
         .
         .
      <Row>
        <Cell>
          <Data>Test m</Data>
        </Cell>
        <Cell>
          <Data>Preconditions for test m</Data>
        </Cell>
        <Cell>
          <Data>The setup for test m</Data>
        </Cell>
        <Cell />
      </Row>
      <Row>
        <Cell />
        <Cell>
          <Data>Step 1</Data>
        </Cell>
        <Cell>
          <Data>Todo in step 1</Data>
        </Cell>
        <Cell />
        <Cell>
          <Data>Expected result</Data>  <!--omitted if empty-->
        </Cell>
      </Row>          
         .
         .
      <Row>
        <Cell />
        <Cell>
          <Data>Step k</Data>
        </Cell>
        <Cell>
          <Data>Todo in step k</Data>
        </Cell>
        <Cell>
          <Data>Expected result</Data> <!--omitted if empty-->
        </Cell>
      </Row>
    </Table>
  </Worksheet>
</Workbook>

第 2 列 (Cell[2]) 可以看作是第 3-4 列的标题

我想要的输出应该是以下格式:

<case>
  <title>Test 1</title>
  <precond>The setup for test 1</precond>
  <step>
    <index>1</index>
    <content>Todo in step 1</content>
    <expected>Expected result</expected> <!--omitted if empty-->
  </step>
     .
     .
  <step>
    <index>n</index>
    <content>Todo in step n</content>
    <expected>Expected result</expected> <!--omitted if empty-->
  </step>
</case>
   .
   .
.......
   .
   .
<case>
  <title>Test m</title>
  <precond>The setup for test m</precond>
  <step>
    <index>1</index>
    <content>Todo in step 1</content>
    <expected>Expected result</expected>  <!--omitted if empty-->
  </step>
    .
    .
  <step>
    <index>n</index>
    <content>Todo in step n</content>
    <expected>Expected result</expected>  <!--omitted if empty-->
  </step>
</case>

我的问题是定义 xslt 以便 &lt;case&gt; 标签包含所有行,直到下一行在 Cell[1] 中有数据条目。如果我使用&lt;xsl:if test="Cell[1]/Data"&gt; 查找下一个测试,则必须在&lt;/xsl:if&gt; 之前输入结束&lt;/case&gt;,因此我需要在该语句中迭代以下同级行(测试步骤)。我该如何做到这一点?

这是我对 xslt 的微弱尝试:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" />


<xsl:template match="Workbook/Worksheet/Table">
  <xsl:for-each select="Row">
    <xsl:if test="Cell[1]/Data">   
      <case>
        <title>
          <xsl:value-of select="Cell[1]/Data"/>
        </title>
        <xsl:if test="Cell[3]/Data">
          <precond>
            <xsl:value-of select="Cell[3]/Data"/>
          </precond>
        </xsl:if>

        <!-- Here I need to iterate "while not" following-sibling::Cell[1]/Data and extract the data from cells 2-4.-->

      </case>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

【问题讨论】:

  • 它需要是 XSLT 1.0 还是你有能力使用 XSLT 2.0?
  • 马克,我还没有真正考虑过这些差异。我也不太熟悉。我使用 System.Xml.Xsl 作为 .net 中的引擎制作了一个小 C# 程序。从here看来,xslt 1.0 是必要的
  • 还添加了 XSLT 1.0 解决方案。

标签: xml excel xslt xslt-1.0


【解决方案1】:

如果您坚持使用 XSLT 1.0,索引是一种可行且简单的方法:

<xsl:key name="steps" match="Row[not(Cell[1]/Data)]" use="generate-id(preceding-sibling::Row[Cell[1]/Data][1])"/>

<xsl:template match="/">
  <result>
    <xsl:for-each select="//Row[Cell[1]/Data]">
      <case>
        <title><xsl:value-of select="Cell[1]/Data"/></title>
        <precond><xsl:value-of select="Cell[3]/Data"/></precond>
        <xsl:for-each select="key('steps', generate-id(.))">
          <step>
            <index><xsl:value-of select="Cell[2]/Data" /></index>
            <content><xsl:value-of select="Cell[3]/Data" /></content>
            <expected><xsl:value-of select="Cell[4]/Data" /></expected>
          </step>
        </xsl:for-each>
      </case>
    </xsl:for-each>     
  </result>
</xsl:template>

【讨论】:

  • 谢谢。这和马克的解决方案都有效。不关心效率,我为代码大小选择了这个解决方案。
【解决方案2】:

如果您能够使用 XSLT 2.0,您可以使用以下 XSLT 作为示例:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <!-- Identity template to 'loop' through all input XML (nodes and attributes) -->
    <xsl:template match="@*|node()">
        <xsl:apply-templates select="@*|node()" />
    </xsl:template>

    <!-- create new root element -->
    <xsl:template match="Workbook">
        <data>
            <xsl:apply-templates select="@*|node()" />
        </data>
    </xsl:template>

    <!-- Match on Table element, to do some grouping -->
    <xsl:template match="Table">
        <!-- Group all rows together, each group starts with a Row where Cell[1] is not empty -->
        <xsl:for-each-group select="Row" group-starting-with="Row[Cell[1] != '']">
            <case>
                <title><xsl:value-of select="Cell[1]/Data" /></title>
                <precond><xsl:value-of select="Cell[3]/Data" /></precond>
                <!-- Loop through the group, but forget the first occurence, since it is the header, use before this -->
                <xsl:for-each select="current-group()[position() &gt; 1]">
                    <step>
                        <index><xsl:value-of select="Cell[2]/Data" /></index>
                        <content><xsl:value-of select="Cell[3]/Data" /></content>
                        <expected><xsl:value-of select="Cell[4]/Data" /></expected>
                    </step>
                </xsl:for-each>
            </case>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

以下 XSLT 将在 XSLT 1.0 甚至 XSLT 2.0 中工作:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <!-- Identity template to 'loop' through all input XML (nodes and attributes) -->
    <xsl:template match="@*|node()">
        <xsl:apply-templates select="@*|node()" />
    </xsl:template>

    <!-- create new root element -->
    <xsl:template match="Workbook">
        <data>
            <xsl:apply-templates select="descendant::Row[Cell[1] != '']" />
        </data>
    </xsl:template>

    <!-- Match Rows where first Cell is empty and create the <case> element with header information -->
    <xsl:template match="Row[Cell[1] != '']">
        <case>
            <title><xsl:value-of select="Cell[1]/Data" /></title>
            <precond><xsl:value-of select="Cell[3]/Data" /></precond>

            <xsl:choose>
                <xsl:when test="count(following-sibling::Row[Cell[1] != '']) = 0">
                    <!-- When there are no next Row elements with empty first Cells, we can just process the remaining Rows -->
                    <xsl:apply-templates select="following-sibling::Row[Cell[1] = '']" />
                </xsl:when>
                <xsl:otherwise>
                    <!-- There are still Rows with empty first Cells, so we only process the Rows in between -->
                    <xsl:apply-templates select="following-sibling::Row[Cell[1] != ''][1]/preceding-sibling::Row[Cell[1] = '']" />
                </xsl:otherwise>
            </xsl:choose>
        </case>
    </xsl:template>

    <!-- Process Rows with empty first Cells, which will be the step information -->
    <xsl:template match="Row[Cell[1] = '']">
        <step>
            <index><xsl:value-of select="Cell[2]/Data" /></index>
            <content><xsl:value-of select="Cell[3]/Data" /></content>
            <expected><xsl:value-of select="Cell[4]/Data" /></expected>
        </step>
    </xsl:template>
</xsl:stylesheet>

【讨论】:

    猜你喜欢
    • 2017-10-04
    • 2018-08-03
    • 1970-01-01
    • 2021-09-29
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多