复杂的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

(注意:如果您在XSLT以外的函数式编程语言方面做得很好,那么这个问题可能仍然存在——对我来说,这并不一定是XSLT造成的。)

我将如何改变这一点:

<!-- 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的扩展,对吗?那么,没有为此设置节点?如果没有这一点,它会是这样的:

  • 找到所有根节点,以不同的方式处理它们(它们没有父节点,因此应用不同的规则)

  • 对于每个这样的节点,爬过每个//单元节点并测试它是否可能是当前节点的直接子节点。如果它可能是个孩子,我们还得把它分组,所以

  • 沿着前面的轴爬过每个//单元节点。对于每个前面的//单元节点,请查看它是否也是步骤2中父节点的直接子节点

  • 找到作为子节点的前//单元节点后,将前一个子节点的标签与步骤2中找到的子节点进行比较。如果它们相等,则不输出任何内容(因为此标签是在前面输出的)。否则

  • 开始输出子节点。再次爬过每个//单元节点,以找到此标签的所有位置,其中它是在步骤2中找到的父节点的子节点

  • 递归:返回步骤2,使用此子节点作为新的父节点

  • 在MSXML2上测试,看起来它正在生成您要求的内容。据我所知,即使我添加了更多的元素,它也应该可以工作。写这篇文章当然很有趣,它让我想到了在使用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 &gt;= $Lparent
                                and @pos &lt; $Rparent]"
                            mode="child" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates
                            select="$childRow/cell
                                [@pos &gt;= $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 &gt;= $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 &lt; $RparentPos">
            -->
    
            <xsl:if test="
                $parentCell
                and (@pos &gt;= $parentCell/@pos)
                and (not($RparentPos) or @pos &lt; $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>
    
    
    按标签将儿童分组
    查找孩子的所有位置
    按标签将儿童分组
    对
    是否有先前的孩子具有相同的标签?