复杂的XSLT递归树转换
(注意:如果您在XSLT以外的函数式编程语言方面做得很好,那么这个问题可能仍然存在——对我来说,这并不一定是XSLT造成的。) 我将如何改变这一点:复杂的XSLT递归树转换,xslt,Xslt,(注意:如果您在XSLT以外的函数式编程语言方面做得很好,那么这个问题可能仍然存在——对我来说,这并不一定是XSLT造成的。) 我将如何改变这一点: <!-- note that empty cells are implicit and therefore nonexistent --> <tables> <table pos="1"> <row pos="1"> <cell pos="1">Category A
<!-- note that empty cells are implicit and therefore nonexistent -->
<tables>
<table pos="1">
<row pos="1">
<cell pos="1">Category A</cell>
<cell pos="10">Category B</cell>
</row>
<row pos="2">
<cell pos="1">Sub-Category 1</cell>
<cell pos="4">Sub-Category 2</cell>
<cell pos="6">Sub-Category 3</cell>
<cell pos="10">Sub-Category 1</cell>
</row>
</table>
<table pos="2">
<row pos="1">
<cell pos="1">Category A</cell>
<cell pos="11">Category B</cell>
</row>
<row pos="2">
<cell pos="1">Sub-Category 1</cell>
<cell pos="2">Sub-Category 2</cell>
<cell pos="4">Sub-Category 3</cell>
<cell pos="10">Sub-Category 4</cell>
<cell pos="11">Sub-Category 1</cell>
<cell pos="12">Sub-Category 2</cell>
</row>
</table>
</tables>
A类
B类
第1小类
次级类别2
次级类别3
第1小类
A类
B类
第1小类
次级类别2
次级类别3
次级类别4
第1小类
次级类别2
为此:
<tree>
<node label="Category A">
<positions>
<pos table="1" row="1" cell="1" />
<pos table="2" row="1" cell="1" />
</positions>
<node label="Sub-Category 1">
<positions>
<pos table="1" row="2" cell="1" />
<pos table="2" row="2" cell="1" />
</positions>
</node>
<node label="Sub-Category 2">
<positions>
<pos table="1" row="2" cell="4" />
<pos table="2" row="2" cell="2" />
</positions>
</node>
<node label="Sub-Category 3">
<positions>
<pos table="1" row="2" cell="6" />
<pos table="2" row="2" cell="4" />
</positions>
</node>
<node label="Sub-Category 4">
<positions>
<pos table="2" row="2" cell="10" />
</positions>
</node>
</node>
<node label="Category B">
<positions>
<pos table="1" row="1" cell="10" />
<pos table="2" row="1" cell="11" />
</positions>
<node label="Sub-Category 1">
<positions>
<pos table="1" row="2" cell="10" />
<pos table="2" row="2" cell="11" />
</positions>
</node>
<node label="Sub-Category 2">
<positions>
<pos table="2" row="2" cell="12" />
</positions>
</node>
</node>
</tree>
请注意,每个表表示任意深度的2D类别树。每一行表示前一行的子级,其中子类别通过其各自的位置附着到父级-如果它们的位置在左侧>=父级,在右侧<下一个父级(如果有),则它们是子级
我希望输出是一棵树,按标签分组(仅在描述的父子关系中,但通过表)。例如,“A类”和“B类”分别存在“子类别1”
有n个表,其中有n行,每个行有n个单元格。您可以将输入数据结构想象成一个3D立方体,每个表表示不同年份的大致相同数据
如果只是一个表(“年”),我可以做上面的事情,但是对于n个表,我很难找到一个应用这个东西的方法,因为其他方面平等的父母的位置不同
最后,我对一个无扩展功能的XSLT1.0解决方案感兴趣,尽管非常感谢任何(算法)帮助。这个问题困扰了我很长一段时间,我似乎无法控制它。我觉得必须有一个干净的解决办法,我就是看不出来。我相信这可以用一个递归模板、几个键和一些非常聪明的XPath来完成
不过,我想这个问题是风滚草徽章的材料。:-) 那么每一行中的单元格实际上是前一行中单元格的子单元格,其中子单元格的“pos”值>=父单元格的“pos”值,以及前一行上后续父单元格(如果有)的“pos”值 如果您下定决心要为此使用XSL,那么在下面的同级轴上。然而,这将是一个混乱的选择路径。可能更容易进行两种转换:一种是将平面数据组织成分层格式,另一种是生成最终输出
就我个人而言,我认为我应该编写一个显式处理DOM树的程序,特别是如果这是一次性操作的话。我怀疑它的写入速度也会一样快,而且在选择逻辑中不会出现奇怪的角落情况。这是我第一次尝试这样做。这看起来很容易,直到我遇到递归分组的麻烦。我考虑过使用节点集,但我认为这是XSL1.0的扩展,对吗?那么,没有为此设置节点?如果没有这一点,它会是这样的:
<?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>
按标签将儿童分组
查找孩子的所有位置
按标签将儿童分组
对
是否有先前的孩子具有相同的标签?