Search code examples
xslt-1.0xslt-2.0

XSLT: Convert Name/Value pair and transform an XML


I need to convert a name value pair into XML. I'm able to generate an XML, but the element name should be grouped and it should not be duplicated. Please see below. The FieldValue element contains 2 OrderItem values in the Detail node. If the FieldValue with OrderItem repeats, then the result should be grouped into one OrderItem node. Please help.

Source XML:

<SC>
	<Header>
		<Record>
			<FieldName>Schema</FieldName>
			<FieldValue>OrderHeader</FieldValue>
		</Record>
		<Record>
			<FieldName>Order</FieldName>
			<FieldValue>1234</FieldValue>
		</Record>
	</Header>
	<Detail>
		<Record>
			<FieldName>Schema</FieldName>
			<FieldValue>OrderItem</FieldValue>
		</Record>
		<Record>
			<FieldName>Item</FieldName>
			<FieldValue>1</FieldValue>
		</Record>
		<Record>
			<FieldName>Qty</FieldName>
			<FieldValue>10</FieldValue>
		</Record>
	</Detail>
	<Detail>
		<Record>
			<FieldName>Schema</FieldName>
			<FieldValue>OrderItem</FieldValue>
		</Record>
		<Record>
			<FieldName>Item</FieldName>
			<FieldValue>2</FieldValue>
		</Record>
		<Record>
			<FieldName>Qty</FieldName>
			<FieldValue>20</FieldValue>
		</Record>
	</Detail>
</SC>

Target XML:

<Order>
 <OrderItem>
   <Item>
      <Item>1</Item>
      <Qty>10</Qty>
   </Item>
   <Item>
      <Item>2</Item>
      <Qty>20</Qty>
   </Item>
 </OrderItem>
</Order>

XSLT:

<xsl:template match="@*|node()">
<Order>
    <xsl:for-each select="Detail">
        <Item>
            <xsl:apply-templates select="Record[position()>1]"/>
        </Item>
</xsl:for-each>
</Order>
</xsl:template>

<xsl:template match="Record">
    <xsl:element name="{FieldName}">
        <xsl:value-of select="FieldValue"/>
    </xsl:element>
</xsl:template>

Solution

  • The grouping can be done as follows:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="xs"
        version="2.0">
    
        <xsl:output indent="yes"/>
    
        <xsl:template match="SC">
            <Order>
                <xsl:for-each-group select="Detail" group-by="Record[1]/FieldValue">
                    <xsl:element name="{current-grouping-key()}">
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:element>
                </xsl:for-each-group>
            </Order>
        </xsl:template>
    
        <xsl:template match="Detail">
            <Item>
                <xsl:apply-templates select="Record[position() gt 1]"/>
            </Item>
        </xsl:template>
    
        <xsl:template match="Record">
            <xsl:element name="{FieldName}">
                <xsl:value-of select="FieldValue"/>
            </xsl:element>
        </xsl:template>
    
    </xsl:stylesheet>