【问题标题】:XSLT to regroup XML data dynamically from FMPXMLRESULT using only XSLT 1.0XSLT 仅使用 XSLT 1.0 从 FMPXMLRESULT 动态重组 XML 数据
【发布时间】:2021-04-16 22:33:46
【问题描述】:

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

两个主要症结是

  1. 按位置将 DATA 节点从 COL 节点中拉出并分配给它们自己的组
  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 似乎能够远更复杂的任务。

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

【问题讨论】:

    标签: xml xslt-1.0 filemaker xslt-grouping


    【解决方案1】:

    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,但是字段导出顺序可能会改变(尽管字段名称将始终保持不变)所以我希望有一种方法可以使用元数据来处理这个问题。据我了解,XSLT 可以实现我想做的事情(它似乎能够完成更复杂的任务),我真的很喜欢 XSLT 可以做到。
    • 另外,我排除了使用 FMPDSORESULT 的可能性,因为它是一种已弃用的技术,而且该解决方案需要经过验证。通过更新编辑了 OP。
    • 恐怕我不明白这个练习的目的。通常,导出旨在由目标应用程序使用,转换的目的是在目标应用程序的架构中创建一个文档。因此,您的字段名称是无关紧要的。 OTOH,字段导出顺序必须一成不变。
    • 在您确实需要原始字段名称的不太可能的情况下,我建议您再次使用 FMPDSORESULT 语法。诚然,它从版本 6 到 7 已被“弃用”——但它仍然存在于我们身边。 nothing 是“面向未来的”:任何功能都可以更改或删除,无论是否通知。是的,甚至可以按照您想要的方式转换 FMPXMLRESULT - 但我不会花时间在上面。
    • 好的,感谢您抽出宝贵时间回复。
    【解决方案2】:

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

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

    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>
    

    【讨论】:

      【解决方案3】:

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

      <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>
      

      【讨论】:

      • 这是一个很好的解决方案,比我得到的那个更好。它不需要在导出订单中包含“pk”字段,也不需要组的子项相邻。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-26
      • 1970-01-01
      • 2011-03-24
      • 2018-03-28
      • 1970-01-01
      相关资源
      最近更新 更多