开发者

Using Template Within XSLT Key Generation

开发者 https://www.devze.com 2023-02-08 11:41 出处:网络
I have an XML document containing items, each having a tokenized ID string. <?xml version=\"1.0\"?>

I have an XML document containing items, each having a tokenized ID string.

<?xml version="1.0"?>
<Test>
<Items>
    <Item>
        <ID>1_A_3</ID>
        <Name>foo</Name>
    </Item>
    <Item>
        <ID>1_B_5</ID>
        <Name>bar</Name>
    </Item>
    <Item>
        <ID>1_B_7</ID>
        <Name>baz</Name>
    </Item>
</Items>
</Test>

I need to transform this into another XML document that groups the items according to the middle part of their ID string (the letter in the above example).

<?xml version='1.0' ?>
<GroupedItems>
    <Group id="A">
        <Item>foo</Item>
    </Group>
    <Group id="B">
        <Item>bar</Item>
        <Item>baz</Item>
    </Group>
</GroupedItems>

I am finding the groups using the keys functionality:

<xsl:key name="uniqueGroupIDs" 
   match="Test/Items/Item" 
   use="substring-before(substring-after(ID,'_'),'_')"/>

<xsl:for-each 
   select="Test/Items/Item[generate-id() = 
    generate-id(key('uniqueGroupIDs',
    substring-before(substring-after(ID,'_'),'_')))]">

Notice the code duplication of the substring calls in both places. I开发者_如何学C already have a template that does the same thing:

<xsl:template name="ExtractGroupID">
    <xsl:param name="idString"/>        
<xsl:value-of 
       select="substring-before(substring-after($idString, '_'),'_')"/>
</xsl:template>

Is there any way to use that template within the key statements, to avoid the duplication of code?

In XSLT 2.0 I would just define a function to do it, but I'm stuck with XSLT 1.0 due to a tools limitation beyond my control.


First, if you take a look into all the answers here with Muenchian grouping method, you will see that every one of them "duplicate" the key value calculation.

The only "solution" for this is a two phase transformation: one template only adding a node with calculate value to the input source, and a key using this generated node (no further calculation). This method is also valid with complex calculated key value: the ones beyond XPath expressions power.

As example, this stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
 exclude-result-prefixes="msxsl">
    <xsl:key name="kItemByKey" match="Item[@key]" use="@key"/>
    <xsl:template match="/">
        <xsl:variable name="vrtfFirstPass">
            <xsl:apply-templates select="/*/*/*"/>
        </xsl:variable>
        <GroupedItems>
            <xsl:apply-templates select="msxsl:node-set($vrtfFirstPass)/*"/>
        </GroupedItems>
    </xsl:template>
    <xsl:template match="Item[not(@key)]">
        <Item key="{substring-before(substring-after(ID,'_'),'_')}">
            <xsl:copy-of select="@*|node()"/>
        </Item>
    </xsl:template>
    <xsl:template match="Item[@key]"/>
    <xsl:template match="Item[generate-id()
                              = generate-id(
                                   key('kItemByKey',@key)[1]
                                )]"
                  priority="1">
        <Group id="{@key}">
            <xsl:apply-templates select="key('kItemByKey',@key)/Name"/>
        </Group>
    </xsl:template>
    <xsl:template match="Name">
        <Item>
            <xsl:value-of select="."/>
        </Item>
    </xsl:template>
</xsl:stylesheet>

Output:

<GroupedItems>
    <Group id="A">
        <Item>foo</Item>
    </Group>
    <Group id="B">
        <Item>bar</Item>
        <Item>baz</Item>
    </Group>
</GroupedItems>

Note: node-set() extension function.


With vanilla XSLT 1.0 you're stuck with the code duplication if you want to use the Muenchian grouping method. Solutions that would be able to use templates in the key extraction would be considerably slower and less readable.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号