2013-11-08 3 views
1

Я изо всех сил пытаюсь преобразовать XML-файл excel в другой формат xml.excel xml to xml - xslt для итерации внутреннего цикла

Вот упрощенный экстракт источника:

<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 (Ячейка [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, так что <case> теги не охватывают все строки до следующей строки, которая имеет ввод данных в ячейке [1]. Если я использую <xsl:if test="Cell[1]/Data">, чтобы найти следующий тест, закрытие </case> должно быть введено до </xsl:if>, и поэтому мне нужно выполнить итерацию следующих строк (шагов теста) в этом выражении. Как это сделать?

Вот моя слабая попытка на 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> 
+0

Нужно ли быть XSLT 1.0 или у вас есть возможность использовать XSLT 2.0? –

+0

Марк, я действительно не рассматривал различия. Я тоже не очень хорошо знаком. Я сделал небольшую программу на C#, используя System.Xml.Xsl как движок в .net. Из [здесь] (http://stackoverflow.com/questions/1525299/xpath-and-xslt-2-0-for-net) кажется, что требуется xslt 1.0 – user2964512

+0

Также добавлено решение 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> 
+0

Спасибо. И это, и решения Марка работают. Не заботясь об эффективности, я выбираю это решение для размера кода. – user2964512

1

Если вы можете использовать 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> 
Смежные вопросы