1

I am trying to use Apache Xalan to group a list of product variants by their productId's. Here is an input sample:

***input_1.xml***
<?xml version="1.0" encoding="utf-8"?>
<root>
    <variant>
        <productId>1</productId>
        <price>100</price>
        <stock unit="item">10</stock>
        <attributes>
            <attribute name="color" value="red" />
        </attributes>
    </variant>
    <variant>
        <productId>1</productId>
        <price>100</price>
        <stock unit="item">8</stock>
        <attributes>
            <attribute name="color" value="blue" />
        </attributes>
    </variant>
    <variant>
        <productId>1</productId>
        <price>150</price>
        <stock unit="item">12</stock>
        <attributes>
            <attribute name="color" value="green" />
        </attributes>
    </variant>
    <variant>
        <productId>2</productId>
        <price>200</price>
        <stock unit="item">7</stock>
        <attributes>
            <attribute name="color" value="purple" />
            <attribute name="material" value="faux-leather" />
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>190</price>
        <stock unit="item">22</stock>
        <attributes>
            <attribute name="color" value="yellow" />
            <attribute name="size" value="XL" />
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>180</price>
        <stock unit="item">13</stock>
        <attributes>
            <attribute name="color" value="yellow" />
            <attribute name="size" value="L" />
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>170</price>
        <stock unit="item">5</stock>
        <attributes>
            <attribute name="color" value="yellow" />
            <attribute name="size" value="M" />
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>170</price>
        <stock unit="item">7</stock>
        <attributes>
            <attribute name="color" value="yellow" />
            <attribute name="size" value="S" />
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>180</price>
        <stock unit="item">12</stock>
        <attributes>
            <attribute name="color" value="yellow" />
            <attribute name="size" value="XS" />
        </attributes>
    </variant>
</root>

I then use the following command from the shell:

xalan -in input_1.xml -xsl muenchian_1.xsl -out output_1.xml -indent 4

To transform the input with the following stylesheet:

***muenchian_1.xml***
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   >
    <xsl:strip-space elements="*" />
    <xsl:output method="xml" indent="yes"/>
    <xsl:key name="variants-by-productId" match="/root/variant" use="productId"/>
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/root/variant[productId][generate-id() =
         generate-id(key('variants-by-productId', productId)[1])]" priority="1">
        <product-listing-group>
            <productId>
                <xsl:value-of select="productId"/>
            </productId>
            <xsl:for-each select="key('variants-by-productId', productId)">
                <xsl:call-template name="identity" />
            </xsl:for-each>
        </product-listing-group>
    </xsl:template>
</xsl:stylesheet>

Expecting to get the following output:

***expected_1.xml***
<?xml version="1.0" encoding="utf-8"?>
<root>
    <product>
        <productId>1</productId>
        <variant>
            <price>100</price>
            <stock unit="item">10</stock>
            <attributes>
                <attribute name="color" value="red" />
            </attributes>
        </variant>
        <variant>
            <price>100</price>
            <stock unit="item">8</stock>
            <attributes>
                <attribute name="color" value="blue" />
            </attributes>
        </variant>
        <variant>
            <price>150</price>
            <stock unit="item">12</stock>
            <attributes>
                <attribute name="color" value="green" />
            </attributes>
        </variant>
    </product>
    <product>
        <productId>2</productId>
        <variant>
            <price>200</price>
            <stock unit="item">7</stock>
            <attributes>
                <attribute name="color" value="purple" />
                <attribute name="material" value="faux-leather" />
            </attributes>
        </variant>
    </product>
    <product>
        <productId>3</productId>
        <variant>
            <price>190</price>
            <stock unit="item">22</stock>
            <attributes>
                <attribute name="color" value="yellow" />
                <attribute name="size" value="XL" />
            </attributes>
        </variant>
        <variant>
            <price>180</price>
            <stock unit="item">13</stock>
            <attributes>
                <attribute name="color" value="L" />
            </attributes>
        </variant>
        <variant>
            <price>170</price>
            <stock unit="item">5</stock>
            <attributes>
                <attribute name="color" value="M" />
            </attributes>
        </variant>
        <variant>
            <price>170</price>
            <stock unit="item">7</stock>
            <attributes>
                <attribute name="color" value="S" />
            </attributes>
        </variant>
        <variant>
            <price>180</price>
            <stock unit="item">12</stock>
            <attributes>
                <attribute name="color" value="XS" />
            </attributes>
        </variant>
    </product>
</root>

but instead I get:

***output_1.xml***
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <product-listing-group>
        <productId>1</productId>
        <variant>
            <productId>1</productId>
            <price>100</price>
            <stock unit="item">10</stock>
            <attributes>
                <attribute name="color" value="red"/>
            </attributes>
        </variant>
        <variant>
            <productId>1</productId>
            <price>100</price>
            <stock unit="item">8</stock>
            <attributes>
                <attribute name="color" value="blue"/>
            </attributes>
        </variant>
        <variant>
            <productId>1</productId>
            <price>150</price>
            <stock unit="item">12</stock>
            <attributes>
                <attribute name="color" value="green"/>
            </attributes>
        </variant>
    </product-listing-group>
    <variant>
        <productId>1</productId>
        <price>100</price>
        <stock unit="item">8</stock>
        <attributes>
            <attribute name="color" value="blue"/>
        </attributes>
    </variant>
    <variant>
        <productId>1</productId>
        <price>150</price>
        <stock unit="item">12</stock>
        <attributes>
            <attribute name="color" value="green"/>
        </attributes>
    </variant>
    <product-listing-group>
        <productId>2</productId>
        <variant>
            <productId>2</productId>
            <price>200</price>
            <stock unit="item">7</stock>
            <attributes>
                <attribute name="color" value="purple"/>
                <attribute name="material" value="faux-leather"/>
            </attributes>
        </variant>
    </product-listing-group>
    <product-listing-group>
        <productId>3</productId>
        <variant>
            <productId>3</productId>
            <price>190</price>
            <stock unit="item">22</stock>
            <attributes>
                <attribute name="color" value="yellow"/>
                <attribute name="size" value="XL"/>
            </attributes>
        </variant>
        <variant>
            <productId>3</productId>
            <price>180</price>
            <stock unit="item">13</stock>
            <attributes>
                <attribute name="color" value="yellow"/>
                <attribute name="size" value="L"/>
            </attributes>
        </variant>
        <variant>
            <productId>3</productId>
            <price>170</price>
            <stock unit="item">5</stock>
            <attributes>
                <attribute name="color" value="yellow"/>
                <attribute name="size" value="M"/>
            </attributes>
        </variant>
        <variant>
            <productId>3</productId>
            <price>170</price>
            <stock unit="item">7</stock>
            <attributes>
                <attribute name="color" value="yellow"/>
                <attribute name="size" value="S"/>
            </attributes>
        </variant>
        <variant>
            <productId>3</productId>
            <price>180</price>
            <stock unit="item">12</stock>
            <attributes>
                <attribute name="color" value="yellow"/>
                <attribute name="size" value="XS"/>
            </attributes>
        </variant>
    </product-listing-group>
    <variant>
        <productId>3</productId>
        <price>180</price>
        <stock unit="item">13</stock>
        <attributes>
            <attribute name="color" value="yellow"/>
            <attribute name="size" value="L"/>
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>170</price>
        <stock unit="item">5</stock>
        <attributes>
            <attribute name="color" value="yellow"/>
            <attribute name="size" value="M"/>
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>170</price>
        <stock unit="item">7</stock>
        <attributes>
            <attribute name="color" value="yellow"/>
            <attribute name="size" value="S"/>
        </attributes>
    </variant>
    <variant>
        <productId>3</productId>
        <price>180</price>
        <stock unit="item">12</stock>
        <attributes>
            <attribute name="color" value="yellow"/>
            <attribute name="size" value="XS"/>
        </attributes>
    </variant>
</root>

As you can see while the variants are grouped correctly, all the variants except for the first in their respective groups are repeated twice, once withing the grouping and once just outside of it.

Why is this? And how can I fix it?

kaan_a
  • 3,503
  • 1
  • 28
  • 52

1 Answers1

1

You need to block processing for the second, third, fourth, .. variant in a group, otherwise the default identity transformation copies them:

<xsl:template match="/root/variant[productId][not(generate-id() = generate-id(key('variants-by-productId', productId)[1]))]"/>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Could you also tell me why the identity transformation doesn't copy the first one? – kaan_a Jul 05 '20 at 06:58
  • 1
    Well, for the first one you already have the template set up with `xsl:template match="/root/variant[productId][generate-id() = generate-id(key('variants-by-productId', productId)[1])]"` to output the wrapper `product-listing-group`, so there obviously the identity transformation doesn't kick in as `match="@* | node()"` (where here the `node()` could match) has a lower priority (by default) as any `match="variant"` or `match="/root/variant..."`. But the predicate `generate-id() = generate-id(key('variants-by-productId', productId)[1])` is basically the XSLT 1 way of `. is key('...', ...)[1]` – Martin Honnen Jul 05 '20 at 07:06