0

Trying to assembly correct XSLT-code to assign common id which is taken from the lowest member (from its "uniq-id") of the recursive chain to all elements above. Can't get ancestors' nodes.

source xml

<root>
  <Object id="top"                        uniq-id="001" status="0" rank="1"/>
  <Object id="middle"  id-parent="top"    uniq-id="020" status="0" rank="3"/>
  <Object id="middle"  id-parent="top"    uniq-id="021" status="1" rank="3"/>

  <!--   only the element with status="0" is considered -->

  <Object id="bottom-1"  id-parent="middle" uniq-id="111" status="1" rank="6" />
  <Object id="bottom-2"  id-parent="middle" uniq-id="222" status="1" rank="6" />
  <Object id="bottom-3"  id-parent="middle" uniq-id="333" status="0" rank="6" />  <!-- will be taken -->
  <Object id="bottom-4"  id-parent="middle" uniq-id="444" status="1" rank="6" />
  <Object id="bottom-5"  id-parent="middle" uniq-id="555" status="1" rank="7" />
  <Object id="bottom-6"  id-parent="middle" uniq-id="666" status="0" rank="7" />  <!-- will be taken -->
  <Object id="bottom-7"  id-parent="middle" uniq-id="777" status="1" rank="6" />
  <Object id="bottom-8"  id-parent="middle" uniq-id="888" status="1" rank="6" />
  <Object id="bottom-9"  id-parent="middle" uniq-id="999" status="0" rank="6" />  <!-- will be taken -->
</root>

starting-xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:exslt="http://exslt.org/common">
  <xsl:key name="Object-By-id"        match="Object[(@status='0')]"  use="@id"/>
  <xsl:key name="Object-By-id-parent" match="Object[(@status='0')]"  use="string(@id-parent)"/>

  <xsl:variable name="fold-rtf">
    <xsl:apply-templates select="/" mode="fold"/>
  </xsl:variable>
  <xsl:variable name="folded-tree" select="exslt:node-set($fold-rtf)"/>

  <xsl:template match="@*|node()[(@status='0')]">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()[(@status='0')]"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Object[@status=0]/@*[last()]">
    <xsl:variable name="current" select=".."/>
    <xsl:copy/>
    <xsl:for-each select="$folded-tree">
      <xsl:for-each select="key('Object-By-id',$current/@id)">

        <!-- ====== !!  =======-->

        <xsl:if test="position()!=last()">
          <xsl:for-each select="ancestor-or-self::*">
            <xsl:attribute name="chain-id">
              <xsl:value-of select="@uniq-id"/>
            </xsl:attribute>
          </xsl:for-each>
        </xsl:if>

        <!-- ======= !! =======-->

      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="/|*" mode="fold">
    <xsl:copy>
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates select="key('Object-By-id-parent',string(@id))" mode="fold">
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

the result (not correct) https://xsltfiddle.liberty-development.net/94Acsm2/1

assumed output

<root>
  <Object id="top"                          uniq-id="001" status="0" rank="1" chain-id="20"/>
  <Object id="middle"    id-parent="top"    uniq-id="020" status="0" rank="3" chain-id="20"/>

  <Object id="bottom-3"  id-parent="middle" uniq-id="333" status="0" rank="6" chain-id="333"/>
  <Object id="middle"    id-parent="top"    uniq-id="020" status="0" rank="3" chain-id="333"/>
  <Object id="top"                          uniq-id="001" status="0" rank="1" chain-id="333"/>

  <Object id="bottom-6"  id-parent="middle" uniq-id="666" status="0" rank="7" chain-id="666"/>
  <Object id="middle"    id-parent="top"    uniq-id="020" status="0" rank="3" chain-id="666"/>
  <Object id="top"                          uniq-id="001" status="0" rank="1" chain-id="666"/>

  <Object id="bottom-9"  id-parent="middle" uniq-id="999" status="0" rank="6" chain-id="999"/>
  <Object id="middle"    id-parent="top"    uniq-id="020" status="0" rank="3" chain-id="999"/>
  <Object id="top"                          uniq-id="001" status="0" rank="1" chain-id="999"/>
</root>

Welcome all your solutions how to improve XSLT-code

Alex
  • 49
  • 1
  • 5
  • I have to admit that I have read your description multiple times, and looked at the input and the assumed output xml and I don't understand what you are trying to do, I mean I understand that you are trying to get the assumed output from the input but I don't understand the logic for how the elements in the assumed output are related to each other. Hopefully you can clarify the question, on the off chance that others have the same problem. – user254694 Jan 31 '20 at 10:47
  • 1
    `/@*[last()]` doesn't make any sense. You're asking for the last attribute, but the order of attributes is completely unpredictable. – Michael Kay Jan 31 '20 at 11:48
  • to user254694 - this description may be similar to the previous ones, but the question is quite specific and new. It just also relies on the source XML, which is recursion (yes, the presence of recursion is what unites all my recent questions. The thing is not the easiest, so I have to tinker with it). Btw, among of all i thought - this one is quite clear about main condition\question ('Get a common id to all ancestors from the lowest member'). Rephrase in other words? I'll try. – Alex Jan 31 '20 at 12:08
  • All previous requests, in general, dealt with one direction - to untangle the whole chain of interconnected elements, restore them and somehow explicit them readable in human logic and perception. – Alex Jan 31 '20 at 12:08
  • if we continue to describe in human logic (space and time) - current issue actually a continuation from the place where the previous recursion algorithm remained. 1- step - we find the "end" of the recursive chain. 2 - Now you need to "run through" this chain back, now knowing the uniq-id value of the "lower" element - assign its value to all elements in the reverse order (from bottom to top) (at least for now I think the abstract logic of a possible algorithm is kind of that. Tactical problem to know how represent this abtraction in specific xslt-code. – Alex Jan 31 '20 at 12:08

1 Answers1

1

It's always difficult to reverse engineer your requirements from non-working code, but I think what you're trying to do is to select elements with @status="0", and for each of these, display the logical ancestors, that is, the recursive expansion of the relation parent(X), where parent(X) selects the element whose @id is equal to X/@parent-id.

It's a long time since I coded in XSLT 1.0 (it's been superseded for a long time now...) so apologies if I make any errors.

First define a key that selects objects by id:

<xsl:key name="by-id" match="Object" use="@id"/>

Now define a recursive template to print the logical ancestors of a node:

<xsl:template match="Object" mode="ancestors">
  <xsl:copy-of select="."/>
  <xsl:apply-templates select="key('by-id', @parent-id)" mode="ancestors"/>
</xsl:template>

and now invoke this for the selected items:

<xsl:template match="/">
 <root>
  <xsl:apply-templates select="root/Object[@status='0']" mode="ancestors"/>
 </root>
</xsl:template>

Not tested.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Thank you Michael! i'll try to implement it as soon as. – Alex Jan 31 '20 at 12:12
  • Decided to make a new assembly relying only on your code. The algorithm really determines the "lowest" element of the chain, based on status = "0", but does not go further (what, in fact, is the real MacGuffin of this episode). https://xsltfiddle.liberty-development.net/pPJ9hEf – Alex Feb 01 '20 at 14:30
  • The main thing that I am trying to achieve this time is not so much to explicate the assembled chain in the direction from top to bottom, but to re-assemble now in the reverse order, in which each parent element a) duplicated b) (main option!) - get a new common attribute "chain-id", the value of which would be taken from the "uniq-id" of the "lower element" (here they are represented by "333", "666", "999" values) interconnecting scheme (basic) https://imgur.com/p868Xqc interconnecting output scheme https://imgur.com/PRDYvyo – Alex Feb 01 '20 at 14:30
  • From a little experience using XSLT, I can assume that the solution lies next to the use of XSLT-lexems such as 'position' and 'last'. Roughly speaking, reverse rebuilding and its explication begins to occur when the 'position' of Object element is 'last'. – Alex Feb 01 '20 at 14:30
  • I looked at sources like Novachev's method https://stackoverflow.com/questions/58101443/xpath-recursive-parent-selection-in-a-flat-structure/58174330#58174330 but while this knowledge gives so many lego bricks that even understanding the formal logic of what should happen, I can’t figure out how to bring it to working code. – Alex Feb 01 '20 at 14:31
  • briefly,maybe most readable explanation what should happen https://imgur.com/PRDYvyo – Alex Feb 01 '20 at 14:38
  • @Alex, sorry, I belong to the wrong generation, I have no idea what a MacGuffin is. And I now know that I don't understand your requirements, whereas I previously thought I understood them. – Michael Kay Feb 01 '20 at 16:53