XSLT 仅使用 XSLT 1.0 从 FMPXMLRESULT 动态重组 XML 数据

XSLT to regroup XML data dynamically from FMPXMLRESULT using only XSLT 1.0

我需要将 XML 数据从一种结构转换为另一种结构。我需要根据源 XML 中的元数据构建目标 XML。源 XML 具有固定结构,但目标 XML 的结构需要根据源 XML 中的元数据动态构建(包括标签名称和数据分组)。

以下来源 XML 提供了结构示例。源 XML 将始终使用 FMPXMLRESULT 结构。

XML

<?xml version="1.0" encoding="UTF-8" ?>
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
  <METADATA>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="ID" TYPE="NUMBER"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="Description" TYPE="TEXT"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="Customer" TYPE="TEXT"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderItem::ProductName" TYPE="TEXT"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderItem::UnitPrice" TYPE="NUMBER"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderItem::Quantity" TYPE="NUMBER"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderItem::TaxCode" TYPE="TEXT"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderItem::Total" TYPE="NUMBER"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderTaxCode::TaxCode" TYPE="TEXT"/>
    <FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="OrderTaxCode::TaxRate" TYPE="NUMBER"/>
  </METADATA>
  <RESULTSET FOUND="2">
    <ROW MODID="1" RECORDID="1">
      <COL><DATA>1</DATA></COL>
      <COL><DATA>Order for first project</DATA></COL>
      <COL><DATA>Customer No 1</DATA></COL>
      <COL>
        <DATA>Product A</DATA>
        <DATA>Product B</DATA>
      </COL>
      <COL>
        <DATA>10.50</DATA>
        <DATA>12.10</DATA>
      </COL>
      <COL>
        <DATA>2</DATA>
        <DATA>1</DATA>
      </COL>
      <COL>
        <DATA>VAT</DATA>
        <DATA>VAT0</DATA>
      </COL>
      <COL>
        <DATA>21</DATA>
        <DATA>12.1</DATA>
      </COL>
      <COL>
        <DATA>VAT</DATA>
        <DATA>VAT0</DATA>
      </COL>
      <COL>
        <DATA>0.2</DATA>
        <DATA>0</DATA>
      </COL>
    </ROW>
    <ROW MODID="1" RECORDID="2">
      <COL><DATA>2</DATA></COL>
      <COL><DATA>Order for second project</DATA></COL>
      <COL><DATA>Customer No 2</DATA></COL>
      <COL>
        <DATA>Product 2A</DATA>
        <DATA>Product 2B</DATA>
      </COL>
      <COL>
        <DATA>1.50</DATA>
        <DATA>345</DATA>
      </COL>
      <COL>
        <DATA>17</DATA>
        <DATA>2</DATA>
      </COL>
      <COL>
        <DATA>VAT0</DATA>
        <DATA>VAT</DATA>
      </COL>
      <COL>
        <DATA>25.5</DATA>
        <DATA>690</DATA>
      </COL>
      <COL>
        <DATA>VAT</DATA>
        <DATA>VAT0</DATA>
      </COL>
      <COL>
        <DATA>0.2</DATA>
        <DATA>0</DATA>
      </COL>
    </ROW>
  </RESULTSET>
</FMPXMLRESULT>

鉴于上述 XML(包括元数据),目标 XML 格式如下。

XML

<?xml version="1.0" encoding="UTF-8"?>
<OrderBatch>
  
  <Order>
    <ID>1</ID>
    <Description>Order for first project</Description>
    <Customer>Customer No 1</Customer>
    <OrderItem>
      <ProductName>Product A</ProductName>
      <UnitPrice>10.50</UnitPrice>
      <Quantity>2</Quantity>
      <TaxCode>VAT</TaxCode>
      <Total>21</Total>
    </OrderItem>
    <OrderItem>
      <ProductName>Product B</ProductName>
      <UnitPrice>12.10</UnitPrice>
      <Quantity>1</Quantity>
      <TaxCode>VAT0</TaxCode>
      <Total>12.1</Total>
    </OrderItem>
    <OrderTaxCode>
      <TaxCode>VAT</TaxCode>
      <TaxRate>0.2</TaxRate>
    </OrderTaxCode>
    <OrderTaxCode>
      <TaxCode>VAT0</TaxCode>
      <TaxRate>0</TaxRate>
    </OrderTaxCode>
  </Order>

  <Order>
    <ID>2</ID>
    <Description>Order for second project</Description>
    <Customer>Customer No 2</Customer>
    <OrderItem>
      <ProductName>Product 2A</ProductName>
      <UnitPrice>1.50</UnitPrice>
      <Quantity>17</Quantity>
      <TaxCode>VAT0</TaxCode>
      <Total>25.5</Total>
    </OrderItem>
    <OrderItem>
      <ProductName>Product 2B</ProductName>
      <UnitPrice>345</UnitPrice>
      <Quantity>2</Quantity>
      <TaxCode>VAT</TaxCode>
      <Total>690</Total>
    </OrderItem>
    <OrderTaxCode>
      <TaxCode>VAT</TaxCode>
      <TaxRate>0.2</TaxRate>
    </OrderTaxCode>
    <OrderTaxCode>
      <TaxCode>VAT0</TaxCode>
      <TaxRate>0</TaxRate>
    </OrderTaxCode>
  </Order>

</OrderBatch>

源 XML 中的不同元数据会产生不同的目标 XML。一般规则如下

  1. 字段名称包含在 METADATA 中,数据包含在 RESULTSET
  2. RESULTSET中每ROW中的COL节点位置对应METADATA中的FIELD节点
  3. 名称中包含文本“::”的任何 RESULTSET/FIELD 都应被视为 'grouped' 数据
  4. 分组数据的组名应等于“::”之前的文本(此符号只会在字段名称中出现一次)
  5. 分组数据 COL 节点可能包含 0 个、1 个或多个 DATA 子节点
  6. 未分组的 FIELD 节点(即 NAME 不包含“::”)在 COL 节点中始终恰好有 1 个 DATA 子节点
  7. 分组数据字段将始终相邻(例如,组 ORDERITEM:: 中的所有字段在字段顺序中不会有来自其他组的字段)
  8. 组名、字段名和字段顺序事先未知,可能会更改,XSLT 需要动态处理。上面的 XML 是需要处理的事情的一个很好的例子
  9. 我只能使用 XSLT 1.0
  10. FMPDSORESULT 是一项已弃用的技术,我无法使用它

两个主要症结是

  1. 按位置从COL节点中拉出DATA节点,并分配到各自的组中
  2. 通过元数据分离实现所需

我已经尝试了多种使用嵌套 for-each 循环的方法,以及组织模板的不同方法。我想知道是否可以创建一个内部数据结构,但我也可能看错了问题?

这是我迄今为止想到的最好的,这是我最接近的,但还不够接近

XSLT 1.0

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
  exclude-result-prefixes="fmp"
>
  <xsl:output indent="yes"/>


  <!-- the key indexes the METADATA fields by their position -->
  <xsl:key
    name="fieldList"
    match="fmp:METADATA/fmp:FIELD"
    use="count(preceding-sibling::fmp:FIELD) + 1"
  />

  <!-- template for the data section of the FileMaker XML -->
  <xsl:template match="/fmp:FMPXMLRESULT">
    <OrderBatch>
      <xsl:apply-templates select="fmp:RESULTSET/fmp:ROW" />
    </OrderBatch>
  </xsl:template>

    <!-- template for each row -->
  <xsl:template match="fmp:ROW">
        <!-- for each row, create Order element and apply the relevant template for each column -->
    <Order>
      <xsl:apply-templates select="fmp:COL" />
    </Order>
  </xsl:template>

  <!-- template for each column within each row -->
  <xsl:template match="fmp:COL">
        <!-- set $qualified with the name of the field - this will be qualified with the table occurrence if related -->
    <xsl:variable name="qualified" select="string(key('fieldList', position())/@NAME)"/>
    <!-- set $group with the name of the field's group -->
    <xsl:variable name="group" select="substring-before($qualified, '::')"/>
        <!-- set $name to a value for use as an XML element -->
        <xsl:variable name="name">
            <xsl:choose>

                <!-- if the qualified field is related (contains "::") then remove the table occurrence name -->
            <xsl:when test="contains($qualified, '::')">
                    <xsl:value-of select="substring-after($qualified, '::')"/>
            </xsl:when>

                <!-- if the qualified field is not related then just return the field name -->
            <xsl:otherwise>
                    <xsl:value-of select="$qualified"/>
            </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>



    <!-- create the element with the field's name and use the data as the element's value -->
    <xsl:choose>
      <!-- related element - need to group -->
      <xsl:when test="contains($qualified, '::')">
        <!-- group each DATA element in turn -->
        <!-- actually only need to run this on the first COL in a group - but I'll figure that out later -->
        <xsl:for-each select="fmp:DATA">
          <xsl:apply-templates select=".">
            <xsl:with-param name="pGroup" select="$group" />
            <xsl:with-param name="pName" select="$name" />
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:when>

      <!-- element is at top level so just create the field/value -->
      <xsl:otherwise>
        <xsl:element name="{$name}">
                <xsl:value-of select="." />
            </xsl:element>
        </xsl:otherwise>
        </xsl:choose>

    </xsl:template>

  <!-- template for grouping DATA nodes across multiple COL nodes -->
  <xsl:template match="fmp:DATA">
    <xsl:param name = "pGroup" />
    <xsl:param name = "pName" />
    <xsl:element name="{$pGroup}">
      <xsl:variable name="pos" select="position()" />
        <xsl:apply-templates select="../../fmp:COL" mode="group">
          <xsl:with-param name="pGroup" select="$pGroup" />
          <xsl:with-param name="pName" select="$pName" />
          <xsl:with-param name="pos" select="$pos" />
        </xsl:apply-templates>

    </xsl:element>
  </xsl:template>

  <!-- template for cycling through COL nodes and getting the DATA node if it belongs to the specified group -->
  <xsl:template match="fmp:COL" mode="group">
    <xsl:param name = "pGroup" />
    <xsl:param name = "pName" />
    <xsl:param name = "pos" /> <!-- this will help select the correct DATA node - not sure how to use it yet though -->
    <xsl:variable name="qualified" select="string(key('fieldList', position())/@NAME)"/>
    <xsl:variable name="colGroup" select="substring-before($qualified, '::')"/>
    <xsl:if test="contains($qualified, '::') and $pGroup = $colGroup">
      <xsl:element name="substring-after($qualified, '::')">
                <xsl:value-of select="." />
            </xsl:element>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

我知道这并不容易,也不是使用 XSLT 的正常方式(它通常会被编写为适合目标结构),但是我相信这是一个可以解决的问题并且 XSLT 似乎有能力复杂得多的任务。

非常欢迎任何有关如何解决此问题的帮助。非常感谢。

FMPXMLRESULT 语法的优势在于它允许您更改解决方案的字段名称而无需破坏 XSLT 样式表。输出元素和属性名称应该符合您的目标应用程序的 XML 模式,并被硬编码到样式表中。

因此我建议您忽略 METADATA 部分中包含的字段名称,并仅根据字段在字段导出顺序中的位置进行转换。否则您应该使用 FMPDSORESULT 语法,您可以在其中更改字段导出顺序,但不能更改字段名称。

考虑到这一点,您的样式表可能如下所示:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fmp="http://www.filemaker.com/fmpxmlresult" 
exclude-result-prefixes="fmp">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/fmp:FMPXMLRESULT">
    <OrderBatch>
        <xsl:for-each select="fmp:RESULTSET/fmp:ROW">
            <Order>
                <!-- order -->
                <ID>
                    <xsl:value-of select="fmp:COL[1]/fmp:DATA"/>
                </ID>
                <Description>
                    <xsl:value-of select="fmp:COL[2]/fmp:DATA"/>
                </Description>
                <Customer>
                    <xsl:value-of select="fmp:COL[3]/fmp:DATA"/>
                </Customer>
                <!-- items -->
                <xsl:for-each select="fmp:COL[4]/fmp:DATA">
                    <xsl:variable name="i" select="position()"/>
                    <OrderItem>
                        <ProductName>
                            <xsl:value-of select="."/>
                        </ProductName>
                        <UnitPrice>
                            <xsl:value-of select="../../fmp:COL[5]/fmp:DATA[$i]"/>
                        </UnitPrice>
                        <Quantity>
                            <xsl:value-of select="../../fmp:COL[6]/fmp:DATA[$i]"/>
                        </Quantity>
                        <TaxCode>
                            <xsl:value-of select="../../fmp:COL[7]/fmp:DATA[$i]"/>
                        </TaxCode>
                        <Total>
                            <xsl:value-of select="../../fmp:COL[8]/fmp:DATA[$i]"/>
                        </Total>
                    </OrderItem>
                </xsl:for-each>
                <!-- tax codes -->
                <xsl:for-each select="fmp:COL[9]/fmp:DATA">
                    <xsl:variable name="i" select="position()"/>
                    <OrderTaxCode>
                        <TaxCode>
                            <xsl:value-of select="."/>
                        </TaxCode>
                        <TaxRate>
                            <xsl:value-of select="../../fmp:COL[10]/fmp:DATA[$i]"/>
                        </TaxRate>
                    </OrderTaxCode>
                </xsl:for-each>
            </Order>
        </xsl:for-each>
    </OrderBatch>
</xsl:template>

</xsl:stylesheet>

为了解决这个问题,我在 XSLT 中采用了 2 遍方法并使用了 xmlns:exslt 扩展。

正如已经指出的那样,确实没有完全通用的解决方案,也没有完全面向未来的解决方案,但是我这里的解决方案足以满足我的需求。我有 commented/documented 下面的代码。希望其他人会发现它有用,甚至在将来改进它。

XSLT 1.0

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
  xmlns:exslt="http://exslt.org/common"
  exclude-result-prefixes="fmp"
>
  <xsl:output indent="yes"/>

  <!--
  NOTES ON NON-GENERALISED ELEMENTS

  REQUIRED NODE - <pk>
  each record in the source XML must contain a node named <pk> for the following to work
  this is required when defining the matching criteria for the xsl:key "KeyGroups", and anywhere that needs to match this key
  the <pk> node is used in the second pass, the name of the <pk> node would need to be amended in all 3 places in the second pass if it needed changing

  SPECIFIC NODE NAMES - this information can't be derived from the source document
  OrderBatch - is the name of the root node that will contain the target XML
  Order - is the name of each of the record nodes inside the container
  these node names can be changed as long as every instance of them is changed throughout the document

  OVERRIDE FIELD NAMES - there is a section in the first pass that allows the renaming of field names (by substituting the fully qualified field name for a custom string)
  the code should still work without any renaming as the renaming serves no functional purpose it is merely a way to tidy field names up if needed
  -->


  <!-- index the METADATA fields by their position -->
  <xsl:key
    name="KeyMetaData"
    match="fmp:METADATA/fmp:FIELD"
    use="count(preceding-sibling::fmp:FIELD) + 1"
  />



  <!-- ### FIRST PASS ### -->

  <!-- set the results of the first pass into $firstPassResult, this will then be used in the second pass -->
  <xsl:variable name="firstPassResult">
    <xsl:apply-templates select="/" mode="firstPass"/>
  </xsl:variable>

  <!-- template to start reading the data from FMPXMLRESULT -->
  <xsl:template match="/fmp:FMPXMLRESULT" mode="firstPass">
    <OrderBatch>
      <xsl:apply-templates select="fmp:RESULTSET/fmp:ROW" mode="firstPass" />
    </OrderBatch>
  </xsl:template>

    <!-- template for each row in FMPXMLRESULT -->
  <xsl:template match="fmp:ROW" mode="firstPass">
        <!-- for each row, create Order element and apply the relevant template for each column -->
    <Order>
      <xsl:apply-templates select="fmp:COL" mode="firstPass" />
    </Order>
  </xsl:template>

  <!-- template for each column in FMPXMLRESULT -->
  <xsl:template match="fmp:COL" mode="firstPass">
        <!-- set $qualified with the name of the field - this will be qualified with the table occurrence if related -->
    <xsl:variable name="qualified" select="string(key('KeyMetaData', position())/@NAME)"/>
    <xsl:variable name="group" select="translate(substring-before($qualified, '::'), ' :.', '')"/>

        <!-- set $name to a value for use as an XML element -->
        <xsl:variable name="name">
            <xsl:choose>

                <!-- ################################################## -->
                <!-- SPECIFIC FIELD NAME OVERRIDES HERE -->
                <xsl:when test="$qualified = 'order.order_revision.company_CUSTOMER::company_name'"><xsl:value-of select="'CustomerName'"/></xsl:when>
                <xsl:when test="$qualified = 'order.order_revision.company_SUPPLIER::company_name'"><xsl:value-of select="'SupplierName'"/></xsl:when>
                <!-- ################################################## -->

                <!-- if the qualified field is related (contains "::") then $name shouldn't include the table occurrence name -->
            <xsl:when test="contains($qualified, '::')">
                    <xsl:value-of select="translate(substring-after($qualified, '::'), ' :.', '')"/>
            </xsl:when>

                <!-- if the qualified field is not related then qualified will be the field name -->
            <xsl:otherwise>
                    <xsl:value-of select="translate($qualified, ' :.', '')"/>
            </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        <!-- create the element with the field's name and use the data as the element's value -->
        <xsl:for-each select="fmp:DATA">
            <xsl:element name="{$name}">
        <!-- include some attributes for elements that need to be grouped in the next pass -->
                <xsl:if test="count(preceding-sibling::fmp:DATA | following-sibling::fmp:DATA) > 0">
          <!-- add an attribute 'g' to indicate the name of the group it belongs to -->
          <xsl:attribute name="g">
                        <xsl:value-of select="$group" />
                    </xsl:attribute>
          <!-- add an attribute 'n' to indicate the which iteration of the data this is -->
          <xsl:attribute name="n">
                        <xsl:value-of select="count(preceding-sibling::fmp:DATA) + 1" />
                    </xsl:attribute>
                </xsl:if>
        <!-- populate with data from the original node -->
                <xsl:value-of select="." />
            </xsl:element>
        </xsl:for-each>
    </xsl:template>



  <!-- ### SECOND PASS ### -->

  <!-- the second pass uses $firstPassResult (converted to a node set using namepsace exslt:node-set as declared in header) -->
  <xsl:template match="/">
    <xsl:apply-templates select="exslt:node-set($firstPassResult)" mode="secondPass"/>
  </xsl:template>

  <!-- template for the outer container -->
  <xsl:template match="OrderBatch" mode="secondPass">
    <xsl:copy>
      <xsl:apply-templates select="Order" mode="secondPass" />
    </xsl:copy>
  </xsl:template>

  <!-- template for each record -->
  <xsl:template match="Order" mode="secondPass">
    <xsl:call-template name="grouping" />
  </xsl:template>

  <!-- index unique groups, uniqueness is based on
  attribute @g - the group as defined in first pass
  attribute @n - the data iteration as defined in first pass
  the parent record's <pk> node - the requirement of this node is an unfortunate but manageable constraint on the source data -->
  <xsl:key name="KeyGroups" match="*[@n]" use="concat(@g,'+',@n,'+',../pk)" />

  <!-- the grouping template -->
  <xsl:template match="@*|node()" name="grouping" mode="secondPass">
    <xsl:copy><xsl:apply-templates select="@*|node()" mode="secondPass"/></xsl:copy>
  </xsl:template>

  <!-- template to match the first element with each @gr value -->
  <xsl:template match="*[@n][generate-id() =
         generate-id(key('KeyGroups', concat(@g,'+',@n,'+',../pk))[1])]" priority="2" mode="secondPass">
   <xsl:element name="{@g}">
      <xsl:for-each select="key('KeyGroups', concat(@g,'+',@n,'+',../pk))">
        <xsl:call-template name="grouping" />
      </xsl:for-each>
    </xsl:element>
  </xsl:template>

  <!-- ignore subsequent nodes, they're handled within the first element template -->
  <xsl:template match="*[@n]" priority="1" mode="secondPass" />

</xsl:stylesheet>

这可能对您有所帮助。如前所述,您将需要计算出前缀才能使用节点集功能。您可以验证假设并进行调整。如果您有任何问题,请告诉我。

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
  xmlns:msxml="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="fmp">

  <xsl:output indent="yes"/>

  <xsl:variable name="metadata">
    <xsl:for-each select="//fmp:FIELD">
      <xsl:element name="element">
        <xsl:variable name="name" select="@NAME"/>
        <xsl:choose>
          <xsl:when test="contains($name, '::')">
            <xsl:element name="parent">
              <xsl:value-of select="substring-before($name, '::')"/>
            </xsl:element>
            <xsl:element name="child">
              <xsl:value-of select="substring-after($name, '::')"/>
            </xsl:element>
          </xsl:when>
          <xsl:otherwise>
            <xsl:element name="parent">
              <xsl:value-of select="$name"/>
            </xsl:element>
            <xsl:element name="child"/>
          </xsl:otherwise>
        </xsl:choose>  
        <xsl:element name="position">
          <xsl:value-of select="position()"/>
        </xsl:element>
    </xsl:element>
    </xsl:for-each>
  </xsl:variable>

  <!-- NOTE:  Replace msxml with your prefix that converts RTF's to node sets.  -->
  <xsl:variable name="metadataList" select="msxml:node-set($metadata)"/>

  <!-- Get a distinct list of parent elements.  -->
  <xsl:variable name="parents">
    <xsl:copy-of select="$metadataList/element[not(parent = preceding-sibling::element/parent)]"/>  
  </xsl:variable>

  <xsl:variable name="parentList" select="msxml:node-set($parents)"/>

  <xsl:template match="fmp:FMPXMLRESULT">
    <xsl:element name="OrderBatch">
      <xsl:apply-templates select="node()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="fmp:ROW">
    <xsl:variable name="rowNode" select="."/> 
    <xsl:element name="Order">
      <!-- Loop thru the distinct list of parents.  -->
      <xsl:for-each select="$parentList/element">
        <xsl:variable name="parent" select="parent"/>
        <xsl:variable name="firstSetPosition" select="position"/>
        <!-- Loop thru on the first set of data nodes for this parent.  -->
        <xsl:for-each select="$rowNode/fmp:COL[number($firstSetPosition)]/fmp:DATA">
          <xsl:variable name="dataposition" select="position()"/>
          <xsl:element name="{$parent}">
            <!-- Loop thru all the child nodes for this parent.  -->
            <xsl:for-each select="$metadataList/element[parent = $parent]">
              <xsl:variable name="position" select="position"/>
              <xsl:variable name="child" select="string(child)"/>
              <xsl:choose>
                <!-- When the parent has no child nodes.  -->
                <xsl:when test="$child = ''">
                  <xsl:value-of select="string($rowNode/fmp:COL[number($position)]/fmp:DATA[$dataposition])"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:element name="{$child}">
                    <xsl:value-of select="string($rowNode/fmp:COL[number($position)]/fmp:DATA[$dataposition])"/>
                  </xsl:element>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:for-each>
          </xsl:element>
        </xsl:for-each>
        </xsl:for-each>  
      </xsl:element>
  </xsl:template>

  <xsl:template match="fmp:DATA">
    <xsl:value-of select="."/>
  </xsl:template>

  <xsl:template match="node()">
      <xsl:apply-templates select="node()"/>
  </xsl:template>
</xsl:stylesheet>