sabyasachi
sabyasachi

Reputation: 15

XSLT mapping to add missing child tags in the same sequence

I have the below input xml where some mandatory xml tags are missing like <email_template_id> which needs to be reproduced in the output in the same sequence

<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
<result>
<total_results>700</total_results>
<emailClick>
<id>1</id>
<url>https://www.google.com</url>
<email_template_id>12</email_template_id>
<created_at>2023-02-14 00:00:10</created_at>
</emailClick>
<emailClick>
<id>2</id>
<url>https://www.hello.com</url>
<created_at>2023-02-14 00:00:10</created_at>
</emailClick>
</result>
</rsp>

and need the below output

<emailClick>
<id>1</id>
<url>https://www.google.com</url>
<email_template_id>12</email_template_id>
<created_at>2023-02-14 00:00:10</created_at>
</emailClick>
<emailClick>
<id>2</id>
<url>https://www.hello.com</url>
<email_template_id/>
<created_at>2023-02-14 00:00:10</created_at>
</emailClick>

I am using below xslt to achieve this output

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent ="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="rsp/result/emailClick">
<emailClick>
<id><xsl:value-of select="id" /></id>
<url><xsl:value-of select="url" /></url>
<email_template_id><xsl:value-of select="email_template_id"/></email_template_id>
<created_at><xsl:value-of select="created_at"/></created_at>
</emailClick>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Is there any better way to achieve this , any guidance would be highly appreciated

Upvotes: 0

Views: 120

Answers (2)

John Ernst
John Ernst

Reputation: 1235

This is a push programming approach. Which means it pushes the child nodes using apply-templates. It also uses a mode. So the nodes being output are pushed to an identity template.

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

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

<xsl:template match="emailClick">
  <xsl:copy>
    <xsl:apply-templates select="@*" mode="output"/>
    <xsl:choose>
      <xsl:when test="id">
        <xsl:apply-templates select="id" mode="output"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="id"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="url">
        <xsl:apply-templates select="url" mode="output"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="url"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="email_template_id">
        <xsl:apply-templates select="email_template_id" mode="output"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="email_template_id"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="created_at">
        <xsl:apply-templates select="created_at" mode="output"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:element name="created_at"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:copy>
</xsl:template>

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

</xsl:stylesheet>

Upvotes: 0

Heiko Thei&#223;en
Heiko Thei&#223;en

Reputation: 17507

The following stylesheet avoids any mention of element names inside the xsl:for-each and instead reads the mandatory element names from the <so:emailClick> element and processes them generically:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:so="https://stackoverflow.com">
  <xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="yes" />
  <xsl:strip-space elements="*" />
  <so:emailClick>
    <id />
    <url />
    <email_template_id />
    <created_at />
  </so:emailClick>
  <xsl:template match="/">
    <xsl:for-each select="rsp/result/emailClick">
      <xsl:variable name="this" select="." />
      <xsl:copy>
        <xsl:for-each select="document('')/*/so:*[local-name()=name(current())]/*">
          <xsl:element name="{name()}">
            <xsl:value-of select="$this/*[name()=name(current())]" />
          </xsl:element>
        </xsl:for-each>
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Is that what you meant by "a generic way of doing this"?

(XSLT 1.0)

Upvotes: 0

Related Questions