XML transformation based on attributes endings

2019-09-02 18:08发布

I've this xml code:

<items>
    <item name="xyz_low" />
    <item name="xyz_hight" />
    <item name="xyz_medium" />
    <item name="abc_medium" />
    <item name="abc_low" />
    <item name="abc_hight" />
</items>

I would like to get something like:

<items>
    <item name="xyz_hight" />
    <item name="xyz_medium" />
    <item name="xyz_low" />
    <item name="abc_hight" />
    <item name="abc_medium" />
    <item name="abc_low" />
</items>

The sorting happens only between tags with the same first part of the attribute name (the part before the underscore). The order is hight, medium and low.

I've look at ends-with, but it's not enough for me because I don't know if the first part could be xyz, abc or something else.

I don't need to sort the first part: I just want to sort the suffixes within the group of item tags starting in the same way.

Is it possible or I should choose a different approach?

标签: xml xslt
1条回答
ゆ 、 Hurt°
2楼-- · 2019-09-02 18:31

To sort items in a certain fixed order like this you can use a trick like this: define a variable for the sort order

<xsl:variable name="sortOrder" select="'|hight|medium|low|'" />

and now you can sort using

<xsl:sort select="string-length(substring-before($sortOrder, concat('|', value, '|')))"
          data-type="number" />

for whatever value you need. If the value is "hight" then the substring-before will be of length 0, for "medium" it'll be of length 6, etc.

Here's an XSLT 2.0 template that'll do what you're after, assuming that variable definition

<xsl:template match="items">
  <items>
    <xsl:for-each-group select="item" group-adjacent="substring-before(@name, '_')">
      <xsl:perform-sort select="current-group()">
        <xsl:sort select="string-length(substring-before($sortOrder,
                                concat('|', substring-after(@name, '_'), '|')))"
          data-type="number" />
      </xsl:perform-sort>
    </xsl:for-each-group>
  </items>
</xsl:template>

To do the same in XSLT 1.0 you'd need to do a Muenchian grouping trick

<xsl:key name="itemByPrefix" match="item" use="substring-before(@name, '_')" />

and then in the template

<xsl:template match="items">
  <items>
    <xsl:for-each select="item[generate-id() = generate-id(
                key('itemByPrefix', substring-before(@name, '_'))[1])]">
      <xsl:for-each select="key('itemByPrefix', substring-before(@name, '_'))">
        <xsl:sort select="string-length(substring-before($sortOrder,
                                concat('|', substring-after(@name, '_'), '|')))"
          data-type="number" />
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:for-each>
  </items>
</xsl:template>

(the XSLT 2.0 version still works if there are several different sets of items in the source document, the XSLT 1.0 version would get a lot messier in that case).

查看更多
登录 后发表回答