I have these two xmls
1> default.xml
<UI-defination>
<class >
<list_view >
<members>
<member col_span="1" name="code" displayName="Code"/>
<member col_span="1" name="creationTS" displayName="CreationTS"/>
<member col_span="1" name="creator" displayName="Creator"/>
<member col_span="1" name="displayName" displayName="DisplayName"/>
<member col_span="1" name="emailAddress" displayName="EmailAddress"/>
<member col_span="1" name="id" displayName="Id"/>
</members>
</list_view>
</class>
</UI-defination>
2>Rules.xml
<UI-defination>
<class name="Role">
<list_view multiselect="true">
<members>
<member name="displayName" sequence="3"/>
<member name="code" sequence="4"/>
</members>
<exclude>
<members>
<member name="id"/>
<member name="creator"/>
</members>
</exclude>
</list_view>
</class>
</UI-defination>
I want display list of member element according to following rules
- rules.xml/members/member should be excluded from list
- element should be get display according to sequence attribute in rules.xml
- Those element which not present in rules.xml/members/member should get display according to their natural order in default.xml.
Expected output is....
creationTS
emailAddress
displayName
code
Then the elements which has sequence attr, they should be at that
exact position. Then void position should get filled by the natural
order of remaining element.(i.e. by the elements which don't have
sequence attr., from default.xml)
IMHO, this is not a sorting problem, but a queuing problem.
Let us simplify the example by having members of two types: placed members, whose place in the queue is reserved, and ordinary members that need to fill the gaps between the placed members.
<members>
<member id="1" place="9"/>
<member id="2" place="2"/>
<member id="3" place="5"/>
<member id="4"/>
<member id="5"/>
<member id="6"/>
<member id="7"/>
<member id="8"/>
<member id="9"/>
<member id="10"/>
<member id="11"/>
<member id="12"/>
</members>
The queuing algorithm could be described as follows:
The reserved members are sorted by their reserved place.
Each reserved member "calls" a group of ordinary members to be seated ahead of it.
In our example, we have reserved members at places 2, 5 and 9. Before
seating the reserved member at place 9, we need to call a group of 3
ordinary members to be seated at places 6, 7 and 8.
The size of this group is equal to the size of the gap between the
current member's reserved place and the place of its preceding
reserved sibling. That's 9 - 5 - 1 = 3 in our example.
The starting position of this group can be deduced as follows: since
the preceding member's reserved place is #5, then 5 members have
already been seated; out of these, 2 members (at places #2 and #5)
are reserved members, therefore only 3 ordinary members have been
seated and our group starts at position 5 - 3 + 1 = 4.
The last reserved member calls the rest of the ordinary members to be seated after it.
Here's an example of implementation in XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="reserved-members">
<xsl:for-each select="/members/member[@place]">
<xsl:sort select="@place" data-type="number" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="ordinary-members" select="/members/member[not(@place)]"/>
<xsl:template match="/">
<output>
<xsl:for-each select="exsl:node-set($reserved-members)/member">
<xsl:variable name="previous-place">
<xsl:choose>
<xsl:when test="position()>1">
<xsl:value-of select="preceding-sibling::member[1]/@place"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="gap-size" select="@place - $previous-place - 1"/>
<xsl:variable name="gap-start" select="$previous-place - count(preceding-sibling::member) + 1"/>
<!-- fill the gap with ordinary members -->
<xsl:for-each select="$ordinary-members[$gap-start <= position() and position() < $gap-start + $gap-size]">
<xsl:copy-of select="."/>
</xsl:for-each>
<!-- output the reserved member -->
<member id="{@id}" place="{@place}" start="{$gap-start}" size="{$gap-size}"/>
<!-- output remaining ordinary members -->
<xsl:if test="position()=last()">
<xsl:for-each select="$ordinary-members[position() >= $gap-start + $gap-size]">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
When applied to the above input example, the result is:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<member id="4"/>
<member id="2" place="2" start="1" size="1"/>
<member id="5"/>
<member id="6"/>
<member id="3" place="5" start="2" size="2"/>
<member id="7"/>
<member id="8"/>
<member id="9"/>
<member id="1" place="9" start="4" size="3"/>
<member id="10"/>
<member id="11"/>
<member id="12"/>
</output>
With the same remarks applying as in @Martin Honnen's answer my answer also works for XSLT 1.0 (for whatever it's worth):
<?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" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:variable name="excludes" select="document('Rules.xml')//exclude/members"/>
<xsl:variable name="sequences" select="document('Rules.xml')//list_view/members"/>
<xsl:template match="/">
<members>
<!-- output all member with an explicit sequence -->
<xsl:for-each select="//member">
<xsl:sort select="$sequences/member[@name=current()/@name]/@sequence"/>
<xsl:if test="$sequences/member[@name=current()/@name]/@sequence and not($excludes/member[@name=current()/@name])">
<member><xsl:value-of select="@name"/></member>
</xsl:if>
</xsl:for-each>
<!-- output all member without an explicit sequence -->
<xsl:for-each select="//member">
<xsl:if test="not($sequences/member[@name=current()/@name]/@sequence) and not($excludes/member[@name=current()/@name])">
<member><xsl:value-of select="@name"/></member>
</xsl:if>
</xsl:for-each>
</members>
</xsl:template>
</xsl:stylesheet>
yielding the result
<?xml version="1.0" encoding="UTF-8"?>
<members>
<member>displayName</member>
<member>code</member>
<member>creationTS</member>
<member>emailAddress</member>
</members>
The following assumes you simply want to output the member
elements, it uses a recursive function to construct the right sequence from the original elements, either using an element for which a position is defined in the rules.xml or filling the position with the next remaining element:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs mf"
xmlns:mf="http://example.com/mf">
<xsl:param name="rules-uri" select="'Rules.xml'"/>
<xsl:variable name="rules-doc" select="doc($rules-uri)"/>
<xsl:variable name="main-input" select="/"/>
<xsl:output indent="yes"/>
<xsl:key name="member" match="list_view/members/member" use="@name"/>
<xsl:key name="pos" match="list_view/members/member" use="xs:integer(@sequence)"/>
<xsl:variable name="exclude" select="key('member', $rules-doc//exclude/members/member/@name)"/>
<xsl:variable name="process" select="//list_view/members/member except $exclude"/>
<xsl:variable name="no-key" select="$process[not(key('member', @name, $rules-doc))]"/>
<xsl:function name="mf:fill" as="element(member)*">
<xsl:param name="pos" as="xs:integer"/>
<xsl:param name="length" as="xs:integer"/>
<xsl:param name="no-key" as="element(member)*"/>
<xsl:sequence select="if ($pos gt $length)
then ()
else
(if (key('pos', $pos, $rules-doc))
then (key('member', key('pos', $pos, $rules-doc)/@name, $main-input),
mf:fill($pos + 1, $length, $no-key))
else ($no-key[1], mf:fill($pos + 1, $length, $no-key[position() gt 1])))"/>
</xsl:function>
<xsl:template match="/">
<xsl:sequence select="mf:fill(1, count($process), $no-key)"/>
</xsl:template>
</xsl:stylesheet>
With the input being
<UI-defination>
<class >
<list_view >
<members>
<member col_span="1" name="code" displayName="Code"/>
<member col_span="1" name="creationTS" displayName="CreationTS"/>
<member col_span="1" name="creator" displayName="Creator"/>
<member col_span="1" name="displayName" displayName="DisplayName"/>
<member col_span="1" name="emailAddress" displayName="EmailAddress"/>
<member col_span="1" name="id" displayName="Id"/>
</members>
</list_view>
</class>
</UI-defination>
and the rules defining
<UI-defination>
<class name="Role">
<list_view multiselect="true">
<members>
<member name="displayName" sequence="2"/>
<member name="code" sequence="4"/>
</members>
<exclude>
<members>
<member name="id"/>
<member name="creator"/>
</members>
</exclude>
</list_view>
</class>
</UI-defination>
I get the result
<member col_span="1" name="creationTS" displayName="CreationTS"/>
<member col_span="1" name="displayName" displayName="DisplayName"/>
<member col_span="1" name="emailAddress" displayName="EmailAddress"/>
<member col_span="1" name="code" displayName="Code"/>
Here is an adoption of the XSLT I posted to the XML input Michael posted:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs mf"
xmlns:mf="http://example.com/mf">
<!--
<xsl:param name="rules-uri" select="'test2014062704.xml'"/>
<xsl:variable name="rules-doc" select="doc($rules-uri)"/>
-->
<xsl:variable name="main-input" select="/"/>
<xsl:output indent="yes"/>
<xsl:key name="member" match="members/member" use="@id"/>
<xsl:key name="pos" match="members/member" use="xs:integer(@place)"/>
<xsl:variable name="process" select="members/member"/>
<xsl:variable name="no-key" select="members/member[not(@place)]"/>
<xsl:function name="mf:fill" as="element(member)*">
<xsl:param name="pos" as="xs:integer"/>
<xsl:param name="length" as="xs:integer"/>
<xsl:param name="no-key" as="element(member)*"/>
<xsl:sequence select="if ($pos gt $length)
then ()
else
(if (key('pos', $pos, $main-input))
then (key('pos', $pos, $main-input),
mf:fill($pos + 1, $length, $no-key))
else ($no-key[1], mf:fill($pos + 1, $length, $no-key[position() gt 1])))"/>
</xsl:function>
<xsl:template match="/">
<xsl:sequence select="mf:fill(1, count($process), $no-key)"/>
</xsl:template>
</xsl:stylesheet>
Then with Saxon 9.5 on the input
<members>
<member id="1" place="9"/>
<member id="2" place="2"/>
<member id="3" place="5"/>
<member id="4"/>
<member id="5"/>
<member id="6"/>
<member id="7"/>
<member id="8"/>
<member id="9"/>
<member id="10"/>
<member id="11"/>
<member id="12"/>
</members>
I get the result
<member id="4"/>
<member id="2" place="2"/>
<member id="5"/>
<member id="6"/>
<member id="3" place="5"/>
<member id="7"/>
<member id="8"/>
<member id="9"/>
<member id="1" place="9"/>
<member id="10"/>
<member id="11"/>
<member id="12"/>
which is the same order it appears as the one produced by Michael's XSLT 1.0 code.