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
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.
Thank you Michael! i'll try to implement it as soon as.
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). xsltfiddle.liberty-development.net/pPJ9hEf
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) imgur.com/p868Xqc interconnecting output scheme imgur.com/PRDYvyo
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'.
I looked at sources like Novachev's method stackoverflow.com/questions/58101443/… 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.