1
votes

This question is kind of related to my earlier question XSLT 1.0: using EXSLT to get element name according to substring I have the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<GenericRecs>
<GenericRecord>
    <record>
        <MBH1/>
    </record>
    <record>
        <BAL1/>
    </record>
    <record>
        <MBH2/>
    </record>
    <record>
        <BAL2/>
    </record>
    <record>
        <PAY2/>
    </record>
    <record>
        <MBH3/>
    </record>
    <record>
        <BAL3/>
    </record>
    <record>
        <PAY3/>
    </record>
</GenericRecord>
</GenericRecs>

and I would like to get this output:

<?xml version="1.0" encoding="UTF-8"?>
<list>
<Card>
    <Data>MBH1</Data>
    <Data>BAL1</Data>
</Card>
<Card>
    <Data>MBH2</Data>
    <Data>BAL2</Data>
    <Data>PAY2</Data>
</Card>
<Card>
    <Data>MBH3</Data>
    <Data>BAL3</Data>
    <Data>PAY3</Data>
</Card>
</list>

With the help of Tomalak have come up with the following XSLT:

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

<xsl:template match="/">
    <list>
        <xsl:apply-templates select="//record/*[starts-with(name(), 'MBH')]"/>
    </list>
</xsl:template>

<xsl:template match="//record/*[starts-with(name(), 'MBH')]">
    <Card>
        <xsl:copy/>
    </Card> 
</xsl:template>

</xsl:stylesheet>

But that only gives me this output:

<?xml version="1.0" encoding="UTF-8"?>
<list>
<Card>
    <MBH1/>
</Card>
<Card>
    <MBH2/>
</Card>
<Card>
    <MBH3/>
</Card>
</list>

It is easy to uniquely identify the <MBH> elements and list them. I have tried to also work with a key that matches //record/*[not(starts-with(name(), 'MBH'))] but that of course selects all of the other records not related to the previous <MBH>. Is it even possible to get the wanted output with that structure? How could XSL know the (varying) amount of elements to the next <MBH> element?

1

1 Answers

2
votes

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kByNameSuf" match="record/*"
  use="translate(name(), translate(name(),'0123456789',''),'')"/>

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

 <xsl:template match=
  "record[generate-id(*[1])
         =
          generate-id(key('kByNameSuf',
                           translate(name(*[1]),
                                     translate(name(*[1]),'0123456789',''),'')
                          )[1]
                      )
         ]
  ">
  <Card>
    <xsl:for-each select=
     "key('kByNameSuf',
          translate(name(*[1]),
                    translate(name(*[1]),'0123456789',''),'')
                    )
     ">
       <Data><xsl:value-of select="name()"/></Data>
    </xsl:for-each>
  </Card>
 </xsl:template>
 <xsl:template match="record"/>
</xsl:stylesheet>

when applied on the provided XML document:

<GenericRecs>
<GenericRecord>
    <record>
        <MBH1/>
    </record>
    <record>
        <BAL1/>
    </record>
    <record>
        <MBH2/>
    </record>
    <record>
        <BAL2/>
    </record>
    <record>
        <PAY2/>
    </record>
    <record>
        <MBH3/>
    </record>
    <record>
        <BAL3/>
    </record>
    <record>
        <PAY3/>
    </record>
</GenericRecord>
</GenericRecs>

produces the wanted, correct result:

<GenericRecs>
   <GenericRecord>
      <Card>
         <Data>MBH1</Data>
         <Data>BAL1</Data>
      </Card>
      <Card>
         <Data>MBH2</Data>
         <Data>BAL2</Data>
         <Data>PAY2</Data>
      </Card>
      <Card>
         <Data>MBH3</Data>
         <Data>BAL3</Data>
         <Data>PAY3</Data>
      </Card>
   </GenericRecord>
</GenericRecs>

Explanation:

  1. Use of Muenchian Grouping.

  2. Assumes that there are digits only in the suffix of a name.

  3. Use of the double translate method (first proposed by Michael Kay).


II. XSLT 2.0 solution:

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

 <xsl:template match="GenericRecord">
     <xsl:for-each-group select="*"
        group-by="replace(name(*[1]), '^.*(\d+)$', '$1')">
      <Card>
          <xsl:for-each select="current-group()">
            <Data><xsl:value-of select="name(*[1])"/></Data>
          </xsl:for-each>
      </Card>
     </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>