使用XSLT 1.0包装段落中的文本和元素

时间:2016-09-06 08:22:43

标签: html xml xslt xslt-1.0

我有一个问题,我已经使用XSLT 2.0解决了,但现在我需要能够在XSLT 1.0中做同样的事情(因为使用XSLT 1.0兼容处理器的约束)。

事实上,我需要不同类型的XHTML和XML,以及可能略有不同的场景,但为了简单起见,我将在XHTML中给出一个示例,以找到对此的一般解决方案:

假设我有一个像这样的HTML表:

<table frame="void">
        <col width="50%" />
        <col width="50%" />
        <thead>
            <tr>
                <th></th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>text text <b>text</b> text <i>text</i> text</td>
                <td>text text <b>text</b> text <i>text</i> text<img src="mypic.png" alt="mypic"
                     />text <b>text</b> text</td>
            </tr>
            <tr>
                <td>
                    <table frame="void">
                        <col width="50%" />
                        <col width="50%" />
                        <thead>
                            <tr>
                                <th></th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td></td>
                                <td></td>
                            </tr>
                            <tr>
                                <td></td>
                                <td></td>
                            </tr>
                        </tbody>
                    </table>
                </td>
                <td><img src="mypic.png" alt="mypic" /></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>

我现在想要的是用<p>标签包装表格单元格中的所有文本和内联元素,以获得:

<table frame="void">
        <col width="50%" />
        <col width="50%" />
        <thead>
            <tr>
                <th></th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    <p>text text <b>text</b> text <i>text</i> text</p>
                </td>
                <td><p>text text <b>text</b> text <i>text</i> text</p><img src="mypic.png"
                        alt="mypic" /><p>text <b>text</b> text</p></td>
            </tr>
            <tr>
                <td>
                    <table frame="void">
                        <col width="50%" />
                        <col width="50%" />
                        <thead>
                            <tr>
                                <th></th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td></td>
                                <td></td>
                            </tr>
                            <tr>
                                <td></td>
                                <td></td>
                            </tr>
                        </tbody>
                    </table>
                </td>
                <td><img src="mypic.png" alt="mypic" /></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>

请注意,其他几个单元格都有图像和嵌套表格。如果这些单元格中只有一个图像或一个表格(没有文本或内联元素),则不应将它们包装在p标签中。

另请注意,其中一个图像具有周围文本和内嵌元素。在这种情况下,图像之前的文本和内联应该包装在图像之前的p标签中(或者表格或任何非内联元素),并且图像之后的文本/内联应该包装在另一个p标签中。 (在这个用例中,img被认为是非内联元素btw)。

在XSLT 2.0中执行此操作时,我使用此模板来处理此问题,从表格单元格的模板调用它而不是仅应用子模板:

<xsl:template name="wrapInPara">
    <xsl:apply-templates select="@class"></xsl:apply-templates>
    <xsl:for-each-group select="node()[not(self::text() and normalize-space(.) = '')]"            
        group-adjacent="boolean(self::text() | self::e:b | self::e:i | self::e:em | self::e:strong | self::e:a | self::e:u | self::e:span)">
        <xsl:choose>
            <xsl:when test="current-grouping-key()">
                <p>                        
                    <xsl:apply-templates select="current-group()"/>
                </p>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="current-group()"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each-group>
</xsl:template>

并称之为:

<xsl:template match="td">
    <xsl:copy>
        <xsl:call-template name="wrapInPara"/>
    </xsl:copy>
</xsl:template>

(正如您所看到的,需要考虑的不仅仅是<b><i>标签,而且它可能是除了应该从包装中排除的嵌套表格或图像之外的其他标签,所以我希望能够在可能的情况下为类似的用例修改答案。

我一直试图弄清楚如何在XSLT 1.0中做这样的事情,看看Muenchian分组方法,类似的问题似乎表明,但我不能让它起作用。

任何帮助都非常感谢!

1 个答案:

答案 0 :(得分:2)

这是你可以做到的一种方式。实际上,它将“para”元素分组为不是“para”元素的第一个前一个兄弟。然后wrapInPara节点选择非“para”元素,并在p标记中包装任何后续“para”元素(使用键)。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*" />

    <xsl:key name="para" 
             match="text()|b|i|em|strong|a|u|span" 
             use="concat(generate-id(..), '|', generate-id(preceding-sibling::node()[not((self::text()|self::b|self::i|self::em|self::strong|self::a|self::u|self::span))][1]))" />

    <xsl:template name="wrapInPara">
        <xsl:apply-templates select="@class" />
        <!-- Handle `para` elements that have no preceding non-para nodes -->
        <xsl:call-template name="groupInPara">
            <xsl:with-param name="group" select="key('para', concat(generate-id(), '|'))" />
        </xsl:call-template>
        <xsl:for-each select="node()[not((self::text()|self::b|self::i|self::em|self::strong|self::a|self::u|self::span))]">
            <xsl:apply-templates select="." />
            <!-- Wrap any following `para` elements -->
            <xsl:call-template name="groupInPara">
                <xsl:with-param name="group" select="key('para', concat(generate-id(..), '|', generate-id()))" />
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="groupInPara">
        <xsl:param name="group" />
        <xsl:if test="$group">
            <p>
                <xsl:apply-templates select="$group" />
            </p>
        </xsl:if>
    </xsl:template>

    <xsl:template match="td">
        <xsl:copy>
            <xsl:call-template name="wrapInPara"/>
        </xsl:copy>
    </xsl:template>

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