How to loop through xml nodes and recreate the xml structure

Viewed 49

I have this XML node:

<partyIdentification>
                <id>123</id>
                <schemeId>1111</schemeId>
                <!--GlobalIDs start from here-->
                <id>1234567</id>
                <schemeId>0011</schemeId>
                <id>7654321</id>
                <schemeId>0022</schemeId>
                <id>98765432</id>
                <schemeId>0033</schemeId>
                <id>13680123</id>
                <schemeId>0044</schemeId>
            </partyIdentification>

I need to loop through these and recreate the structure like this:

<partyIdentification>
                <ID sid="1111">123</id>
                <GlobalID sid="0011">1234567</GlobalID>
                <GlobalID sid="0022">7654321</GlobalID>
                <GlobalID sid="0033">98765432</GlobalID>
                <GlobalID sid="0044">13680123</GlobalID>
</partyIdentification>

Please note: id and schemeId are linked to each other. So, if there is a schemeID after id, then those are grouped together, as you can see in expected result. So, anything which comes before the comment: "GlobalIDs start from here" should be added to the field <ID and in attribute sid, there can be multiple before this comment. And, whatever comes after the comment "GlobalIDs start from here" should go in <GlobalID and in sid attribute.

I tried many things, for example exsl:node-set and also preceeding and following sibling:

<xsl:template match="partyIdentification/id[following-sibling::comment()[1]]">
        <xsl:variable name="IDs">
            <xsl:copy-of select="."/>
        </xsl:variable>
        <xsl:variable name="IDsRestructuring">
            <xsl:for-each select="$IDs/id">
                <id sid="{following-sibling::schemeId[1]}"><xsl:value-of select="normalize-space(.)"/></id>
            </xsl:for-each>
        </xsl:variable>
        <xsl:for-each select="exsl:node-set($IDsRestructuring)/id">
        <ID schemeID="{@sid}"><xsl:value-of select="."/></ID>
        </xsl:for-each>
    </xsl:template>

Can someone please help?

2 Answers

This may help you. In your desired output your beginning and ending tags do not match. But this does.

<xsl:template match="partyIdentification">
  <xsl:copy>
    <!-- Select the schemeId before the coment. -->
    <xsl:apply-templates select="comment()/preceding-sibling::schemeId" mode="FirstschemeId"/>
    <!-- Select the schemeId after the coment. -->
    <xsl:apply-templates select="comment()/following-sibling::schemeId"/>  
  </xsl:copy>
</xsl:template>

<!-- Use mode for the first schemeID -->
<xsl:template match="schemeId" mode="FirstschemeId">
  <xsl:element name="ID">
    <xsl:attribute name="sid">
      <xsl:value-of select="."/>
    </xsl:attribute>
    <xsl:value-of select="preceding-sibling::id[1]"/>
  </xsl:element>
</xsl:template>

<!-- Do not use mode for after the first schemeID. -->
<xsl:template match="schemeId">
  <xsl:element name="GlobalID">
    <xsl:attribute name="sid">
      <xsl:value-of select="."/>
    </xsl:attribute>
    <xsl:value-of select="preceding-sibling::id[1]"/>
  </xsl:element>
</xsl:template>

As an alternative for the answer of John Ernst

You could also use this:

<?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:template match="partyIdentification">
    <!-- Since we need to use the comment more than once, lets put in a variable -->
    <xsl:variable name="commentSplitter" select="comment()[.='GlobalIDs start from here']"/>
    <xsl:copy>
      <!-- Select all preceding-siblings with the explicit elemName: `ID` -->
      <xsl:apply-templates select="$commentSplitter/preceding-sibling::schemeId">
        <xsl:with-param name="elemName" select="'ID'"/>
      </xsl:apply-templates>
      <!-- Select all following-siblings without a explicit elemName and use the default in that template -->
      <xsl:apply-templates select="$commentSplitter/following-sibling::schemeId"/>
    </xsl:copy>
  </xsl:template>  
  
  <!-- Just one template dealing with both element-names, to reduce redundant coding -->
  <xsl:template match="schemeId">
    <xsl:param name="elemName" select="'GlobalID'"/>
    <xsl:element name="{$elemName}">
      <xsl:attribute name="sid">
        <xsl:value-of select="."/>
      </xsl:attribute>
      <xsl:value-of select="preceding-sibling::id[1]"/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>
Related