对于每个孩子

时间:2016-12-01 16:34:54

标签: xml xslt xpath

我需要复制子元素的每个实例的父元素,但我不想在输出中包含子元素的兄弟元素。 然后,我想根据子节点中的字段对父项进行排序

示例输入:

<Customer>
  <a/>
  <b/>
  <Market>
    <MarketNumber>100</MarketNumber>
  </Market>
  <Market>
    <MarketNumber>200</MarketNumber>
  </Market>
  <c/>
</Customer>
<Customer>
  <a/>
  <b/>
  <Market>
    <MarketNumber>100</MarketNumber>
  </Market>
  <c/>
</Customer>

期望的输出(3个客户,每个市场一个):

<Customer>
  <a/>
  <b/>
  <Market>
    <MarketNumber>100</MarketNumber>
  </Market>
  <c/>
</Customer>
<Customer>
  <a/>
  <b/>
  <Market>
    <MarketNumber>100</MarketNumber>
  </Market>
  <c/>
</Customer>
<Customer>
  <a/>
  <b/>
  <Market>
    <MarketNumber>200</MarketNumber>
  </Market>
  <c/>
</Customer>

我通过选择所有市场并手动复制其父元素来解决它,但是想要在不命名它们的情况下完成它(它在父节点中的33个元素)。问题是我无法选择&#34; ../。&#34;因为那时我也复制了兄弟市场。

<xsl:apply-templates select="Customer/Market">
    <xsl:sort select="MarketNumber" data-type="number" order="ascending"/>
</xsl:apply-templates>
...
<xsl:template match="Market">
    <!--Duplicate all customer data for each market-->
    <Customer>
        <xsl:copy-of select="../a"/>
        <xsl:copy-of select="../b"/>
        <xsl:copy-of select="."/> <!--Current market -->
        <xsl:copy-of select="../c"/>
    </Customer>
</xsl:template>

我怀疑这可以通过复制除Market之外的父元素然后使用&#34;复制市场来解决。&#34;就像我上面所做但我无法弄清楚如何...任何提示欢迎!其他解决方案也很受欢迎,但它也需要处理排序。

谢谢! 理查德

2 个答案:

答案 0 :(得分:1)

<xsl:apply-templates select="Customer/Market">
    <xsl:sort select="MarketNumber" data-type="number" order="ascending"/>
</xsl:apply-templates>
...
<xsl:template match="Market">
    <!--Duplicate all customer data for each market-->
    <Customer>
        <xsl:copy-of select="../*[not(self::Market)] | ."/>
    </Customer>
</xsl:template>

这将复制所有不是Market元素的兄弟姐妹,并且还会复制当前(匹配的)Market元素。那是你想要的吗?

答案 1 :(得分:0)

我参加聚会晚了几年,但是我自己才有这个要求。我想出的解决方案是使用命名模板来递归复制XML树,不包括我不想复制的元素名称,并使用generate-id函数来标识仍要复制的节点。有关更多详细信息,请参见下面的代码中的注释。

此解决方案在XSLT 1.0中有效。

此处提供了XSLT小提琴示例:https://xsltfiddle.liberty-development.net/bnnZWm

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
  exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>

  <!-- copy everything as is, unless told otherwise-->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- 
  match on the document element
  ensure there's a single root element, Customers
  match on any Market nodes, so we get a copy of everything for that Market's customer (as per the Market template) with that Market
  OR match any customer that doesn't have any markets; so we don't exclude those from the results (did you want that; if not switch the apply-templates for the commented out version below)
  -->
  <xsl:template match="/">
    <Customers>
      <xsl:apply-templates select="//Market|//Customer[not(./Markets/Market)]" />
      <!-- <xsl:apply-templates select="//Market" /> -->
    </Customers>
  </xsl:template>

  <!-- 
  For the markets matched above, call our copyAllButMe template
  set the rootNode (the node to be copied) to the current Market's ancestor Customer element
  set the ignoreElementName to the current element's name (i.e. so when copying the customer, we don't copy any child Market elements
  set the includeNodeId to the current element's id (i.e. so when ignoring other Markets, we don't ignore the current element)
  -->
  <xsl:template match="Market">
    <xsl:call-template name="copyAllButMe">
      <xsl:with-param name="rootNode" select="../.." />
      <xsl:with-param name="ignoreElementName" select="local-name()" />
      <xsl:with-param name="includeNodeId" select="generate-id()" />
    </xsl:call-template>
  </xsl:template>

  <!--
  Test: 
    Is the current element's name a name we should be ignoring?  
    If it is, does this element's ID match the ID we want to include?
  If we're not ignoring this element name, or the id is the one to include then we proceed:
  Copy the element's name
  Copy any attrinbutes of thie element
  Loop through all child nodes of this element
  - If the child node is itself an element, call this template (i.e. recursion)
  - Otherwise (e.g. it' text node, comment node, etc), copy the node as-is
  -->
  <xsl:template name="copyAllButMe">
    <xsl:param name="rootNode" />
    <xsl:param name="ignoreElementName" />
    <xsl:param name="includeNodeId" />
    <xsl:if test="(local-name($rootNode) != $ignoreElementName) or (generate-id($rootNode) = $includeNodeId)">
      <xsl:element name="{local-name($rootNode)}" namespace="{namespace-uri($rootNode)}">
        <xsl:copy-of select="$rootNode/attribute::*" />
        <xsl:for-each select="$rootNode/child::node()">
          <xsl:choose>
            <xsl:when test="self::*">
              <xsl:call-template name="copyAllButMe">
                <xsl:with-param name="rootNode" select="self::node()" />
                <xsl:with-param name="ignoreElementName" select="$ignoreElementName" />
                <xsl:with-param name="includeNodeId" select="$includeNodeId" />
              </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="self::node()" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </xsl:element>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>