Use XSLT to group repetitive XML data

2019-04-13 14:45发布

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>

标签: xml xslt
2条回答
Deceive 欺骗
2楼-- · 2019-04-13 14:55

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>
查看更多
太酷不给撩
3楼-- · 2019-04-13 15:12

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>
查看更多
登录 后发表回答