XSLT – Pass attributes to create parent elements,

2019-08-22 03:49发布

问题:

I'm getting back to learning XSLT after a while and find myself struggling with the push approach.

From the following (simplified) document:

<Index>
    <Person id="Je1" age="30" nationality="Fra">Jean</Person>
    <Person id="Ma1" age="30" nationality="Eng">Mary</Person>
    <Person id="Je2" age="40" nationality="Fra">Jean</Person>
    <Person id="Lu1" age="20" nationality="Ita">Luigi</Person>
    <Person id="He1" age="50" nationality="Gre">Hector</Person>
    <Person id="Pe1" age="45" nationality="Gre">Penelope</Person>
</Index>

I would like: 1. to use the distinct values of the attribute "nationality" to create sorted elements, 2. to pass the items to the corresponding elements 3. to order them according to another original attribute, e.g. "age"

<OrderedIndex>
    <Country key="Eng">
        <Person id="Ma1" age="30" nationality="E">Mary</Person>
    </Country>
    <Country key="Fra">
        <Person id="Je1" age="30" nationality="F">Jean</Person>
        <Person id="Je2" age="40" nationality="F">Jean</Person>
    </Country>
    <Country key="Gre">
        <Person id="Pe1" age="45" nationality="Gre">Penelope</Person>
        <Person id="He1" age="50" nationality="Gre">Hector</Person>
    </Country>
    <Country key="Ita">
        <Person id="Lu1" age="20" nationality="Ita">Luigi</Person>
    </Country>
</OrderedIndex>

I am managing the first step thus:

<xsl:variable name="cou" select="distinct-values(//@nationality)"/>
<xsl:template match="*">
    <xsl:text>&#xa;</xsl:text>
    <List>
    <xsl:for-each select="$cou">
        <xsl:sort/>
        <xsl:text>&#xa;</xsl:text>
        <xsl:element name="country">
            <xsl:attribute name="country">
                <xsl:copy-of select="."/>
            </xsl:attribute>
        </xsl:element>
    </xsl:for-each>
    <xsl:text>&#xa;</xsl:text>
    </List>
</xsl:template>

— but am not managing to use <apply-templates> or <for-each> after that. There is obviously something I don't understand in the selection process.

Thanks for your help!

回答1:

Your distinct-values is selecting @n attributes, but I think you mean to select @nationality attributes

<xsl:variable name="cou" select="distinct-values(//@nationality)"/>

However, distinct-values may not be the best choice to use here, because it returns a sequence of atomic values, so you will no longer be in the context of the original XML document. xsl:for-each-group may be a better match here

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes" />

    <xsl:template match="/*">
        <OrderedIndex>
            <xsl:for-each-group select="Person" group-by="@nationality">
                <xsl:sort select="@nationality"/>
                <Country key="{current-grouping-key()}">
                    <xsl:apply-templates select="current-group()">
                        <xsl:sort select="@age" />
                    </xsl:apply-templates>
                </Country>
            </xsl:for-each-group>
        </OrderedIndex>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Notes:

  • There is really no need to try and output line breaks when outputting XML (unless you do have some special requirement). Just use the indent option on xsl:output

  • The identity template is used to copy the Person elements

  • Note the use of Attribute Value Templates when creating the key attribute on the country element.

Also note, if you did want to use distinct-values you could do it like this...

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes" />

    <xsl:variable name="cou" select="distinct-values(//@nationality)"/>
    <xsl:variable name="root" select="/"/>

    <xsl:key name="people" match="Person" use="@nationality" />

    <xsl:template match="/*">
        <List>
        <xsl:for-each select="$cou">
            <xsl:sort select="." />
            <country key="{.}">
                <xsl:apply-templates select="apply-templates select="">
                    <xsl:sort select="@age" />
                </xsl:apply-templates>
            </country>
        </xsl:for-each>
        </List>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>


标签: xslt