so here's a basic example of a XML that i have:
<?xml version="1.0" encoding="UTF-8"?>
<Project>
<ProductPool>
<Product Type="A">
<PagePool>
<Page Number="1"/>
<Page Number="2"/>
</PagePool>
</Product>
<Product Type="B">
<PagePool>
<Page Number="1"/>
<Page Number="2"/>
</PagePool>
</Product>
</ProductPool>
</Project>
I wanted to have a for-each loop that reads the numbers attribute and adds each element specific page attributes into one so the desired result would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<Project>
<UnboundComponent PageSequence="1 2"/>
<BoundComponent PageSequence="1 2"/>
</Project>
Unfortunatly i always get "Number="1 2 1 2" in both Components.
Heres the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:element name="Project">
<xsl:for-each select="//Product">
<xsl:variable name="elem-name">
<xsl:choose>
<xsl:when test='@Type="B"'>BoundComponent</xsl:when>
<xsl:otherwise>UnboundComponent</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elem-name}">
<xsl:attribute name="PageSequence">
<xsl:for-each select="/Project/ProductPool/Product/PagePool/Page">
<xsl:value-of select="@Number"/>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
CodePudding user response:
You could add a predicate filter to test whether the @Number
value is equal to any of the preceding::Page/@Number
values, and exclude if it's a repeat:
<xsl:for-each select="/Project/ProductPool/Product/PagePool/Page/@Number[not(. = preceding::Page/@Number)]">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
With XSLT 2.0 (and greater), you could just select all of the @Number
and use distinct-values()
:
<xsl:attribute name="PageSequence" select="distinct-values(/Project/ProductPool/Product/PagePool/Page/@Number)"/>
CodePudding user response:
Your problem is in the select
attribute of your inner for-each
statement. At that point in the stylesheet, the context node is a Product
(because you are inside a <xsl:for-each select="//Product">
statement). But your inner for-each
statement's select
expression starts with a /
which means it starts searching from the root of the document, and that's why it finds all the Page
elements.
I have changed it, below, so that the inner for-each
statement's select
attribute contains an XPath that's evaluated relative to the Product
, i.e. just PagePool/Page
, which therefore returns only Page
elements that are children of PagePool
elements that are children of the current ("context") node.
I also moved the <xsl:text> </xsl:text>
to be inside the for-each
instead of after it, since I think you wanted to insert spaces between each of the numbers.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:element name="Project">
<xsl:for-each select="//Product">
<xsl:variable name="elem-name">
<xsl:choose>
<xsl:when test='@Type="B"'>BoundComponent</xsl:when>
<xsl:otherwise>UnboundComponent</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elem-name}">
<xsl:attribute name="PageSequence">
<xsl:for-each select="PagePool/Page">
<xsl:value-of select="@Number"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>