xmlxsltxslt-1.0muenchian-grouping

Applying Muenchian grouping to XSLT


completely new to xlst, I've tried to follow numerous links to apply Muenchian grouping but to no avail, most likely because I'm not putting the extra lines in the right places probably, and have no idea how to convert to xlst2 in order to use for-each-group instead. Anyway the csv file I'm trying to convert is very simple:

123456789,2021-08-05T00:00:00+00:00,GBP,2099-12-31T00:00:00+00:00,23.00
123456789,2021-07-22T00:00:00+00:00,GBP,2099-07-22T00:00:00+00:00,35.00
123456789,2021-07-06T00:00:00+00:00,GBP,2099-07-13T00:00:00+00:00,39.50
987654321,2021-08-05T00:00:00+00:00,EUR,2099-12-31T00:00:00+00:00,25.95
987654321,2021-07-22T00:00:00+00:00,EUR,2099-07-22T00:00:00+00:00,39.95
987654321,2021-07-06T00:00:00+00:00,EUR,2099-07-13T00:00:00+00:00,44.95

Or in XML format:

<DEMarkdown xmlns="http://F.FlatFileSchema1">
    <DEMarkdowns xmlns="">
        <sku>123456789</sku>
        <valid-from>2021-08-05T00:00:00+00:00</valid-from>
        <currency>GBP</currency>
        <valid-to>2099-12-31T00:00:00+00:00</valid-to>
        <value>23.00</value>
    </DEMarkdowns>
    <DEMarkdowns xmlns="">
        <sku>123456789</sku>
        <valid-from>2021-07-22T00:00:00+00:00</valid-from>
        <currency>GBP</currency>
        <valid-to>2099-07-22T00:00:00+00:00</valid-to>
        <value>35.00</value>
    </DEMarkdowns>
    <DEMarkdowns xmlns="">
        <sku>123456789</sku>
        <valid-from>2021-07-06T00:00:00+00:00</valid-from>
        <currency>GBP</currency>
        <valid-to>2099-07-13T00:00:00+00:00</valid-to>
        <value>39.50</value>
    </DEMarkdowns>
    <DEMarkdowns xmlns="">
        <sku>987654321</sku>
        <valid-from>2021-08-05T00:00:00+00:00</valid-from>
        <currency>EUR</currency>
        <valid-to>2099-12-31T00:00:00+00:00</valid-to>
        <value>25.95</value>
    </DEMarkdowns>
    <DEMarkdowns xmlns="">
        <sku>987654321</sku>
        <valid-from>2021-07-22T00:00:00+00:00</valid-from>
        <currency>EUR</currency>
        <valid-to>2099-07-22T00:00:00+00:00</valid-to>
        <value>39.95</value>
    </DEMarkdowns>
    <DEMarkdowns xmlns="">
        <sku>987654321</sku>
        <valid-from>2021-07-06T00:00:00+00:00</valid-from>
        <currency>EUR</currency>
        <valid-to>2099-07-13T00:00:00+00:00</valid-to>
        <value>44.95</value>
    </DEMarkdowns>
</DEMarkdown>

I've used BizTalk to generate my Flat File Source Schema:

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://F.FlatFileSchema1" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" targetNamespace="http://F.FlatFileSchema1" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:annotation>
    <xs:appinfo>
      <schemaEditorExtension:schemaInfo namespaceAlias="b" extensionClass="Microsoft.BizTalk.FlatFileExtension.FlatFileExtension" standardName="Flat File" xmlns:schemaEditorExtension="http://schemas.microsoft.com/BizTalk/2003/SchemaEditorExtensions" />
      <b:schemaInfo standard="Flat File" codepage="65001" default_pad_char=" " pad_char_type="char" count_positions_by_byte="false" parser_optimization="speed" lookahead_depth="3" suppress_empty_nodes="false" generate_empty_nodes="true" allow_early_termination="false" early_terminate_optional_fields="false" allow_message_breakup_of_infix_root="false" compile_parse_tables="false" root_reference="DEMarkdown" />
    </xs:appinfo>
  </xs:annotation>
  <xs:element name="DEMarkdown">
    <xs:annotation>
      <xs:appinfo>
        <b:recordInfo structure="delimited" child_delimiter_type="hex" child_delimiter="0xD 0xA" child_order="postfix" sequence_number="1" preserve_delimiter_for_empty_data="true" suppress_trailing_delimiters="false" />
      </xs:appinfo>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:annotation>
          <xs:appinfo>
            <groupInfo sequence_number="0" xmlns="http://schemas.microsoft.com/BizTalk/2003" />
          </xs:appinfo>
        </xs:annotation>
        <xs:element maxOccurs="unbounded" name="DEMarkdowns">
          <xs:annotation>
            <xs:appinfo>
              <b:recordInfo structure="delimited" child_delimiter_type="char" child_delimiter="," child_order="infix" sequence_number="1" preserve_delimiter_for_empty_data="true" suppress_trailing_delimiters="false" />
            </xs:appinfo>
          </xs:annotation>
          <xs:complexType>
            <xs:sequence>
              <xs:annotation>
                <xs:appinfo>
                  <groupInfo sequence_number="0" xmlns="http://schemas.microsoft.com/BizTalk/2003" />
                </xs:appinfo>
              </xs:annotation>
              <xs:element name="sku" type="xs:string">
                <xs:annotation>
                  <xs:appinfo>
                    <b:fieldInfo justification="left" sequence_number="1" />
                  </xs:appinfo>
                </xs:annotation>
              </xs:element>
              <xs:element name="valid-from" type="xs:dateTime">
                <xs:annotation>
                  <xs:appinfo>
                    <b:fieldInfo justification="left" sequence_number="2" />
                  </xs:appinfo>
                </xs:annotation>
              </xs:element>
              <xs:element name="currency" type="xs:string">
                <xs:annotation>
                  <xs:appinfo>
                    <b:fieldInfo justification="left" sequence_number="3" />
                  </xs:appinfo>
                </xs:annotation>
              </xs:element>
              <xs:element name="valid-to" type="xs:dateTime">
                <xs:annotation>
                  <xs:appinfo>
                    <b:fieldInfo justification="left" sequence_number="4" />
                  </xs:appinfo>
                </xs:annotation>
              </xs:element>
              <xs:element name="value" type="xs:decimal">
                <xs:annotation>
                  <xs:appinfo>
                    <b:fieldInfo justification="left" sequence_number="5" />
                  </xs:appinfo>
                </xs:annotation>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

The XML destination schema is this:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="http://www.fakewebsite.com/xml/ns/enfinity/6.5/core/impex-dt" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.fakewebsite.com/7.1/bc_pricing/impex" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="enfinity">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="product-price-list">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="display-name" type="xs:string" />
                            <xs:element name="description" type="xs:string" />
                            <xs:element name="enabled" type="xs:boolean" />
                            <xs:element name="priority" type="xs:unsignedByte" />
                            <xs:element name="target-groups">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="customer-segments">
                                            <xs:complexType>
                                                <xs:sequence>
                                                    <xs:element maxOccurs="unbounded" name="customer-segment">
                                                        <xs:complexType>
                                                            <xs:attribute name="id" type="xs:string" use="required" />
                                                            <xs:attribute name="repository-id" type="xs:string" use="required" />
                                                        </xs:complexType>
                                                    </xs:element>
                                                </xs:sequence>
                                            </xs:complexType>
                                        </xs:element>
                                    </xs:sequence>
                                </xs:complexType>
                            </xs:element>
                            <xs:element maxOccurs="unbounded" name="product-price-list-entry">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element maxOccurs="unbounded" name="price-scale-table">
                                            <xs:complexType>
                                                <xs:sequence>
                                                    <xs:element name="valid-from" type="xs:dateTime" />
                                                    <xs:element name="valid-to" type="xs:dateTime" />
                                                    <xs:element name="price-scale-entries">
                                                        <xs:complexType>
                                                            <xs:sequence>
                                                                <xs:element name="fixed-price-entry">
                                                                    <xs:complexType>
                                                                        <xs:sequence>
                                                                            <xs:element name="value" type="xs:decimal" />
                                                                        </xs:sequence>
                                                                        <xs:attribute name="quantity" type="xs:unsignedByte" use="required" />
                                                                        <xs:attribute name="unit" type="xs:string" use="required" />
                                                                    </xs:complexType>
                                                                </xs:element>
                                                            </xs:sequence>
                                                        </xs:complexType>
                                                    </xs:element>
                                                </xs:sequence>
                                                <xs:attribute name="type-code" type="xs:unsignedByte" use="required" />
                                                <xs:attribute name="currency" type="xs:string" use="required" />
                                            </xs:complexType>
                                        </xs:element>
                                    </xs:sequence>
                                    <xs:attribute name="sku" type="xs:unsignedInt" use="required" />
                                    <xs:attribute name="import-mode" type="xs:string" use="required" />
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                        <xs:attribute name="id" type="xs:string" use="required" />
                        <xs:attribute name="priceType" type="xs:string" use="required" />
                        <xs:attribute name="import-mode" type="xs:string" use="required" />
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
            <xs:attribute name="major" type="xs:unsignedByte" use="required" />
            <xs:attribute name="minor" type="xs:unsignedByte" use="required" />
            <xs:attribute name="family" type="xs:string" use="required" />
            <xs:attribute name="branch" type="xs:string" use="required" />
            <xs:attribute name="build" type="xs:string" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>

The current output of my XML looks like this at the moment:

<?xml version="1.0" encoding="utf-8"?>
<ns0:enfinity major="6" minor="1" family="enfinity" branch="enterprise" build="build" xmlns:ns0="http://www.fakewebsite.com/7.1/bc_pricing/impex" xmlns:s0="http://F.FlatFileSchema1">
    <ns0:product-price-list id="DEMarkdown" priceType="ES_SalePrice" import-mode="UPDATE">
        <ns0:display-name>DE Markdown</ns0:display-name>
        <ns0:description/>
        <ns0:enabled>true</ns0:enabled>
        <ns0:priority>1</ns0:priority>
        <ns0:target-groups>
            <ns0:customer-segments>
                <ns0:customer-segment id="Everyone" repository-id="WhiteStuff-DE-Anonymous"/>
                <ns0:customer-segment id="IG_RegisteredUsers" repository-id="WhiteStuff-DE-Anonymous"/>
            </ns0:customer-segments>
        </ns0:target-groups>
        <ns0:product-price-list-entry sku="123456789" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="GBP">
                <ns0:valid-from>2021-08-05T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-12-31T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>23.00</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
        <ns0:product-price-list-entry sku="123456789" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="GBP">
                <ns0:valid-from>2021-07-22T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-07-22T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>35.00</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
        <ns0:product-price-list-entry sku="123456789" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="GBP">
                <ns0:valid-from>2021-07-06T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-07-13T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>39.50</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
        <ns0:product-price-list-entry sku="987654321" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="EUR">
                <ns0:valid-from>2021-08-05T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-12-31T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>25.95</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
        <ns0:product-price-list-entry sku="987654321" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="EUR">
                <ns0:valid-from>2021-07-22T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-07-22T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>39.95</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
        <ns0:product-price-list-entry sku="987654321" import-mode="REPLACE">
            <ns0:price-scale-table type-code="1" currency="EUR">
                <ns0:valid-from>2021-07-06T00:00:00+00:00</ns0:valid-from>
                <ns0:valid-to>2099-07-13T00:00:00+00:00</ns0:valid-to>
                <ns0:price-scale-entries>
                    <ns0:fixed-price-entry quantity="1" unit="">
                        <ns0:value>44.95</ns0:value>
                    </ns0:fixed-price-entry>
                </ns0:price-scale-entries>
            </ns0:price-scale-table>
        </ns0:product-price-list-entry>
    </ns0:product-price-list>
</ns0:enfinity>

But I want to group by sku so each sku only appears once within product-price-list-entry, so that it looks like this instead:

<enfinity xsi:schemaLocation="http://www.fakewebsite.com/7.1/bc_pricing/impex bc_pricing.xsd" xmlns="http://www.fakewebsite.com/7.1/bc_pricing/impex" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="http://www.fakewebsite.com/6.5/core/impex-dt" major="6" minor="1" family="enfinity" branch="enterprise" build="build">
  <product-price-list id="DEMarkdown" priceType="ES_SalePrice" import-mode="UPDATE">
    <display-name>DE Markdown</display-name>
    <description></description>
    <enabled>true</enabled>
    <priority>1</priority>
    <target-groups>
      <customer-segments>
        <customer-segment id="Everyone" repository-id="WhiteStuff-DE-Anonymous"/>
        <customer-segment id="IG_RegisteredUsers" repository-id="WhiteStuff-DE-Anonymous"/>
      </customer-segments>
    </target-groups>
    <product-price-list-entry sku="123456789" import-mode="REPLACE">
      <price-scale-table type-code="1" currency="GBP">
        <valid-from>2021-08-05T00:00:00+00:00</valid-from>
        <valid-to>2099-12-31T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>23.00</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
      <price-scale-table type-code="1" currency="GBP">
        <valid-from>2021-07-22T00:00:00+00:00</valid-from>
        <valid-to>2099-07-22T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>35.00</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
      <price-scale-table type-code="1" currency="EUR">
        <valid-from>2021-07-22T00:00:00+00:00</valid-from>
        <valid-to>2099-07-22T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>39.95</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
    </product-price-list-entry>
    <product-price-list-entry sku="987654321" import-mode="REPLACE">
      <price-scale-table type-code="1" currency="EUR">
        <valid-from>2021-08-05T00:00:00+00:00</valid-from>
        <valid-to>2099-12-31T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>25.95</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
      <price-scale-table type-code="1" currency="GBP">
        <valid-from>2021-07-05T00:00:00+00:00</valid-from>
        <valid-to>2099-07-13T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>39.50</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
      <price-scale-table type-code="1" currency="EUR">
        <valid-from>2021-07-05T00:00:00+00:00</valid-from>
        <valid-to>2099-07-13T00:00:00+00:00</valid-to>
        <price-scale-entries>
          <fixed-price-entry quantity="1" unit="">
            <value>44.95</value>
          </fixed-price-entry>
        </price-scale-entries>
      </price-scale-table>
    </product-price-list-entry>
  </product-price-list>
</enfinity>

The xlst which currently converts csv to xml is this, which is what I'm trying to modify. I've taken out what I tried to add myself as it didn't work :(

<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ns0="http://www.fakewebsite.com/7.1/bc_pricing/impex" xmlns:s0="http://F.FlatFileSchema1">
    <xsl:output method="xml" indent="no"/>
    <xsl:template match="/">
        <xsl:apply-templates select="/s0:DEMarkdown"/>
    </xsl:template>
    <xsl:template match="/s0:DEMarkdown">
        <ns0:enfinity>
            <xsl:attribute name="major">
                <xsl:text>6</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="minor">
                <xsl:text>1</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="family">
                <xsl:text>enfinity</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="branch">
                <xsl:text>enterprise</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="build">
                <xsl:text>build</xsl:text>
            </xsl:attribute>
            <ns0:product-price-list>
                <xsl:attribute name="id">
                    <xsl:text>DEMarkdown</xsl:text>
                </xsl:attribute>
                <xsl:attribute name="priceType">
                    <xsl:text>ES_SalePrice</xsl:text>
                </xsl:attribute>
                <xsl:attribute name="import-mode">
                    <xsl:text>UPDATE</xsl:text>
                </xsl:attribute>
                <ns0:display-name>
                    <xsl:text>DE Markdown</xsl:text>
                </ns0:display-name>
                <ns0:description>
                    <xsl:text/>
                </ns0:description>
                <ns0:enabled>
                    <xsl:text>true</xsl:text>
                </ns0:enabled>
                <ns0:priority>
                    <xsl:text>1</xsl:text>
                </ns0:priority>
                <ns0:target-groups>
                    <ns0:customer-segments>
                        <ns0:customer-segment>
                            <xsl:attribute name="id">
                                <xsl:text>Everyone</xsl:text>
                            </xsl:attribute>
                            <xsl:attribute name="repository-id">
                                <xsl:text>WhiteStuff-DE-Anonymous</xsl:text>
                            </xsl:attribute>
                        </ns0:customer-segment>
                        <ns0:customer-segment>
                            <xsl:attribute name="id">
                                <xsl:text>IG_RegisteredUsers</xsl:text>
                            </xsl:attribute>
                            <xsl:attribute name="repository-id">
                                <xsl:text>WhiteStuff-DE-Anonymous</xsl:text>
                            </xsl:attribute>
                        </ns0:customer-segment>
                    </ns0:customer-segments>
                </ns0:target-groups>
                <xsl:for-each select="DEMarkdowns">
                    <ns0:product-price-list-entry>
                        <xsl:attribute name="sku">
                            <xsl:value-of select="sku/text()"/>
                        </xsl:attribute>
                        <xsl:attribute name="import-mode">
                            <xsl:text>REPLACE</xsl:text>
                        </xsl:attribute>
                        <ns0:price-scale-table>
                            <xsl:attribute name="type-code">
                                <xsl:text>1</xsl:text>
                            </xsl:attribute>
                            <xsl:attribute name="currency">
                                <xsl:value-of select="currency/text()"/>
                            </xsl:attribute>
                            <ns0:valid-from>
                                <xsl:value-of select="valid-from/text()"/>
                            </ns0:valid-from>
                            <ns0:valid-to>
                                <xsl:value-of select="valid-to/text()"/>
                            </ns0:valid-to>
                            <ns0:price-scale-entries>
                                <ns0:fixed-price-entry>
                                    <xsl:attribute name="quantity">
                                        <xsl:text>1</xsl:text>
                                    </xsl:attribute>
                                    <xsl:attribute name="unit">
                                        <xsl:text/>
                                    </xsl:attribute>
                                    <ns0:value>
                                        <xsl:value-of select="value/text()"/>
                                    </ns0:value>
                                </ns0:fixed-price-entry>
                            </ns0:price-scale-entries>
                        </ns0:price-scale-table>
                    </ns0:product-price-list-entry>
                </xsl:for-each>
            </ns0:product-price-list>
        </ns0:enfinity>
    </xsl:template>
</xsl:stylesheet>

Solution

  • Let me start with something irrelevant to your question, but still pertinent to the topic, IMHO: your current stylesheet is unduly verbose. As noted in the comments, it could be reduced significantly by using attribute value templates instead of xsl:attribute instructions. In addition, the first template is entirely redundant, as this is taken care of by the built-in template rules. And a few other things. I have taken the liberty of removing all that unnecessary verbosity.

    Other than that, this is a pretty standard implementation of Muenchian grouping - not sure why you had such problems with it:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.fakewebsite.com/7.1/bc_pricing/impex" 
    xmlns:s0="http://F.FlatFileSchema1"
    exclude-result-prefixes="s0">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:key name="prod" match="DEMarkdowns" use="sku" />
        
    <xsl:template match="/s0:DEMarkdown">
        <enfinity major="6" minor="1" family="enfinity" branch="enterprise" build="build">
            <product-price-list id="DEMarkdown" priceType="ES_SalePrice" import-mode="UPDATE">
                <display-name>DE Markdown</display-name>
                <description></description>
                <enabled>true</enabled>
                <priority>1</priority>
                <target-groups>
                    <customer-segments>
                        <customer-segment id="Everyone" repository-id="WhiteStuff-DE-Anonymous"/>
                        <customer-segment id="IG_RegisteredUsers" repository-id="WhiteStuff-DE-Anonymous"/>
                    </customer-segments>
                </target-groups>
                <!-- create a group for each unique sku -->
                <xsl:for-each select="DEMarkdowns[count(. | key('prod', sku)[1]) = 1]">
                    <product-price-list-entry sku="{sku}" import-mode="REPLACE">
                        <!-- for each member of current group -->
                        <xsl:for-each select="key('prod', sku)">
                            <price-scale-table type-code="1" currency="{currency}">
                                <valid-from>
                                    <xsl:value-of select="valid-from"/>
                                </valid-from>
                                <valid-to>
                                    <xsl:value-of select="valid-to"/>
                                </valid-to>
                                <price-scale-entries>
                                    <fixed-price-entry quantity="1" unit="">
                                        <value>
                                            <xsl:value-of select="value"/>
                                        </value>
                                    </fixed-price-entry>
                                </price-scale-entries>
                            </price-scale-table>
                        </xsl:for-each>
                    </product-price-list-entry>
                </xsl:for-each>
            </product-price-list>
        </enfinity>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Applied to your input example,, this will generate:

    Result

    <?xml version="1.0" encoding="UTF-8"?>
    <enfinity xmlns="http://www.fakewebsite.com/7.1/bc_pricing/impex" major="6" minor="1" family="enfinity" branch="enterprise" build="build">
      <product-price-list id="DEMarkdown" priceType="ES_SalePrice" import-mode="UPDATE">
        <display-name>DE Markdown</display-name>
        <description/>
        <enabled>true</enabled>
        <priority>1</priority>
        <target-groups>
          <customer-segments>
            <customer-segment id="Everyone" repository-id="WhiteStuff-DE-Anonymous"/>
            <customer-segment id="IG_RegisteredUsers" repository-id="WhiteStuff-DE-Anonymous"/>
          </customer-segments>
        </target-groups>
        <product-price-list-entry sku="123456789" import-mode="REPLACE">
          <price-scale-table type-code="1" currency="GBP">
            <valid-from>2021-08-05T00:00:00+00:00</valid-from>
            <valid-to>2099-12-31T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>23.00</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
          <price-scale-table type-code="1" currency="GBP">
            <valid-from>2021-07-22T00:00:00+00:00</valid-from>
            <valid-to>2099-07-22T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>35.00</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
          <price-scale-table type-code="1" currency="GBP">
            <valid-from>2021-07-06T00:00:00+00:00</valid-from>
            <valid-to>2099-07-13T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>39.50</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
        </product-price-list-entry>
        <product-price-list-entry sku="987654321" import-mode="REPLACE">
          <price-scale-table type-code="1" currency="EUR">
            <valid-from>2021-08-05T00:00:00+00:00</valid-from>
            <valid-to>2099-12-31T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>25.95</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
          <price-scale-table type-code="1" currency="EUR">
            <valid-from>2021-07-22T00:00:00+00:00</valid-from>
            <valid-to>2099-07-22T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>39.95</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
          <price-scale-table type-code="1" currency="EUR">
            <valid-from>2021-07-06T00:00:00+00:00</valid-from>
            <valid-to>2099-07-13T00:00:00+00:00</valid-to>
            <price-scale-entries>
              <fixed-price-entry quantity="1" unit="">
                <value>44.95</value>
              </fixed-price-entry>
            </price-scale-entries>
          </price-scale-table>
        </product-price-list-entry>
      </product-price-list>
    </enfinity>