user2967948
user2967948

Reputation: 549

Renaming Tags via XSLT for Similar Tag Pattern

I have an xml as:

<FlightDetails1>
                    <CouponNumber1>1</CouponNumber1>
            <ServiceClass1>Y</ServiceClass1>
</FlightDetails1>
<FlightDetails2>
                        <CouponNumber2>2</CouponNumber2>
                        <ServiceClass2>Y</ServiceClass2>
</FlightDetails2>
<FlightDetails3>
                        <CouponNumber3></CouponNumber3>
                        <ServiceClass3>N</ServiceClass3>
</FlightDetails3>

Need to transform this xml to the below format:

<FlightDetails>
                        <CouponNumber>1</CouponNumber>
            <ServiceClass>Y</ServiceClass>
</FlightDetails>
<FlightDetails>
                        <CouponNumber>2</CouponNumber>
                        <ServiceClass>Y</ServiceClass>
</FlightDetails>
<FlightDetails>
                        <CouponNumber></CouponNumber>
                        <ServiceClass>N</ServiceClass>
</FlightDetails>

Previously, when the tags were like <FlightDetails> and <CouponNumber>, I used the 'copy-of' function in the XSLT. With the tags being renamed, what is the simplest way to achieve that with an xslt? XSLT:

<xsl:output indent="yes"/>
          <xsl:template match="/">
          <xsl:copy-of select="//FlightDetails1"/>
          </xsl:template>

Upvotes: 1

Views: 106

Answers (2)

Tomalak
Tomalak

Reputation: 338316

Here's a generic version

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

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

  <!-- elements whose names end with digits -->
  <xsl:template match="*[
     starts-with(name(), translate(name(), '01234567890', ''))
     and substring-after(name(), translate(name(), '01234567890', ''))
  ]">
    <xsl:element name="{translate(name(), '01234567890', '')}">
      <xsl:apply-templates select="node() | @*" />
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

The second template matches all elements that end in digits by...

  1. removing all digits from the name

    translate(name(), '01234567890', '') := 'FlightDetails1' -> 'FlightDetails'
    
  2. making sure it does not match elements with digits in the middle

    starts-with('FlightDetails1', 'FlightDetails')     := true
    starts-with('Flight1Details', 'FlightDetails')     := false
    
  3. checking there is something after the start of the string (by definition this can only be one or more digits, all other elements already fail the previous test)

    substring-after('FlightDetails1', 'FlightDetails') := '1' (evaluates to true)
    

    This way the template matches any element with a name in the form letters123.

  4. <xsl:element name="{translate(name(), '01234567890', '')}"> then re-creates that element without the trailing digit.

Upvotes: 1

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

If you do not want the element names to persist, you can no longer use copy (or copy-of, the way you are using it).

Instead, match these elements in a separate template, for instance like this:

<xsl:template match="*[starts-with(name(),'FlightDetails')]">
  <xsl:element name="FlightDetails">
  <!--Further processing-->
  </xsl:element>
</xsl:template>

This template matches all forms of FlightDetails elements, that is to say, regardless of the number at the end of the element name. Then, with the xsl:element instruction you create a new element with the name "FlightDetails".

Then, go on to write similar templates for the other elements you wish to normalize. Note that now apply-templates/ has come into play to leave it to the processor to decide which template to apply next.

A stylesheet that summarizes all this (I added a root element to your input XMl to make it well-formed):

<?xml version="1.0" encoding="utf-8"?>

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

<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="*[starts-with(name(),'FlightDetails')]">
  <xsl:element name="FlightDetails">
     <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

<xsl:template match="*[starts-with(name(),'CouponNumber')]">
  <xsl:element name="CouponNumber">
     <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

<xsl:template match="*[starts-with(name(),'ServiceClass')]">
  <xsl:element name="ServiceClass">
     <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

<xsl:template match="text()">
  <xsl:copy/>
</xsl:template>

</xsl:stylesheet>

Gives the following output:

<?xml version="1.0" encoding="UTF-8"?>
<FlightDetails>
  <CouponNumber>1</CouponNumber>
  <ServiceClass>Y</ServiceClass>
</FlightDetails>
<FlightDetails>
  <CouponNumber>2</CouponNumber>
  <ServiceClass>Y</ServiceClass>
</FlightDetails>
<FlightDetails>
  <CouponNumber/>
  <ServiceClass>N</ServiceClass>
</FlightDetails>

Upvotes: 0

Related Questions