Home > Software design >  XSL 1.0 Insert Multiple Attribute Values into a single one
XSL 1.0 Insert Multiple Attribute Values into a single one

Time:06-30

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>
  • Related