Emil Tinebo
Emil Tinebo

Reputation: 15

XSLT Split children into new nodes

I need to transform the structure of an XML file and figured XSLT might be the best solution (that's what it's for right?) I have some experience with XSLT but it is slightly limited so I need some guidance.

The scenario is as follows; I have an XML containing one or more records. Each record has a set of fields and each field has a set of sub fields. The order of the sub fields is significant. What needs to be done is that the input structure needs to be transformed so that each occurrence of sub field codes A and K should result in a new field (with a new code and sub field K renamed to A) in the output with the sub fields that follows either sub field A or K, as the example below illustrates. The number of sub fields K is arbitrary and may differ from record to record the the xslt need to be slightly general.

Here is my input XML:

    <?xml version="1.0" encoding="UTF-8" ?>
    <record>
      <field code="123">
         <subfield code="A">Abc</subfield>
         <subfield code="B">De</subfield>
         <subfield code="K">Fgh</subfield>
         <subfield code="C">IJ</subfield>
         <subfield code="K">Klmn</subfield>
         <subfield code="D">OP</subfield>
     </field>
     <field>... more datafields... </field>
   </record>

The desired output is as follows:

    <?xml version="1.0" encoding="UTF-8" ?>
    <record>
      <field code="124">
         <subfield code="A">Abc</subfield>
         <subfield code="B">De</subfield>   
     </field>
     <field code="124">
         <subfield code="A">Fgh</subfield>
         <subfield code="C">IJ</subfield>
     </field>
     <field code="124">
         <subfield code="A">Klmn</subfield>
         <subfield code="D">OP</subfield>
     </field>
     <field>... more datafields... </field>
   </record>

If anyone can provide me with some sample xslt that at least points me in the right direction I would me much obliged.

EDIT: Just to clarify. There can be any number of sub fields between the A and K sub fields.

Upvotes: 0

Views: 301

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167716

Assuming an XSLT 2.0 processor like Saxon 9 or Altova or XmlPrime you can use

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

<xsl:strip-space elements="*"/>

<xsl:output indent="yes"/>

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

<xsl:template match="field">
  <xsl:for-each-group select="subfield" group-starting-with="subfield[@code = ('A', 'K')]">
    <field code="124">
      <xsl:apply-templates select="current-group()"/>
    </field>
  </xsl:for-each-group>
</xsl:template>

<xsl:template match="subfield/@code[. = 'K']">
  <xsl:attribute name="{name()}" select="'A'"/>
</xsl:template>

</xsl:stylesheet>

which transforms

<?xml version="1.0" encoding="UTF-8" ?>
    <record>
      <field code="123">
         <subfield code="A">Abc</subfield>
         <subfield code="B">De</subfield>
         <subfield code="K">Fgh</subfield>
         <subfield code="C">IJ</subfield>
         <subfield code="K">Klmn</subfield>
         <subfield code="D">OP</subfield>
     </field>
     <field>... more datafields... </field>
   </record>

into

<record>
   <field code="124">
      <subfield code="A">Abc</subfield>
      <subfield code="B">De</subfield>
   </field>
   <field code="124">
      <subfield code="A">Fgh</subfield>
      <subfield code="C">IJ</subfield>
   </field>
   <field code="124">
      <subfield code="A">Klmn</subfield>
      <subfield code="D">OP</subfield>
   </field>
</record>

If you need an XSLT 1.0 solution then

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

<xsl:strip-space elements="*"/>

<xsl:output indent="yes"/>

<xsl:key name="sub" match="subfield[not(@code = 'A' or @code = 'K')]"
  use="generate-id(preceding-sibling::subfield[@code = 'A' or @code = 'K'][1])"/>

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

<xsl:template match="field">
  <xsl:apply-templates select="subfield[@code = 'A' or @code = 'K']" mode="group"/>
</xsl:template>

<xsl:template match="subfield[@code = 'A' or @code = 'K']" mode="group">
  <field code="124">
    <xsl:apply-templates select=". | key('sub', generate-id())"/>
  </field>
</xsl:template>

<xsl:template match="subfield/@code[. = 'K']">
  <xsl:attribute name="{name()}">A</xsl:attribute>
</xsl:template>

</xsl:stylesheet>

should do.

Upvotes: 1

Nils Werner
Nils Werner

Reputation: 36815

<?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" indent="yes"/>
  <xsl:strip-space elements="*"/> 

<xsl:template match="subfield">
    <field code="{../@code}">
        <xsl:copy-of select="." />
        <xsl:copy-of select="following-sibling::subfield[1]" />
    </field>
</xsl:template>

<xsl:template match="record">
    <xsl:apply-templates select="//subfield[position() mod 2 = 1]" />
</xsl:template>

<xsl:template match="/">
    <xsl:apply-templates select="record" />
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions