raffian
raffian

Reputation: 32086

Copy child nodes and change element names with XSL

I have this source XML from a vendor:

Source XML

<?xml version="1.0" encoding="UTF-8"?>
<src:view name="books"
    xmlns="xyz.com/wow"
    xmlns:src="xyz.com/fun" 
    xmlns:xsi="w3.org/2001/XMLSchema-instance">
    <books>
        <title>Searching for Answers</title>
    </books>
    <books>
        <title>Get Rich Quick</title>   
    </books>
    <books>
        <title>Negotiating with the Grim Reaper</title> 
    </books>    
</src:view>

I want to transform into a new document with root element books, then copy all books elements but change the name from books to books_record. Documents will be arbitrary,(books today, widgets tomorrow, etc), but the element name for the root's children is always available from /@name, that's the vendors convention.

Intended Output

<books>
   <books_record>
      <title>Searching for Answers</title>
   </books_record>
   <books_record>
      <title>Get Rich Quick</title>
   </books_record>
   <books_record>
      <title>Negotiating with the Grim Reaper</title>
   </books_record>
</books>

I managed the get right the root element and copying of all child elements, but I'm not sure how to change all <books> to <books_record>. Also, is what I have so far efficient and valid? I know it works, just not 100% sure if it's the best approach.

XSL so far (Updated with help from michael.hor257k)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xsl" version="2.0">
    <xsl:output method="xml" version="1.0" encoding="UTF-8"
        indent="yes" />
    <xsl:strip-space elements="*" />

<xsl:template match="*">
    <xsl:element name="{local-name()}">
        <xsl:copy-of select="@*" />
        <xsl:apply-templates />
    </xsl:element>
</xsl:template>

<xsl:template match="/*">
    <xsl:variable name="rootElementName">
        <xsl:value-of select="@name" />
    </xsl:variable>     
    
    <xsl:element name="{$rootElementName}">      
            <xsl:apply-templates /> 
    </xsl:element>       
  
</xsl:template>

<xsl:template match="/*/node()">
    <xsl:variable name="rootElementName">
        <xsl:value-of select="/*/@name" />
    </xsl:variable>

    <xsl:element name="{$rootElementName}_record">      
            <xsl:apply-templates /> 
    </xsl:element>          
</xsl:template>
</xsl:transform>

Upvotes: 0

Views: 2857

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 117140

I believe you could simplify this to:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="*">
    <xsl:element name="{local-name()}">
        <xsl:copy-of select="@*" />
        <xsl:apply-templates />
    </xsl:element>
</xsl:template>

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

<xsl:template match="*[local-name()='books']">
    <books_record>
        <xsl:apply-templates />
    </books_record>
</xsl:template>

</xsl:transform>

Of course, if you know the expected format of the input, you can get more explicit - thus both shorter and more efficient, for example:

<xsl:transform version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:src="xyz.com/fun" 
xmlns:wow="xyz.com/wow"
exclude-result-prefixes="src wow">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />

<xsl:template match="/">
    <books>
        <xsl:for-each select="src:view/wow:books">
            <books_record>
                <title>
                    <xsl:value-of select="wow:title"/>
                </title>
            </books_record>
        </xsl:for-each>
    </books>
</xsl:template> 

</xsl:transform>

Edit:

If I understand correctly what you mean by "generic", then:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />

<xsl:variable name="tableName" select="/*/@name" />

<xsl:template match="*">
    <xsl:element name="{local-name()}">
        <xsl:copy-of select="@*" />
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

<xsl:template match="/*">
    <xsl:element name="{$tableName}">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

<xsl:template match="*/*">
     <xsl:element name="{$tableName}_record">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

</xsl:transform>

Note:

  1. Variable defined only once;
  2. This part is not necessary:
    exclude-result-prefixes="xsl"

Upvotes: 2

Related Questions