Need help in forming target xml using XSLT

2019-06-11 07:51发布

问题:

I am facing difficult while creating child elements to parent node using XSLT , as the format of the input message coming from external source is bit different. requesting you kindly help on the XSLT code. Please also let me know if i can optimize the existing xslt copied below. Input Message coming from external source:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
    <attr attr-name="A">
        <new-value>adi00010210</new-value>
    </attr>
    <attr attr-name="B">
        <new-value>99</new-value>
    </attr>
    <attr attr-name="C">
        <new-value>active</new-value>
    </attr>
    <attr attr-name="D">
        <new-value>
            <child1>iop00010538</child1>
            <child2>2</child2>
        </new-value>
        <new-value>
            <child1>cid2313213</child1>
            <child2>2</child2>
        </new-value>
        <new-value>
            <child1>hri00075562</child1>
            <child2>1</child2>
        </new-value>
    </attr>
    <attr attr-name="E">
        <new-value>
            <child3>spi00010021</child3>
            <child4>1</child4>
        </new-value>
        <new-value>
            <child3>vuh000123</child3>
            <child4>1</child4>
        </new-value>
    </attr>
</PurchaseOrder>

XSLT Code written to transform The XSLT code also covers separation of values with | symbol if values are coming from same source multiple times.

<?xml version="1.0" encoding="UTF-8"?>
<ns0:stylesheet version="2.0" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
   <ns0:template match="/">
      <ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
         <Orders>
            <Order>
               <ns0:attribute name="id" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
                  <ns0:value-of select="/*/@id"/>
               </ns0:attribute>
               <ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
                  <ns0:with-param name="Attr_value">A</ns0:with-param>
                  <ns0:with-param name="New_Attr">A</ns0:with-param>
               </ns0:call-template>
               <ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
                  <ns0:with-param name="Attr_value">B</ns0:with-param>
                  <ns0:with-param name="New_Attr">B</ns0:with-param>
               </ns0:call-template>
               <ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
                  <ns0:with-param name="Attr_value">C</ns0:with-param>
                  <ns0:with-param name="New_Attr">C</ns0:with-param>
               </ns0:call-template>
            </Order>
         </Orders>
      </ns1:PurchaseOrderMSG>
   </ns0:template>
   <ns0:template name="copy_attr">
      <ns0:param name="Attr_value"/>
      <ns0:param name="New_Attr" select="$Attr_value"/>
      <ns0:param name="length" select="100000"/>
      <ns0:param name="values">
         <ns0:for-each select="//attr[@attr-name = $Attr_value]/new-value">
            <ns0:if test="position()!=1">
               <ns0:text>|</ns0:text>
            </ns0:if>
            <ns0:value-of select="."/>
         </ns0:for-each>
      </ns0:param>
      <ns0:element name="{$New_Attr}">
         <ns0:value-of select="substring($values,1,number($length))"/>
      </ns0:element>
   </ns0:template>
</ns0:stylesheet>

I am facing difficult write code to add child1/child2 (both child can be repeated multiple times) field to parentnode D (occurance 0 to unbounded) and child3/child4 to parentnode E respectively.

Expected Result:

<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
   <Orders>
      <Order>
         <A/>
         <B/>
         <C/>
         <D>
            <Child1/>
            <Child2/>
         </D>
         <E>
            <Child3/>
            <Child4/>
         </E>
      </Order>
   </Orders>
</ns0:PurchaseOrderMSG>
please find the changed input..I have retained old input as well
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
    <attr attr-name="A">
        <new-value>adi00010210</new-value>
    </attr>
    <attr attr-name="B">
        <new-value>99</new-value>
    </attr>
        <attr attr-name="B">
        <new-value>101</new-value>
    </attr>
    <attr attr-name="C">
        <new-value>active</new-value>
    </attr>
    <attr attr-name="D">
        <new-value>
            <child1>iop00010538</child1>
            <child2>2</child2>
        </new-value>
        <new-value>
            <child1>cid2313213</child1>
            <child2>2</child2>
        </new-value>
        <new-value>
            <child1>hri00075562</child1>
            <child2>1</child2>
        </new-value>
    </attr>
    <attr attr-name="E">
        <new-value>
            <child3>spi00010021</child3>
            <child4>1</child4>
        </new-value>
        <new-value>
            <child3>vuh000123</child3>
            <child4>1</child4>
        </new-value>
    </attr>
    <attr attr-name="C">
        <new-value>inactive</new-value>
    </attr>
</PurchaseOrder>
Please find the expected output
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
   <Orders>
      <Order>
         <A>adi00010210</A>
         <B>99|101</B>
         <C>active|inactive</C>
         <D>
            <Child1>iop00010538</Child1>
            <Child2>2</Child2>
         </D>
         <D>
            <child1>cid2313213</child1>
            <child2>2</child2>
        </D>
        <D>
            <child1>hri00075562</child1>
            <child2>1</child2>
         </D>
         <E>
            <Child3>spi00010021</Child3>
            <Child4>1</Child4>
         </E>
         <E>
            <child3>vuh000123</child3>
            <child4>1</child4>
         </E>
      </Order>
   </Orders>
</ns0:PurchaseOrderMSG>

回答1:

Before we go into the solution, I would like to suggest one thing about retaining the prefix of namespace http://www.w3.org/1999/XSL/Transform as xsl instead of ns0 since it is widely accepted and easier to understand. However it is not mandatory and a personal choice. The solution below uses xsl as the prefix.

To start with, we need to prepare a list of nodes based on the value of attr-name of <attr> elements. This can be achieved by using <xsl:for-each> on the <attr> element and using attribute value templates {} for the element name.

<xsl:for-each select="attr">
    <xsl:element name="{@attr-name}">
    ....
    </xsl:element>
</xsl:for-each>

Next is the grouping of the values for the parent node and separating them using | separator. This can be achieved by defining <xsl:key> in XSLT 1.0).

 <xsl:key name="keyAttrName" match="attr" use="@attr-name" />

If using XSLT 2.0, <xsl:for-each-group> can be used.

<xsl:for-each-group select="attr" group-by="@attr-name">

XSLT 1.0 solution

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:key name="keyAttrName" match="attr" use="@attr-name" />

    <xsl:template match="PurchaseOrder">
        <ns1:PurchaseOrderMSG>
            <Orders>
                <Order id="{@id}">
                    <xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', @attr-name)[1])]">
                        <xsl:variable name="nodeName" select="@attr-name" />
                        <xsl:choose>
                            <xsl:when test="key('keyAttrName', @attr-name)/new-value/*/node()">
                                <xsl:for-each select="new-value">
                                    <xsl:element name="{$nodeName}">
                                        <xsl:copy-of select="*" />
                                    </xsl:element>
                                </xsl:for-each>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:element name="{$nodeName}">
                                    <xsl:for-each select="key('keyAttrName', @attr-name)">
                                        <xsl:value-of select="new-value" />
                                            <xsl:if test="position() != last()">
                                            <xsl:value-of select="'|'" />
                                        </xsl:if>
                                    </xsl:for-each>
                                </xsl:element>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each>
                </Order>
            </Orders>
        </ns1:PurchaseOrderMSG>
    </xsl:template>
</xsl:stylesheet>

XSLT 2.0 solution

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:template match="PurchaseOrder">
        <ns1:PurchaseOrderMSG>
            <Orders>
                <Order id="{@id}">
                    <xsl:for-each-group select="attr" group-by="@attr-name">
                        <xsl:choose>
                            <xsl:when test="current-group()/new-value/*/node()">
                                <xsl:for-each select="current-group()/new-value">
                                    <xsl:element name="{current-grouping-key()}">
                                        <xsl:copy-of select="*" />
                                    </xsl:element>
                                </xsl:for-each>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:element name="{current-grouping-key()}">
                                    <xsl:value-of select="current-group()/new-value" separator="|" />
                                </xsl:element>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </Order>
            </Orders>
        </ns1:PurchaseOrderMSG>
    </xsl:template>
</xsl:stylesheet>

Both solutions transform the updated input XML in the output shown below.

<ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
   <Orders>
      <Order id="aoi00037607">
         <A>adi00010210</A>
         <B>99|101</B>
         <C>active|inactive</C>
         <D>
            <child1>iop00010538</child1>
            <child2>2</child2>
         </D>
         <D>
            <child1>cid2313213</child1>
            <child2>2</child2>
         </D>
         <D>
            <child1>hri00075562</child1>
            <child2>1</child2>
         </D>
         <E>
            <child3>spi00010021</child3>
            <child4>1</child4>
         </E>
         <E>
            <child3>vuh000123</child3>
            <child4>1</child4>
         </E>
      </Order>
   </Orders>
</ns1:PurchaseOrderMSG>


标签: xslt