Use XSLT to group repetitive XML data

2019-04-13 14:26发布

问题:

I have an XML that needs to be transformed into a better grouping of the items.

  • The items need to be counted.
  • I do not know how many dogs there will be in the list.
  • I may not know how many descriptive items about the dogs there will be. In the example there are three, but it could be any number. If this cannot be flexible, then a fixed number is ok.

Preferrably XSLT 1.0

Is this possible?

I need to go from this:

<table name = "dogs">
    <fields>
        <field name = "name" value = "dog1"></field>
        <field name = "age" value = "2"></field>
        <field name = "haircolor" value = "brown"></field>
        <field name = "name" value = "dog2"></field>
        <field name = "age" value = "10"></field>
        <field name = "haircolor" value = "white"></field>
        <field name = "name" value = "dog3"></field>
        <field name = "age" value = "7"></field>
        <field name = "haircolor" value = "black"></field>
        <field name = "name" value = "dog4"></field>
        <field name = "age" value = "4"></field>
        <field name = "haircolor" value = "brown"></field>
    </fields>
</table>

To this:

<dogs count = "4">
    <dog>
        <name>dog1</name>
        <age>2</age>
        <haircolor>brown</haircolor>
    </dog>
    <dog>
        <name>dog2</name>
        <age>10</age>
        <haircolor>white</haircolor>
    </dog>
    <dog>
        <name>dog3</name>
        <age>7</age>
        <haircolor>black</haircolor>
    </dog>
    <dog>
        <name>dog4</name>
        <age>4</age>
        <haircolor>brown</haircolor>
    </dog>
</dogs>

回答1:

Assuming each group of fields starts with a name: .

XSLT 1.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="fields-by-lead" match="field[@name!='name']" use="generate-id(preceding-sibling::field[@name='name'][1])" />

<xsl:template match="/table">
    <xsl:variable name="names" select="fields/field[@name='name']" />
    <dogs count="{count($names)}">
        <xsl:for-each select="$names">
            <dog>
                <name><xsl:value-of select="@value"/></name>
                <xsl:for-each select="key('fields-by-lead', generate-id())">
                    <xsl:element name="{@name}">
                        <xsl:value-of select="@value"/>
                    </xsl:element>
                </xsl:for-each>
            </dog>
        </xsl:for-each>
    </dogs>
</xsl:template>

</xsl:stylesheet>

Edit:

The following modification starts a new group for every field whose name matches the name of the very first field.

XSLT 1.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="fields-by-lead" match="field" use="generate-id(preceding-sibling::field[@name=/table/fields/field[1]/@name][1])" />

<xsl:template match="/table">
    <xsl:variable name="lead-label" select="/table/fields/field[1]/@name" />
    <xsl:variable name="leads" select="fields/field[@name=$lead-label]" />
    <dogs count="{count($leads)}">
        <xsl:for-each select="$leads">
            <dog>
                <xsl:for-each select=". | key('fields-by-lead', generate-id())[@name!=$lead-label]">
                    <xsl:element name="{@name}">
                        <xsl:value-of select="@value"/>
                    </xsl:element>
                </xsl:for-each>
            </dog>
        </xsl:for-each>
    </dogs>
</xsl:template>

</xsl:stylesheet>


回答2:

Here is an XSLT 1.0 way of doing it:

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

<xsl:output method="xml" indent="yes"/>

<xsl:template match="table">
  <xsl:element name="{@name}">
    <xsl:variable name="fields" select="fields/field[@name = current()/fields/field[1]/@name]"/>
    <xsl:attribute name="count"><xsl:value-of select="count($fields)"/></xsl:attribute>
    <xsl:apply-templates select="$fields"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field">
  <xsl:variable name="this" select="."/>
  <xsl:element name="{../../@name}">
    <name><xsl:value-of select="@value"/></name>
    <xsl:apply-templates select="following-sibling::field[1][not(@name = $this/@name)]" mode="trans">
      <xsl:with-param name="head-name" select="$this/@name"/>
    </xsl:apply-templates>
  </xsl:element>

</xsl:template>

<xsl:template match="field" mode="trans">
  <xsl:param name="head-name"/>
  <xsl:element name="{@name}"><xsl:value-of select="@value"/></xsl:element>
  <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans">
    <xsl:with-param name="head-name" select="$head-name"/>
  </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>

If you want to use a parameter then the code could use

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

<xsl:param name="head-name" select="'name'"/>

<xsl:output method="xml" indent="yes"/>

<xsl:template match="table">
  <xsl:element name="{@name}">
    <xsl:variable name="fields" select="fields/field[@name = $head-name]"/>
    <xsl:attribute name="count"><xsl:value-of select="count($fields)"/></xsl:attribute>
    <xsl:apply-templates select="$fields"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field">
  <xsl:variable name="this" select="."/>
  <xsl:element name="{../../@name}">
    <name><xsl:value-of select="@value"/></name>
    <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field" mode="trans">
  <xsl:element name="{@name}"><xsl:value-of select="@value"/></xsl:element>
  <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans">
    <xsl:with-param name="head-name" select="$head-name"/>
  </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>


标签: xml xslt