Reputation: 3
I am very new to XSLT, I started reading about it yesterday and I'm stuck. I have the following XML
<?xml version="1.0" encoding="utf-8" ?>
<Menu>
<Function>
<Name>ViewHome</Name>
<Roles>
<Role>Finance</Role>
<Role>Advisor</Role>
<Role>Admin</Role>
</Roles>
</Function>
<Function>
<Name>ViewStaff</Name>
<ChildFunctions>
<Function>
<Name>StaffFunction1</Name>
<Roles>
<Role>Finance</Role>
</Roles>
</Function>
<Function>
<Name>StaffFunction2</Name>
<Roles>
<Role>Admin</Role>
</Roles>
</Function>
<Function>
<Name>StaffFunction3</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</ChildFunctions>
</Function>
<Function>
<Name>ViewDiary</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</Menu>
Which I would like transformed to show only those functions (parent or child) that contain a specified role or roles. So that if Finance and Advisor roles are applied the output would be like:
<?xml version="1.0" encoding="utf-8" ?>
<Menu>
<Function>
<Name>ViewHome</Name>
<Roles>
<Role>Finance</Role>
<Role>Advisor</Role>
</Roles>
</Function>
<Function>
<Name>ViewStaff</Name>
<ChildFunctions>
<Function>
<Name>StaffFunction1</Name>
<Roles>
<Role>Finance</Role>
</Roles>
</Function>
<Function>
<Name>StaffFunction3</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</ChildFunctions>
</Function>
<Function>
<Name>ViewDiary</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</Menu>
I have looked at other posts on this site, but can't generate the required output. The xsl I have been using is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:variable name="rolesList">Advisor Finance</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Function[not(descendant::Role[contains($rolesList, .)])]"/>
<!--
<xsl:template match="Function[not(descendant::Role='Finance')]"/>
-->
</xsl:stylesheet>
Ultimately the rolesList variable will be supplied through code. I just wanted to get the transform working. If I use the commented out template match for just 'Finance' role I get the expected results. Any help greatly appreciated.
Upvotes: 0
Views: 1855
Reputation: 1968
Use an internal lookup list:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="your-urn-here">
<my:rolesList>
<my:role>Advisor</my:role>
<my:role>Finance</my:role>
</my:rolesList>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Function[not(descendant::Role = document('')/*/my:rolesList/my:role)]"
/>
</xsl:stylesheet>
Upvotes: 1
Reputation: 116992
Which I would like transformed to show only those functions (parent or child) that contain a specified role or roles.
I believe your stylesheet does exactly that (although it shouldn't, and with some processors only - see the comments to the question for more details).
However it does not remove the "other" roles from the list of roles descendant to the functions that have passed the test. For that, you would need to add another template matching Role.
Ultimately the rolesList variable will be supplied through code
Ideally, the list would be supplied as a node-set (or something that can be turned into a node-set), so that you can make the comparison without resorting to string functions. In XSLT 2.0 there is more flexibility in this aspect, though.
Upvotes: 0
Reputation: 167516
With XSLT 1.0 using string comparison you could use
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:param name="roleList" select="'|Advisor|Finance|'"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Function">
<xsl:if test="descendant::Role[contains($roleList, concat('|', ., '|'))]">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
<xsl:template match="Role">
<xsl:if test="contains($roleList, concat('|', ., '|'))">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 22617
I suspect that contains() does not work properly if there is more than one Role
element, because the function does not expect lists as its second argument.
Note that you provide the list of roles as a whitespace-separated list. Yet, without a Schema it is not recognized as a list type. That is why you have to rely on string functions, as pointed out by @michael.hor257k.
This (i.e. template matches that contain variables) only works with XSLT 2.0, as pointed out by @Tim C.
Use the following stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="roles">Finance Advisor</xsl:variable>
<xsl:template match="Function[count(Roles/Role) = 1 and not(matches($roles,Roles/Role))]"/>
<xsl:template match="Role[not(matches($roles,.))]"/>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<Menu>
<Function>
<Name>ViewHome</Name>
<Roles>
<Role>Finance</Role>
<Role>Advisor</Role>
</Roles>
</Function>
<Function>
<Name>ViewStaff</Name>
<ChildFunctions>
<Function>
<Name>StaffFunction1</Name>
<Roles>
<Role>Finance</Role>
</Roles>
</Function>
<Function>
<Name>StaffFunction3</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</ChildFunctions>
</Function>
<Function>
<Name>ViewDiary</Name>
<Roles>
<Role>Advisor</Role>
</Roles>
</Function>
</Menu>
Upvotes: 0