XSLT 中的 Xpath:选择其他 2 个元素之间的元素,第二部分

发布于 2024-08-07 13:15:54 字数 4706 浏览 4 评论 0原文

与此问题类似(还有更多相关条目,但是作为新用户我只能发布一个网址): Xpath 获取 2 个元素之间的元素

我有一个问题关于“其他/分隔”元素之间出现的元素集的选择。当尝试将平面 HTML 表格转换为 使用 XSLT 的分层 XML 结构。我尝试在模板中使用递归,但 saxon 拒绝接受这一点,因为它导致了死锁,很可能是我的错,但让我们从头开始。

首先,源数据是 HTML 表:

<table >
    <thead>
        <tr>
            <th>Column 1</th>
            <th>Column 2</th>
            <th>Column 3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th colspan="3" >Group 1</th>
        </tr>
        <tr>
            <td>attribute 1.1.1</td>
            <td>attribute 1.1.3</td>
            <td>attribute 1.1.2</td>
        </tr>
        <tr>
            <td>attribute 1.2.1</td>
            <td>attribute 1.2.2</td>
            <td>attribute 1.2.3</td>
        </tr>
        <tr>
            <td>attribute 1.3.1</td>
            <td>attribute 1.3.2</td>
            <td>attribute 1.3.3</td>
        </tr>
        <tr>
            <th colspan="3" >Group 2</th>
        </tr>
        <tr>
            <td>attribute 2.1.1</td>
            <td>attribute 2.1.3</td>
            <td>attribute 2.1.2</td>
        </tr>
        <tr>
            <td>attribute 2.2.1</td>
            <td>attribute 2.2.2</td>
            <td>attribute 2.2.3</td>
        </tr>
        <tr>
            <td>attribute 2.3.1</td>
            <td>attribute 2.3.2</td>
            <td>attribute 2.3.3</td>
        </tr>
    </tbody>
</table>

XML 中的目标输出是:

 <groups>
    <group name="Group 1">
        <item attribute1="attribute 1.1.1" attribute2="attribute 1.1.3" attribute3="attribute 1.1.2"/>
        <item attribute1="attribute 1.2.1" attribute2="attribute 1.2.2" attribute3="attribute 1.2.3"/>
        <item attribute1="attribute 1.3.1" attribute2="attribute 1.3.2" attribute3="attribute 1.3.3"/>
    </group>
    <group name="Group 2">
        <item attribute1="attribute 2.1.1" attribute2="attribute 2.1.3" attribute3="attribute 2.1.2"/>
        <item attribute1="attribute 2.2.1" attribute2="attribute 2.2.2" attribute3="attribute 2.2.3"/>
        <item attribute1="attribute 2.3.1" attribute2="attribute 2.3.2" attribute3="attribute 2.3.3"/>
    </group>
</groups>

所以我想要拥有所有项目条目(TR 元素)并将它们添加到组中。这基本上可以归结为选择所有后续同级 TR 元素,直到我们遇到具有 TH 元素作为子元素的元素。如果我只能确定具有 TH 子项的第一个 TR 的位置(指示组的新标题),则可以通过以下方式完成:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:for-each select="following-sibling::tr[position() < $positionOfNextThElement]">            
                <xsl:call-template name="item"/>
            </xsl:for-each>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

但是,我无法确定第一个遇到的 TR/TH 标签的位置。

如前所述,我尝试在模板中使用递归:始终调用“item”模板,并在此模板中确定我们是否也想在下一个项目上调用它。我认为问题在于从模板内部调用模板。上下文中的项目没有增加?我应该交出一个参数来确定我们正在处理什么项目吗?

无论如何,这就是我的想法:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:call-template name="item"/>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

<xsl:template name="item">
    <xsl:element name="item">
        <xsl:attribute name="attribute1"><xsl:value-of select="following-sibling::tr[1]/td[1]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[2]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[3]"/></xsl:attribute>
    </xsl:element>
    <!-- When the next element has not got a TH tag, continue with invoking this template -->
    <xsl:if test="count(following-sibling::tr[1]/th) != 1">
        <xsl:call-template name="item"/>
    </xsl:if>
</xsl:template>

欢迎任何关于如何实现这一点的建议!

As similar to this question (there are more related entries, however as a new user I can only post one URL):
Xpath Get elements that are between 2 elements

I have a question regarding the selection of set of elements that occur between 'other / delimiting' elements. This situation occurs when trying to transform a flat HTML table to
a hierarchic XML structure using XSLT. I tried using recursion in the templates, but saxon refused to accept this as it resulted in a dead-lock, most probably my fault, but let's start at the beginning.

First the source data is the HTML table:

<table >
    <thead>
        <tr>
            <th>Column 1</th>
            <th>Column 2</th>
            <th>Column 3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th colspan="3" >Group 1</th>
        </tr>
        <tr>
            <td>attribute 1.1.1</td>
            <td>attribute 1.1.3</td>
            <td>attribute 1.1.2</td>
        </tr>
        <tr>
            <td>attribute 1.2.1</td>
            <td>attribute 1.2.2</td>
            <td>attribute 1.2.3</td>
        </tr>
        <tr>
            <td>attribute 1.3.1</td>
            <td>attribute 1.3.2</td>
            <td>attribute 1.3.3</td>
        </tr>
        <tr>
            <th colspan="3" >Group 2</th>
        </tr>
        <tr>
            <td>attribute 2.1.1</td>
            <td>attribute 2.1.3</td>
            <td>attribute 2.1.2</td>
        </tr>
        <tr>
            <td>attribute 2.2.1</td>
            <td>attribute 2.2.2</td>
            <td>attribute 2.2.3</td>
        </tr>
        <tr>
            <td>attribute 2.3.1</td>
            <td>attribute 2.3.2</td>
            <td>attribute 2.3.3</td>
        </tr>
    </tbody>
</table>

The targeted output in XML would be:

 <groups>
    <group name="Group 1">
        <item attribute1="attribute 1.1.1" attribute2="attribute 1.1.3" attribute3="attribute 1.1.2"/>
        <item attribute1="attribute 1.2.1" attribute2="attribute 1.2.2" attribute3="attribute 1.2.3"/>
        <item attribute1="attribute 1.3.1" attribute2="attribute 1.3.2" attribute3="attribute 1.3.3"/>
    </group>
    <group name="Group 2">
        <item attribute1="attribute 2.1.1" attribute2="attribute 2.1.3" attribute3="attribute 2.1.2"/>
        <item attribute1="attribute 2.2.1" attribute2="attribute 2.2.2" attribute3="attribute 2.2.3"/>
        <item attribute1="attribute 2.3.1" attribute2="attribute 2.3.2" attribute3="attribute 2.3.3"/>
    </group>
</groups>

So I want to have all the item entries, (TR elements) and add them to a group. This basically comes down to select all following-sibling TR elements until we encounter one that has a TH element as a child. If I could only determine the position of this first TR that has a TH child, indicating a new heading for a group, this could be done with:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:for-each select="following-sibling::tr[position() < $positionOfNextThElement]">            
                <xsl:call-template name="item"/>
            </xsl:for-each>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

However, I am not able to determine the position of the first encountered TR/TH tag.

As stated I tried working with recursion in templates: always call the "item" template and in this template determine whether we want to invoke it on the next item as well. I think the problem is in the invocation of the template from within the template. The item in context does not increase? Should I hand over a parameter to determine what item we are working on?

Anyhow, this was what I came up with:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:call-template name="item"/>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

<xsl:template name="item">
    <xsl:element name="item">
        <xsl:attribute name="attribute1"><xsl:value-of select="following-sibling::tr[1]/td[1]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[2]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[3]"/></xsl:attribute>
    </xsl:element>
    <!-- When the next element has not got a TH tag, continue with invoking this template -->
    <xsl:if test="count(following-sibling::tr[1]/th) != 1">
        <xsl:call-template name="item"/>
    </xsl:if>
</xsl:template>

Any suggestions on how to realize this are welcome!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

擦肩而过的背影 2024-08-14 13:15:54

当您递归调用模板“item”时上下文不会增加的原因是 xs:call-template 始终将当前上下文项作为上下文传递。正如您可能看到的那样,变换只是进入无限递归。

假设您始终需要恰好生成三个属性,则甚至不需要递归。

试试这个:

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

    <xsl:template match="table">
        <groups>
            <xsl:apply-templates select="tbody/tr[th]"/>
        </groups>
    </xsl:template>

    <xsl:template match="tr[th]">
        <xsl:variable name="id" select="generate-id(.)"/>
        <group name="{string(th)}">
            <xsl:apply-templates
                select="following-sibling::tr[not(th)][generate-id(preceding-sibling::tr[th][1]) = $id]"/>
        </group>
    </xsl:template>

    <xsl:template match="tr">
        <item attribute1="{td[1]}" attribute2="{td[2]}" attribute3="{td[3]}" />                    
    </xsl:template>

</xsl:stylesheet>

这通过将模板应用到每个标题行来实现。这些模板中的每一个都使用复杂的 xpath 来调用“其”后续行,这些行是具有该特定行作为带有标题的第一个前行的任何后续同级行。

当然,如果属性的数量不同,那么您将需要在那里递归并增加传递一个指示位置的参数。

有几种用于 XSLT 分组的既定方法,其中一种是递归的,就像您所做的那样。另一种方法称为慕尼黑分组。 此处有一篇很好的文章。

The reason that context does not increase when you call the template "item" recursively is that xs:call-template always passes the current context item as context. So as you probably saw, the transform just enters infinite recursion.

Assuming that you always need to produce exactly three attributes, you don't even need recursion.

Try this:

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

    <xsl:template match="table">
        <groups>
            <xsl:apply-templates select="tbody/tr[th]"/>
        </groups>
    </xsl:template>

    <xsl:template match="tr[th]">
        <xsl:variable name="id" select="generate-id(.)"/>
        <group name="{string(th)}">
            <xsl:apply-templates
                select="following-sibling::tr[not(th)][generate-id(preceding-sibling::tr[th][1]) = $id]"/>
        </group>
    </xsl:template>

    <xsl:template match="tr">
        <item attribute1="{td[1]}" attribute2="{td[2]}" attribute3="{td[3]}" />                    
    </xsl:template>

</xsl:stylesheet>

This works by applying templates to each header row. Each of those template uses that complicated xpath to call "its" following rows, which are any following sibling rows that have that specific row as it's first preceding row with a header.

Of course, if the number of attributes vary, then you will need to recurse there and increase pass a parameter indicating the position.

There are a couple of established methods for XSLT grouping, one of which is recursive, like you were doing. Another method is called Muenchian grouping. A good write-up is here.

倥絔 2024-08-14 13:15:54

另一种解决方案,适用于变量属性计数,无需递归。

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

  <xsl:template match="table">
    <groups>
      <xsl:apply-templates select="tbody/tr[th]"/>
    </groups>
  </xsl:template>

  <xsl:template match="tr[th]">
    <group name="{th}">
      <xsl:apply-templates select="
        following-sibling::tr[not(th)][
          generate-id(preceding-sibling::tr[th][1]) = generate-id(current())
        ]
      "/>
    </group>
  </xsl:template>

  <xsl:template match="tr">
    <item>
     <xsl:apply-templates select="td" />
    </item>
  </xsl:template>

  <xsl:template match="td">
    <xsl:attribute name="attribute{position()}">
      <xsl:value-of select="." />
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

An alternative solution, fitted for variable attribute counts without recursion.

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

  <xsl:template match="table">
    <groups>
      <xsl:apply-templates select="tbody/tr[th]"/>
    </groups>
  </xsl:template>

  <xsl:template match="tr[th]">
    <group name="{th}">
      <xsl:apply-templates select="
        following-sibling::tr[not(th)][
          generate-id(preceding-sibling::tr[th][1]) = generate-id(current())
        ]
      "/>
    </group>
  </xsl:template>

  <xsl:template match="tr">
    <item>
     <xsl:apply-templates select="td" />
    </item>
  </xsl:template>

  <xsl:template match="td">
    <xsl:attribute name="attribute{position()}">
      <xsl:value-of select="." />
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文