2

This is my first question here.

I am trying to create a xslt that transforms XML to Text. I need to find the distinct list of the LANG and OFFICEID concatenation, that can then be used in a xsl:for-each loop

I have created a minimal Version of the XML I am working with

    <docinfo>
        <printfile customer="Migrol">
            <envelope postagetype="A">
                <index name="LANG" value="G"/>
                <index name="OFFICEID" value="500"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="F"/>
                <index name="OFFICEID" value="500"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="G"/>
                <index name="OFFICEID" value="500"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="I"/>
                <index name="OFFICEID" value="300"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="G"/>
                <index name="OFFICEID" value="100"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="G"/>
                <index name="OFFICEID" value="100"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="I"/>
                <index name="OFFICEID" value="300"/>
            </envelope>
            <envelope postagetype="A">
                <index name="LANG" value="G"/>
                <index name="OFFICEID" value="500"/>
            </envelope>
        </printfile>
    </docinfo>

With XPath 2.0 this can be solved by using the Statement

/docinfo/printfile/distinct-values(envelope/concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value))

or

/docinfo/printfile/envelope[not(concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value)=preceding-sibling::envelope/concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value))]/concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value)

Sadly I have not found a way to acchive this in XPath 1.0. The concat function seems to be giving me hassles here.

A simple version of the XSLT I want to use

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:template match="printfile">

        <xsl:for-each select="/docinfo/printfile/envelope[not(concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value)=preceding-sibling::envelope/concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value))]">
            <xsl:variable name="OID" select="index[@name='OFFICEID'][1]/@value"/>
            <xsl:variable name="LANG" select="index[@name='LANG'][1]/@value"/>

            <xsl:value-of select="$OID"/>
            <xsl:text> </xsl:text>
            <xsl:value-of select="$LANG"/>

            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>

    </xsl:template>

    <!-- suppress default text -->
    <xsl:template match="text()"/>
</xsl:stylesheet>

The result should look something like

"500 G"
"500 F"
"300 I"
"100 G"

Does anyone have a suggestion on how this could be achieved with XSLT and XPath 1.0

Ed Bangga
  • 12,879
  • 4
  • 16
  • 30
Jann
  • 23
  • 3

1 Answers1

0

The preferred method to extract distinct values in XSLT 1.0 is by Muenchian grouping.

In your example you need to use a compound key:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:key name="env" match="envelope" use="concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value)" />

<xsl:template match="/docinfo">
    <xsl:for-each select="printfile/envelope[count(. | key('env', concat(index[@name='OFFICEID']/@value, index[@name='LANG']/@value))[1]) = 1]">
        <xsl:value-of select="index[@name='OFFICEID']/@value"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="index[@name='LANG']/@value"/>
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Note that in most cases you would include a separator when defining the key, e.g.

<xsl:key name="env" match="envelope" use="concat(index[@name='OFFICEID']/@value, '|', index[@name='LANG']/@value)" />

to prevent false positives.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thank you very much for this. This solved my issue. I will need to look into Muenchian Grouping to see how this works exactly. Thank you again. – Jann Jun 27 '19 at 09:26
  • It is an essential and very often used design pattern in XSLT 1.0. Although some XSLT 1.0 processors also support the EXSLT `set:distinct()` extension function, it wouldn't work here because of the need to combine two values. – michael.hor257k Jun 27 '19 at 09:36