【问题标题】:Merge XML nodes sharing the same name with "_LIST" in the node name and also at root level合并与节点名称中的“_LIST”共享相同名称的 XML 节点以及根级别
【发布时间】:2018-10-11 14:34:14
【问题描述】:

下面是输入 XML,我正在寻找所需的输出 -

   <xml>
    <a>
        <element0>987</element0>
    </a>
    <a>
        <a_list_one>
            <a_lag_one>
                <element1>123</element1>
                <element2>456</element2>
            </a_lag_one>
        </a_list_one>
        <a_list_one>
            <a_lag_one>
                <element1>789</element1>
                <element2>678</element2>
            </a_lag_one>                
        </a_list_one>
        <a_list_two>
            <a_lag_two>
                <a_list_three>
                    <a_lag_three>
                        <element3>570</element3>
                        <element4>678</element4>
                    </a_lag_three>
                </a_list_three>
                <a_list_three>
                    <a_lag_three>
                        <element3>989</element3>
                        <element4>231</element4>
                    </a_lag_three>
                </a_list_three>
            </a_lag_two>
            <a_lag_two>
                <a_list_three>
                    <a_lag_three>
                        <element3>570</element3>
                        <element4>678</element4>
                    </a_lag_three>
                </a_list_three>
                <a_list_three>
                    <a_lag_three>
                        <element3>9873</element3>
                        <element4>278</element4>
                    </a_lag_three>
                </a_list_three>
                <a_list_four>
                    <a_lag_four>
                        <element5>9121</element5>
                        <element6>9879</element6>
                    </a_lag_four>
                </a_list_four>
                <a_list_three>
                    <a_lag_four>
                        <element5>098</element5>
                        <element6>231</element6>
                    </a_lag_four>
                </a_list_three>
            </a_lag_two>
        </a_list_two>
        <a_list_four>
                    <a_lag_four>
                        <element5>654</element5>
                        <element6>7665</element6>
                    </a_lag_four>
        </a_list_four>
    </a>
    <b>
        <b_list_one>
            <b_lag_one>
                <element8>123</element8>
                <element9>456</element9>
            </b_lag_one>
        </b_list_one>
    </b>
    <b>
        <b_list_one>
            <b_lag_one>
                <element8>789</element8>
                <element9>678</element9>
            </b_lag_one>            
        </b_list_one>
    </b>
</xml>

所需的 XML 是:

   <xml>
    <a>
        <element0>987</element0>
        <a_list_one>
            <a_lag_one>
                <element1>123</element1>
                <element2>456</element2>
            </a_lag_one>
            <a_lag_one>
                <element1>789</element1>
                <element2>678</element2>
            </a_lag_one>
        </a_list_one>
        <a_list_two>
            <a_lag_two>
                <a_list_three>
                    <a_lag_three>
                        <element3>570</element3>
                        <element4>678</element4>
                    </a_lag_three>
                    <a_lag_three>
                        <element3>989</element3>
                        <element4>231</element4>
                    </a_lag_three>
                </a_list_three>
            </a_lag_two>
            <a_lag_two>
                <a_list_three>
                    <a_lag_three>
                        <element3>570</element3>
                        <element4>678</element4>
                    </a_lag_three>
                    <a_lag_three>
                        <element3>9873</element3>
                        <element4>278</element4>
                    </a_lag_three>
                    <a_lag_four>
                        <element5>098</element5>
                        <element6>231</element6>
                    </a_lag_four>
                </a_list_three>
                <a_list_four>
                    <a_lag_four>
                        <element5>9121</element5>
                        <element6>9879</element6>
                    </a_lag_four>
                </a_list_four>
            </a_lag_two>
        </a_list_two>
        <a_list_four>
            <a_lag_four>
                <element5>654</element5>
                <element6>7665</element6>
            </a_lag_four>
        </a_list_four>      
    </a>
    <b>
        <b_list_one>
            <b_lag_one>
                <element8>123</element8>
                <element9>456</element9>
            </b_lag_one>
            <b_lag_one>
                <element8>789</element8>
                <element9>678</element9>
            </b_lag_one>            
        </b_list_one>
    </b>
</xml>

我正在寻找可以转换为所需输出的 ​​XSL。在这里,共享相同名称并且还包含“_LIST”的节点应该合并在一起。但是,这个逻辑应该只发生在第一个“_LIST”节点内,不应该应用于内部节点。其次,在根级别也是要合并的节点。例如这里,应该只有一个“a”标签和“b”标签。请帮忙。

【问题讨论】:

  • 请发布您尝试的 XSLT。谢谢。
  • 你说,“这个逻辑应该只发生在第一个“_LIST”节点内,不应该应用于内部节点”。但是,您正在将 a_list_three 组合到所需的结果中。看起来你想要的结果还有其他不一致的地方。在输入中,element2 不在 a_lag_one 中,但它在所需结果中的 a_lag_one 中。您可能需要清理它。
  • 您好 Bluewood56,感谢您的提问。所需的 xml 是正确的。我的意思是,出现在 a_lag_two 中的 a_list_four 标记又出现在 a_list_three 中,不应与出现在 a_list_three 之外的 a_list_four 标记合并,因为它们是不同的,即使它们共享相同的名称,因为它们不属于同一个列表- a_list_three。那是关于 element2 的错字。我纠正了这一点。这也属于 a_lag_one。

标签: xml xslt xslt-1.0 xquery xquery-3.0


【解决方案1】:

这是 XSLT 1.0 的解决方案

  <xsl:stylesheet version="1.0"
  xmlns:msxml="urn:schemas-microsoft-com:xslt"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>

    <xsl:key name="xmlChildren" match="xml/*" use="local-name()"/>
    <xsl:key name="list" match="*[contains(local-name(),'_list')]" use="generate-id(..)"/>

    <!-- Select the child nodes of the xml node. -->
    <xsl:template match="xml/*">
      <!-- Get the name of the current node. -->
      <xsl:variable name="localName" select="local-name()"/>
      <!-- Is this the first child of the xml node with this name? -->
      <xsl:if test="generate-id(.) = generate-id(key('xmlChildren', $localName)[1])">
        <xsl:copy>
          <!-- Output all of the xml grandchild nodes of any xml child node with same name as the current node. -->
          <xsl:apply-templates select="key('xmlChildren', $localName)/*">
              <xsl:with-param name="parentName" select="$localName"/>
          </xsl:apply-templates>
        </xsl:copy>
      </xsl:if>
    </xsl:template>

    <!-- Select the nodes with a local name that contains '_list'. -->
    <xsl:template match="*[contains(local-name(),'_list')]">
      <xsl:param name="parentName"/>

      <xsl:variable name="parentID" select="generate-id(..)"/>

      <!-- Get the name of the current node. -->
      <xsl:variable name="localName" select="local-name()"/>

      <xsl:choose>
        <!-- Is this list a first generation grandchild of xml? -->
        <xsl:when test="parent::*/parent::xml">
          <!-- Is this the first instance of this list? -->
          <xsl:if test="generate-id(.) = generate-id(key('xmlChildren', $parentName)/*[local-name()=$localName][1])">
            <xsl:copy>
              <xsl:apply-templates select="key('xmlChildren', $parentName)/*[local-name()=$localName]/*"/>
            </xsl:copy>
          </xsl:if> 
        </xsl:when>
        <xsl:otherwise>
          <!-- Is this the first instance of this list? -->
          <xsl:if test="generate-id(.) = generate-id(key('list', $parentID)[local-name()=$localName][1])">
            <xsl:copy>
              <xsl:apply-templates select="key('list', $parentID)[local-name() = $localName]/*"/>
            </xsl:copy>
          </xsl:if>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>

    <xsl:template match="node()|@*">
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>  

  </xsl:stylesheet>

【讨论】:

  • Martin Honnen 的 1.0 解决方案基本上是该解决方案的副本。我考虑了第二个模板来抑制空列表。他的回答没有本质区别。所以,作为他抄袭我的,如果你选择 1.0 解决方案,我希望你选择这个。
  • 谢谢 Bluewood66。你的答案几乎是完美的。因为,我观察到了一个小问题。我在“a”标签中引入了一个 new_element。但是,“new_element”在重复的“a”标签之间的“a”标签下原样出现。它没有合并到父“a”标签中。但是,如果您观察到,“element0”得到了很好的合并。我对 new_element 也有同样的期待。请看这个 - xsltfiddle.liberty-development.net/jyH9rN5/1
  • 您可能希望漂亮地打印(格式化)您的输出,以便更容易查看 XSLT 生成的内容。好的,您添加了新的“a”标签作为“a”元素的子元素。该程序完全按照其编写的目的进行。它只合并 XML 节点的子节点和同级列表。在这一点上,我不确定你的要求是什么。但是,现在和最初的样子不同了……也就是说,我真诚地努力帮助你。希望您可以调整代码以执行您想要的操作。否则,如果您希望我按照合同为您完成工作,请告诉我。
  • 谢谢 Bluewood66。我确实对代码做了一个小的修改来解决这个目的。非常感谢你的帮助。热烈的问候。
【解决方案2】:

我认为在 XQuery 3 中,您可以使用两个嵌套的 for .. group by 表达式来解决这个问题:

/*/element { node-name(.) } {
    for $child-element at $pos in *
    group by $element-name := node-name($child-element)
    order by $pos[1]
    return
        element { $element-name } {
            for $grand-child at $pos in $child-element/*
            let $grand-child-name := node-name($grand-child)
            group by $key := $grand-child-name, $handle := contains(string($grand-child-name), '_list')
            order by $pos[1]
            return
                if ($handle)
                then
                    element { $key } {
                        $grand-child/*
                    }
                else $grand-child
        }
}

https://xqueryfiddle.liberty-development.net/pPgCcor

对于 XSLT 1,我会像已经建议的解决方案一样使用键,但我认为为每个键使用两种不同的匹配模式会更容易,一个用于由键建立的组中的第一个项目,该键创建一个副本并处理组的子节点,第二个为空以抑制处理组的重复元素名称:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="child-group" match="/*/*" use="name()"/>
  <xsl:key name="grand-child-group" match="/*/*/*[contains(local-name(), '_list')]" use="name()"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/*/*[generate-id() = generate-id(key('child-group', name())[1])]">
      <xsl:copy>
          <xsl:apply-templates select="key('child-group', name())/node()"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="/*/*[not(generate-id() = generate-id(key('child-group', name())[1]))]"/>

  <xsl:template match="/*/*/*[contains(local-name(), '_list')][generate-id() = generate-id(key('grand-child-group', name())[1])]">
      <xsl:copy>
          <xsl:apply-templates select="key('grand-child-group', name())/node()"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="/*/*/*[contains(local-name(), '_list')][not(generate-id() = generate-id(key('grand-child-group', name())[1]))]"/>  

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/jyH9rN5

根据您的评论,我还尝试使 XQuery 3 解决方案递归:

declare function local:group($elements as element()*) as element()*
{
  for $child-element at $pos in $elements
  let $child-name := node-name($child-element)
  group by $name-group := $child-name, $match := contains(string($child-name), '_list')
  order by $pos[1]
  return
      if ($match)
      then element { $name-group } {
          local:group($child-element/*)
      }
      else if (not($child-element/*))
      then $child-element
      else $child-element/element {$name-group} { local:group(*) }
};

/*/element { node-name(.) } {
    for $child-element at $pos in *
    group by $element-name := node-name($child-element)
    order by $pos[1]
    return element { $element-name } {
         local:group($child-element/*)
    }

}

https://xqueryfiddle.liberty-development.net/pPgCcor/1

【讨论】:

  • 您好 Martin,在您的解决方案中,它没有将“a_list_three”合并为一个。这意味着逻辑没有渗透到内部“_list”节点。
  • 我也非常喜欢 XQUERY 解决方案,但它也有同样的问题。内部 _list 节点未合并为一个。
  • 我理解您的要求“此逻辑 [..] 不应应用于内部节点”作为不合并更深层次元素的要求,XQuery 和 XSLT 1 都简单地应用合并/分组到根元素的子元素(对于任何元素名称)和包含“_list”的大子元素。因此,您需要更详细地解释您的要求,无论这是否应该是一个递归算法,或者是什么标准确切地确定了要合并的内容和不合并的内容。
  • @VarunVemuganti,我已经为 XQuery 解决方案添加了一个改进,它试图通过递归函数解决更深层次的合并/分组问题。
  • 嗨 Martin,我尝试在 XQUERY - 1 中使用 distinct-values(arg) 而不是 Group-By 执行此操作,但在 xquery-1 中似乎很难实现。
猜你喜欢
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多