这是我第一次尝试这个。这看起来很简单,直到我遇到了递归分组的麻烦。我考虑过使用节点集,但我认为这是对xsl 1.0的扩展,对吧?所以,没有为这个设置节点?如果没有这个,它会像这样:
-
找到所有根节点,以不同的方式处理它们(它们没有父节点,因此适用不同的规则)。
-
对于每个这样的节点,遍历每个//cell节点并测试它是否可能是当前节点的直接子节点。如果是个孩子,我们还得把它分组,所以…
-
沿着前面的轴爬行穿过每个/细胞节点。对于前面的每个//cell节点,查看它是否也可能是在步骤2中找到的父节点的直接子节点。
-
在找到前面的//cell节点(它是子节点)时,将前面子节点的标签与步骤2中找到的子节点进行比较。如果它们相等,则不输出任何内容(因为此标签是以前输出的)。否则…
-
开始输出子节点。再次在每个//cell节点中爬行,以定位该标签的所有位置,其中该标签是在步骤2中找到的父节点的子节点。
-
递归:返回步骤2,使用这个子节点作为新的父节点。
在msxml2上测试,看起来它正在生成您要求的内容。它似乎可以工作,因为我知道,即使我再添加一些<row>元素,它也应该可以工作。写起来确实很有趣,它让我思考了一些在使用xsl时通常不会考虑的方向。我想那很好…?再说一遍,也许这是一个可以用xsl解决的问题的例子,但是可以用其他方法更容易地解决。
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="roots" match="/tables/table/row[1]/cell" use="text()" />
<xsl:key name="cell-by-label" match="/tables/table/row/cell" use="text()"/>
<xsl:variable name="cells" select="/tables/table/row/cell" />
<xsl:template match="/tables">
<xsl:variable name="rootCells" select="/tables/table/row[1]/cell"/>
<tree>
<!-- Work on each root-level nodes first. -->
<xsl:apply-templates
mode="root"
select="$rootCells[count(.|key('roots', text())[1]) = 1]" />
</tree>
</xsl:template>
<!--
Root level cells are handled differently. They have no available $parent,
for one thing. Because of that, it seems simpler to handle them as an
exception here, rather than peppering the mode="crawl" with exceptions
for parentless nodes.
-->
<xsl:template match="cell" mode="root">
<node label="{.}">
<!--
Get a list of everywhere that this cell is found.
We are looking for only other root-level cells here.
-->
<positions>
<xsl:for-each select="key('roots', text())">
<pos table="{../../@pos}" row="{../@pos}" cell="{@pos}"/>
</xsl:for-each>
</positions>
<!--
Locate all child nodes, across all tables in which this node is found.
A node is a child node if:
1. It is in a row directly following the row in which this cell is found.
2. If the @pos is >= the @pos of the parent
3. If the @pos is < the @pos of the parent to the right (if there is a parent to the right)
Note: Meeting the above conditions is not difficult; it's grouping at this
point that gives trouble. If the problem permitted extension functions
to XSL 1.0, then perhaps we could generate a node-set and group that.
I've not tried that way, since I'm under the impression that a node-set
would be an extension to 1.0, and therefore, "cheating."
However, if we could generate a node-set and group based on that,
then the following block selects the correct nodes (I think):
<xsl:for-each select="$matches">
<xsl:variable name="childRow" select="../following-sibling::row[1]"/>
<xsl:variable name="Lparent" select="@pos"/>
<xsl:variable name="Rparent" select="following-sibling::cell[1]/@pos"/>
<xsl:choose>
<xsl:when test="$Rparent">
<xsl:apply-templates
select="$childRow/cell[
@pos >= $Lparent
and @pos < $Rparent]"
mode="child" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates
select="$childRow/cell
[@pos >= $Lparent]"
mode="child"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
Without using a node-set, I'll try to solve this by crawling over every
table/row/cell and test each one, every time. Not pretty by a long shot.
But hey, memory and processors are getting cheaper every day.
-->
<xsl:apply-templates select="$cells" mode="crawl">
<xsl:with-param name="parent" select="."/>
<xsl:with-param name="task">Group children by their labels</xsl:with-param>
</xsl:apply-templates>
</node>
</xsl:template>
<xsl:template match="cell" mode="child">
<xsl:param name="parent"/>
<node label="{.}">
<positions>
<xsl:apply-templates mode="crawl"
select="key('cell-by-label', text())">
<xsl:with-param name="parent" select="$parent"/>
<xsl:with-param name="child" select="."/>
<xsl:with-param name="task">Find all positions of a child</xsl:with-param>
</xsl:apply-templates>
</positions>
<!-- And.... Recursion Start Now! -->
<xsl:apply-templates select="$cells" mode="crawl">
<xsl:with-param name="parent" select="."/>
<xsl:with-param name="task">Group children by their labels</xsl:with-param>
</xsl:apply-templates>
</node>
</xsl:template>
<xsl:template match="cell" mode="crawl">
<xsl:param name="parent"/>
<xsl:param name="child"/>
<xsl:param name="task"/>
<xsl:variable name="parentRow"
select="generate-id(../preceding-sibling::row[1])"/>
<xsl:variable name="parentCell"
select="key('cell-by-label', $parent/text())
[$parentRow = generate-id(..)]" />
<xsl:variable name="RparentPos"
select="$parentCell/following-sibling::cell[1]/@pos"/>
<!--
This cell is a child if it is in a row directly following a row
in which the parent cell's text value made an appearance.
<xsl:if test="$parentCell">
This cell is a child if it's @pos is >= the parent cell's pos
<xsl:if test="@pos >= $parentCell/@pos">
If there is a parent cell to the right of this cell's parent cell,
this this cell is a child only if its @pos is < the right-parent
cell's @pos.
<xsl:if test="not($RparentPos) or @pos < $RparentPos">
-->
<xsl:if test="
$parentCell
and (@pos >= $parentCell/@pos)
and (not($RparentPos) or @pos < $RparentPos)">
<xsl:choose>
<!--
If our task is to determine whether there are any nodes prior to
the given child node in the document order which are also
children of the parent and which have the same label value, we do
that now. All we really want is to make a mark. We will later use
string-length to see if we made any marks here or not.
-->
<xsl:when test="$task = 'Are there prior children with equal labels?'">
Yes
</xsl:when>
<!--
Here, our task is to generate the <pos> nodes of the children.
-->
<xsl:when test="$task = 'Find all positions of a child'">
<pos table="{../../@pos}" row="{../@pos}" cell="{@pos}"/>
</xsl:when>
<!--
If our task is to group children by their labels, we need to know
if this is the first child node with this particular label. To do
that, we crawl over all cells along the preceding axis, and if
they are otherwise potential children (see above block), then a
mark is made (perhaps several such marks, doesn't matter how many,
really). If we have any marks when we are done, we know we have
output this label before, so we don't do it again.
-->
<xsl:when test="$task = 'Group children by their labels'">
<xsl:variable name="priorMatches">
<xsl:apply-templates mode="crawl"
select="preceding::cell[text() = current()/text()]">
<xsl:with-param name="parent" select="$parent"/>
<xsl:with-param name="task">Are there prior children with equal labels?</xsl:with-param>
</xsl:apply-templates>
</xsl:variable>
<xsl:if test="string-length($priorMatches) = 0">
<xsl:apply-templates select="." mode="child">
<xsl:with-param name="parent" select="$parent"/>
</xsl:apply-templates>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
编辑1:稍微重新格式化。添加了使用几个xsl:key元素来帮助按标签查找子节点。做了一些其他的优化——希望不会降低可读性。
想法/评论?