Search code examples
arraysloopsxsltkeyrepeat

Parse an xml (created from a json) with many intertwined arrays using xslt


I would like to write an xslt parser to parse this xml, which has multiple inner arrays as it was created from a json payload. Any help is appreciated. I am open to use Xslt 1.0 - 3.0 as well.

<?xml version="1.0" encoding="UTF-8"?>
<dynamic>

    <customer_name>XYZ</customer_name>  
    <state>
        <results>
            <map xmlns="http://www.w3.org/2005/xpath-functions">
                <number key="totalCount">3</number>
                <array key="result">
                    <map>
                        <string key="metricId">(Metric1:splitBy():sort(value(auto,descending)):limit(20)):limit(100):names</string>
                        <number key="dataPointCountRatio">0.0193479</number>
                        <array key="data">
                            <map>
                                <array key="timestamps">
                                    <number>t1</number>
                                    <number>t2</number>
                                </array>
                                <array key="values">
                                    <number>a1</number>
                                    <number>a2</number>
                                </array>
                            </map>
                        </array>
                    </map>
                    <map>
                        <string key="metricId">(Metric2:splitBy():sort(value(auto,descending)):limit(20)):limit(100):names</string>
                        <number key="dataPointCountRatio">0.0187083</number>
                        <array key="data">
                            <map>
                                <array key="timestamps">
                                    <number>t1</number>
                                    <number>t2</number>
                                </array>
                                <array key="values">
                                    <number>b1</number>
                                    <number>b2</number>
                                </array>
                            </map>
                        </array>
                    </map>
                    <map>
                        <string key="metricId">(Metric3:splitBy():sort(value(auto,descending)):limit(20)):limit(100):names</string>
                        <number key="dataPointCountRatio">0.0217464</number>
                        <array key="data">
                            <map>
                                <array key="timestamps">
                                    <number>t1</number>
                                    <number>t2</number>
                                </array>
                                <array key="values">
                                    <number>c1</number>
                                    <number>c2</number>
                                </array>
                            </map>
                        </array>
                    </map>
                </array>
            </map>
        </results>
        <count>1</count>
    </state>
</dynamic>

and the Expected response is something like this , 2 payloads

<?xml version="1.0" encoding="UTF-8"?>
<json>
    <map xmlns="http://www.w3.org/2005/xpath-functions" key="_source">
        <string key="customer_name">XYZ</string>
        <string key="common_date">t1</string>
        <string key="metric1">a1</string>
        <string key="metric2">b1</string>
        <string key="metric3">c1</string>
        <string key="@timestamp">current timestamp</string>
    </map>
</json>


<?xml version="1.0" encoding="UTF-8"?>
<json>
    <map xmlns="http://www.w3.org/2005/xpath-functions" key="_source">
        <string key="customer_name">XYZ</string>
        <string key="common_date">t2</string>
        <string key="metric1">a2</string>
        <string key="metric2">b2</string>
        <string key="metric3">c2</string>
        <string key="@timestamp">current timestamp</string>
    </map>
</json>

Tried to work on transforms but got only the first element in the arrays. Unable to get further elements.


Solution

  • It is very difficult to understand what in your example is real and what is just an example. I am guessing (!) you want to do something like:

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:map="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="map">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/dynamic">
        <xsl:variable name="customer_name" select="customer_name" />
        <xsl:variable name="timestamps" select="descendant::map:array[@key='timestamps'][1]" />
        <xsl:variable name="value-arrays" select="//map:array[@key='values']" />
        <!-- a result for each value in timestamps -->
        <xsl:for-each select="$timestamps/map:number">
            <xsl:variable name="i" select="position()" />
            <result href="file_{$i}.xml">
                <json>
                    <map xmlns="http://www.w3.org/2005/xpath-functions" key="_source">
                        <string key="customer_name">
                            <xsl:value-of select="$customer_name"/>
                        </string>
                        <string key="common_date">
                            <xsl:value-of select="."/>
                        </string>
                        <!-- take the i-th value from each value array --> 
                        <xsl:for-each select="$value-arrays">
                            <xsl:variable name="j" select="position()" />
                            <string key="metric{$j}">
                                <xsl:value-of select="map:number[$i]"/>
                            </string>
                        </xsl:for-each>
                        <string key="@timestamp">
                            <xsl:value-of select="current-dateTime()"/>
                        </string>
                    </map>
                </json> 
            </result>
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Applied to your input example, this will return:

    Result

    <?xml version="1.0" encoding="UTF-8"?>
    <result href="file_1.xml">
       <json>
          <map xmlns="http://www.w3.org/2005/xpath-functions" key="_source">
             <string key="customer_name">XYZ</string>
             <string key="common_date">t1</string>
             <string key="metric1">a1</string>
             <string key="metric2">b1</string>
             <string key="metric3">c1</string>
             <string key="@timestamp">2023-03-08T17:17:37Z</string>
          </map>
       </json>
    </result>
    <result href="file_2.xml">
       <json>
          <map xmlns="http://www.w3.org/2005/xpath-functions" key="_source">
             <string key="customer_name">XYZ</string>
             <string key="common_date">t2</string>
             <string key="metric1">a2</string>
             <string key="metric2">b2</string>
             <string key="metric3">c2</string>
             <string key="@timestamp">2023-03-08T17:17:37Z</string>
          </map>
       </json>
    </result>
    

    Change result to xsl:result-document in order to create a separate file for each "payload".