-2

I have a grouping issue where i need to group all the keys that has the same number under a node. For e.g., my XML looks like :

<results>
   <status>completed</status>
   <info>success</info>
   <prod1>abc</prod1>
   <pub1>test</pub1>
   <sub1>123</sub1>
   <subtype1>pt</subtype1>
   <prod2>def</prod2>
   <pub2>test22</pub2>
   <sub2>456</sub2>
   <subtype2>pt</subtype2>
   <prod3>ghi</prod3>
   <pub3>test33</pub3>
   <sub3>789</sub3>
   <subtype3>pt</subtype3>
</results>

I need to convert the above into:

<results>
   <status>completed</status>
   <info>success</info>
   <products>
      <product>
         <prod>abc</prod>
         <pub>test</pub>
         <sub>123</sub>
         <subtype>pt</subtype>
       </product>
       <product>
         <prod>def</prod>
         <pub>test22</pub>
         <sub>456</sub>
         <subtype>pt</subtype>
       </product>
       <product>
         <prod>ghi</prod>
         <pub>test33</pub>
         <sub>789</sub>
         <subtype>pt</subtype>
       </product>
    </products>
</results>

Any help in resolving the above is highly appreciated. I am currently stuck with this issue and not able to proceed.

The below xslt pulls each element and puts into a node and i m not able to group all elements that ends with a particular number into a single node.

<?xml version="1.0" encoding="UTF-8"?>
   <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:key name="elementByRow" match="/*/*"
           use="(name(.)[1])" />

   <xsl:template match="/messages">
    <messages>
      <!-- pick out the first RowN.* element for each N -->
      <xsl:apply-templates select="*[generate-id() =
         generate-id(key('elementByRow', name(.))[1])]" />
    </messages>
   </xsl:template>

   <xsl:template match="*">
    <row>
      <!-- process _all_ the elements that belong to this row -->
      <xsl:for-each select="key('elementByRow', name(.))[1]">
        <xsl:element name="{name(.)[1]}">
          <xsl:value-of select="." />
        </xsl:element>
      </xsl:for-each>
     </row>
    </xsl:template>
   </xsl:stylesheet>
Ram
  • 3
  • 2
  • On Stack Overflow, you are expected to try to **write the code yourself**. After **[doing more research](//meta.stackoverflow.com/questions/261592)** if you have a problem you can **post what you've tried** with a **clear explanation of what isn't working** and providing a **[Minimal, Complete, and Verifiable example](//stackoverflow.com/help/mcve)** within the question itself. – Rob Jan 18 '19 at 04:09
  • This is not going to be simple. Is there anything we know about the input structure that could be used to simplify the problem? For example, will there always be 4 elements per product? Or will there at least be always the same number of elements per product, and in the same order? Or do we at least know the names (prod, pub, sub and subtype) of the elements that can be used to describe a product? – michael.hor257k Jan 18 '19 at 06:36
  • BTW, if you have a way to persuade the data provider to change their structure to something reasonable, you should definitely try it. Because they are playing a cruel joke on you. – michael.hor257k Jan 18 '19 at 06:40
  • Yes, the elements will remain the same and the keys will be numbered in the ascending order for each product as shown in the above example. The input structure pretty much remains the same except that we do not know the number of products that will be present in the response. – Ram Jan 18 '19 at 06:43
  • @michael - we are interacting with a legacy system and they are not going to change the response. So i will have to process the above response and then convert it to POJO. – Ram Jan 18 '19 at 06:44
  • @Rob - i m currently stuck at writing the xslt which is why i have asked this question. I have tried different ways and all i could come up with is the below xslt where this converts each element under a node rather than grouping the elements that ends with the same number into a particular node. please see the below xslt attached to the question – Ram Jan 18 '19 at 06:52
  • The XSLT you have borrowed from [another question](https://stackoverflow.com/a/19818293/3016153) is completely irrelevant here. If you had a naming scheme where each element's name had the same "RowX." prefix, this would be a lot simpler. – michael.hor257k Jan 18 '19 at 07:01

2 Answers2

0

If the input structure is constant, I would take advantage of that, instead of trying to group by the trailing number.

For example, you could do:

XSLT 1.0

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

<xsl:template match="/results">
    <xsl:copy>
        <xsl:copy-of select="status | info"/>
        <products>
            <xsl:for-each select="*[starts-with(name(), 'prod')]">
                <product>
                    <prod>
                        <xsl:value-of select="." />
                    </prod>
                    <pub>
                        <xsl:value-of select="following-sibling::*[1]" />
                    </pub>
                    <sub>
                        <xsl:value-of select="following-sibling::*[2]" />
                    </sub>
                    <subtype>
                        <xsl:value-of select="following-sibling::*[3]" />
                    </subtype>
                </product>
            </xsl:for-each>
        </products>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
0

Here's a more elaborate approach that groups the elements by their number; note, however, that this assumes an element's name will not contain any digits other than the group number at the end.

XSLT 1.0

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

<xsl:key name="field-by-group" match="*" use="translate(name(), translate(name(), '0123456789', ''), '')" />

<xsl:template match="/results">
    <xsl:copy>
        <xsl:copy-of select="status | info"/>
        <products>
            <xsl:for-each select="*[starts-with(name(), 'prod')]">
                <product>
                    <xsl:for-each select="key('field-by-group', substring-after(name(), 'prod'))">
                        <xsl:element name="{translate(name(), '0123456789', '')}">
                            <xsl:value-of select="."/>
                        </xsl:element>
                    </xsl:for-each>
                </product>
            </xsl:for-each>
        </products>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51