使用XSLT进行XML到XML的转换时出现编码层次结构问题- XSLT v1

3

在我的输入XML文件中,元素属性“lp”中有编码层次结构:

<element lp="1"/>
<element lp="1.1"/>
<element lp="2"/>
<element lp="3"/>
<element lp="3.1" />
<element lp="3.2" />
<element lp="3.2.1" />

如何将此xml数据转换为:
<element lp="1">
   <element lp="1.1"/>
</element>
<element lp="2"/>
<element lp="3">
   <element lp="3.1"/>
   <element lp="3.2">
      <element lp="3.2.1">
   </element>
</element>

您的输入XML仅具有以句点结尾的特定@lp属性(即lp="1.",lp="2.",lp="3."),但在输出XML中,所有@lp属性都以句点结尾,除了lp="3.2.1"。这是有意为之吗? - Tim C
抱歉,属性“lp”的值末尾没有点。 - marcinn
2个回答

3

可能有一种简单的方法可以使用XSLT2.0完成,但我在这里假设使用XSLT1.0。

需要注意的一点是,您的XML不严格有效,因为缺少根元素。为了回答问题,我假设根元素称为 elements

要实现这一点,我认为您需要一个函数来确定元素的“级别”。这可以通过计算@lp属性中句点的数量来完成。在XSLT1.0中,我通过从文本中删除所有句点,并将结果字符串长度与原始字符串长度进行比较来实现此目的。

<xsl:variable name="level" select="string-length(@lp) - string-length(translate(@lp, '.', ''))" />

因此,要匹配顶级元素,您需要执行以下操作...
<xsl:apply-templates 
   select="element[string-length(@lp) - string-length(translate(@lp, '.', '')) = 0]"/>

这将匹配以下元素。
<element lp="1."/>
<element lp="2."/>
<element lp="3."/>

接下来,对于每个匹配的元素,需要匹配后续元素,其中:

  • @lp属性以当前@lp属性开头
  • 级别比当前级别高1

可以使用以下选择器完成此操作:

<xsl:apply-templates 
   select="following-sibling::element[substring(@lp, 1, $len) = $lp][string-length(@lp) - string-length(translate(@lp, '.', '')) = $level + 1]"/>

(注意$len和$level是包含当前@lp属性长度和当前级别的变量)
将所有内容综合起来,得到以下XSLT...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:template match="/elements">
      <elements>
         <xsl:apply-templates select="element[string-length(@lp) - string-length(translate(@lp, '.', '')) = 0]"/>
      </elements>
   </xsl:template>

   <xsl:template match="element">
      <xsl:variable name="lp" select="@lp"/>
      <xsl:variable name="len" select="string-length(@lp)"/>
      <xsl:variable name="level" select="$len - string-length(translate(@lp, '.', ''))" />

      <xsl:copy>
         <xsl:copy-of select="@lp"/>
         <xsl:apply-templates select="following-sibling::element[substring(@lp, 1, $len) = $lp][string-length(@lp) - string-length(translate(@lp, '.', '')) = $level + 1]"/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

当应用于以下XML时:
<elements>
    <element lp="1"/>
    <element lp="1.1"/>
    <element lp="2"/>
    <element lp="3"/>
    <element lp="3.1"/>
    <element lp="3.2"/>
    <element lp="3.2.1"/>
</elements>

生成以下输出。
<elements>
    <element lp="1">
        <element lp="1.1"/>
    </element>
    <element lp="2"/>
    <element lp="3">
        <element lp="3.1"/>
        <element lp="3.2">
            <element lp="3.2.1"/>
        </element>
    </element>
</elements>

+1好答案。他的输入XML并非严格的“格式良好”。术语“有效”在这里不适用(无论是积极的还是消极的),因为没有模式(DTD等)可用于对文档进行验证。 - LarsH
感谢您的正确答案。我在问题中忽略了文档验证上下文,因为我只想专注于算法问题。 - marcinn

3
我认为这个问题之前已经有了答案... 这个样式表:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="root">
        <result>
            <xsl:apply-templates select="element[not(contains(@lp,'.'))]"/>
        </result>
    </xsl:template>
    <xsl:template match="element">
    <xsl:variable name="vLevel" select="concat(@lp,'.')"/>
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates
                 select="../element[starts-with(@lp,$vLevel)]
                                   [not(contains(substring-after(@lp,$vLevel),
                                                 '.'))]"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

输出:

<result>
    <element lp="1">
        <element lp="1.1"></element>
    </element>
    <element lp="2"></element>
    <element lp="3">
        <element lp="3.1"></element>
        <element lp="3.2">
            <element lp="3.2.1"></element>
        </element>
    </element>
</result>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接