Dynamic parameters for XSLT 2.0 group-by - dynamic

I got this input
<?xml version="1.0" encoding="UTF-8"?>
<result>
<datapoint poiid="2492" period="2004" value="1240"/>
<datapoint poiid="2492" period="2005" value="1290"/>
<datapoint poiid="2492" period="2006" value="1280"/>
<datapoint poiid="2492" period="2007" value="1320"/>
<datapoint poiid="2492" period="2008" value="1330"/>
<datapoint poiid="2492" period="2009" value="1340"/>
<datapoint poiid="2492" period="2010" value="1340"/>
<datapoint poiid="2492" period="2011" value="1335"/>
<datapoint poiid="2493" period="2004" value="1120"/>
<datapoint poiid="2493" period="2005" value="1120"/>
<datapoint poiid="2493" period="2006" value="1100"/>
<datapoint poiid="2493" period="2007" value="1100"/>
<datapoint poiid="2493" period="2008" value="1100"/>
<datapoint poiid="2493" period="2009" value="1110"/>
<datapoint poiid="2493" period="2010" value="1105"/>
<datapoint poiid="2493" period="2011" value="1105"/>
</result>
and I use this xslt 2.0
<?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 method="xml" indent="yes"/>
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#poiid">
<node type="poiid" id="{#poiid}">
<xsl:for-each select="current-group()">
<node type="period" id="{#period}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
to convert it into
<?xml version="1.0" encoding="UTF-8"?>
<node type="poiid" id="2492">
<node type="period" id="2004" value="1240"/>
<node type="period" id="2005" value="1290"/>
<node type="period" id="2006" value="1280"/>
<node type="period" id="2007" value="1320"/>
<node type="period" id="2008" value="1330"/>
<node type="period" id="2009" value="1340"/>
<node type="period" id="2010" value="1340"/>
<node type="period" id="2011" value="1335"/>
</node>
<node type="poiid" id="2493">
<node type="period" id="2004" value="1120"/>
<node type="period" id="2005" value="1120"/>
<node type="period" id="2006" value="1100"/>
<node type="period" id="2007" value="1100"/>
<node type="period" id="2008" value="1100"/>
<node type="period" id="2009" value="1110"/>
<node type="period" id="2010" value="1105"/>
<node type="period" id="2011" value="1105"/>
</node>
Works smoothly.
Where I got stuck is when I tried to make it more dynamic. The real life input has 6 attributes for each datapoint instead of 3, and the usecase requires the possibility to set the grouping parameters dynamically.
I tried using parameters
<xsl:param name="k1" select="'poiid'"/>
<xsl:param name="k2" select="'period'"/>
but passing them to the rest of the xslt is something that I can't get right. The code below doesn't work, but clarifies hopefully, what I'm looking for.
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#{$k1}">
<node type="{$k1}" id="#{$k1}">
<xsl:for-each select="current-group()">
<node type="{$k2}" id="#{$k2}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
Any help appreciated..

Here is how to do it:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="k1" select="'poiid'"/>
<xsl:param name="k2" select="'period'"/>
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#*[name()= $k1]">
<node type="{$k1}" id="{#*[name() = $k1]}">
<xsl:for-each select="current-group()">
<node type="{k2}" id="{#*[name()= $k2]}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<result>
<datapoint poiid="2492" period="2004" value="1240"/>
<datapoint poiid="2492" period="2005" value="1290"/>
<datapoint poiid="2492" period="2006" value="1280"/>
<datapoint poiid="2492" period="2007" value="1320"/>
<datapoint poiid="2492" period="2008" value="1330"/>
<datapoint poiid="2492" period="2009" value="1340"/>
<datapoint poiid="2492" period="2010" value="1340"/>
<datapoint poiid="2492" period="2011" value="1335"/>
<datapoint poiid="2493" period="2004" value="1120"/>
<datapoint poiid="2493" period="2005" value="1120"/>
<datapoint poiid="2493" period="2006" value="1100"/>
<datapoint poiid="2493" period="2007" value="1100"/>
<datapoint poiid="2493" period="2008" value="1100"/>
<datapoint poiid="2493" period="2009" value="1110"/>
<datapoint poiid="2493" period="2010" value="1105"/>
<datapoint poiid="2493" period="2011" value="1105"/>
</result>
the wanted, correct result is produced:
<node type="poiid" id="2492">
<node type="" id="2004" value="1240"/>
<node type="" id="2005" value="1290"/>
<node type="" id="2006" value="1280"/>
<node type="" id="2007" value="1320"/>
<node type="" id="2008" value="1330"/>
<node type="" id="2009" value="1340"/>
<node type="" id="2010" value="1340"/>
<node type="" id="2011" value="1335"/>
</node>
<node type="poiid" id="2493">
<node type="" id="2004" value="1120"/>
<node type="" id="2005" value="1120"/>
<node type="" id="2006" value="1100"/>
<node type="" id="2007" value="1100"/>
<node type="" id="2008" value="1100"/>
<node type="" id="2009" value="1110"/>
<node type="" id="2010" value="1105"/>
<node type="" id="2011" value="1105"/>
</node>

Related

How do I handle files of different lengths / more 'lines'

My problem is, is that my input will have varying lengths / lines. My current code (thanks to michael.hor257k on my previous question) works with my test file, but I'm sure it won't work with files that have more items.
For example:
An input file with 100 records
or
an input file with 2 records.
I am also not sure how to ignore the last record as it is junk created in the original csv file that's converted to the input file.
Input:
<csv-xml>
<record line="1">
<csv-field-1>1</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3>7654321</csv-field-3>
<csv-field-4>1</csv-field-4>
<csv-field-5>08/08/19</csv-field-5>
<csv-field-6>08/08/19</csv-field-6>
</record>
<record line="2">
<csv-field-1>2</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3>12345678</csv-field-3>
<csv-field-4>3</csv-field-4>
</record>
<record line="3">
<csv-field-1>2</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3>22345679</csv-field-3>
<csv-field-4>7</csv-field-4>
</record>
<record line="4">
<csv-field-1>2</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3>32345680</csv-field-3>
<csv-field-4>6</csv-field-4>
</record>
<record line="5">
<csv-field-1>2</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3>42345681</csv-field-3>
<csv-field-4>2</csv-field-4>
</record>
<record line="6">
<csv-field-1>3</csv-field-1>
<csv-field-2>12345</csv-field-2>
<csv-field-3></csv-field-3>
</record>
</csv-xml>
Code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<orders>
<order>
<accountNo>
<xsl:value-of select="csv-xml/record[#line>='1']/csv-field-3"/>
</accountNo>
<orderDate>
<xsl:value-of select="csv-xml/record[#line>='1']/csv-field-5"/>
</orderDate>
<orderItems>
<orderItem>
<productCode>
<xsl:value-of select="csv-xml/record[#line>='2']/csv-field-3"/>
</productCode>
<Quantity>
<xsl:value-of select="csv-xml/record[#line>='2']/csv-field-4"/>
</Quantity>
</orderItem>
<orderItem>
<productCode>
<xsl:value-of select="csv-xml/record[#line>='3']/csv-field-3"/>
</productCode>
<Quantity>
<xsl:value-of select="csv-xml/record[#line>='3']/csv-field-4"/>
</Quantity>
</orderItem>
<orderItem>
<productCode>
<xsl:value-of select="csv-xml/record[#line>='4']/csv-field-3"/>
</productCode>
<Quantity>
<xsl:value-of select="csv-xml/record[#line>='4']/csv-field-4"/>
</Quantity>
</orderItem>
<orderItem>
<productCode>
<xsl:value-of select="csv-xml/record[#line>='5']/csv-field-3"/>
</productCode>
<Quantity>
<xsl:value-of select="csv-xml/record[#line>='5']/csv-field-4"/>
</Quantity>
</orderItem>
</orderItems>
</order>
</orders>
</xsl:template>
</xsl:stylesheet>
The output I have:
<?xml version="1.0"?>
<orders>
<order>
<accountNo>7654321</accountNo>
<orderDate>08/08/19</orderDate>
<orderItems>
<orderItem>
<productCode>12345678</productCode>
<Quantity>3</Quantity>
</orderItem>
<orderItem>
<productCode>22345679</productCode>
<Quantity>7</Quantity>
</orderItem>
<orderItem>
<productCode>32345680</productCode>
<Quantity>6</Quantity>
</orderItem>
<orderItem>
<productCode>42345681</productCode>
<Quantity>2</Quantity>
</orderItem>
</orderItems>
</order>
</orders>
The output I want:
<?xml version="1.0"?>
<orders><order accountNo="7654321" orderDate="08/08/19">
<orderItems>
<orderItem productCode="12345678" quantity="3"/>
<orderItem productCode="22345679" quantity="7"/>
<orderItem productCode="32345680" quantity="6"/>
<orderItem productCode="42345681" quantity="2"/>
</orderItems>
</order>
</orders>
How about simply:
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:template match="/csv-xml">
<xsl:variable name="header" select="record[1]" />
<orders>
<order accountNo="{$header/csv-field-3}" orderDate="{$header/csv-field-5}">
<orderItems>
<xsl:for-each select="record[position() != 1 and position() != last()]">
<orderItem productCode="{csv-field-3}" quantity="{csv-field-4}"/>
</xsl:for-each>
</orderItems>
</order>
</orders>
</xsl:template>
</xsl:stylesheet>
P.S. Read about Attribute Value Templates.

XSLT_1: Add missing node based on child element value

could you please assist with solution below.
The sources files some times missing hole segment (which has child 'count' element with number 3). In this case I need to map all missing node segment and add some specific values.
Please see correct source xml file below which has all 'node' segments.
<?xml version="1.0" encoding="utf-8"?>
<root>
<group>
<node segment="1">
<count>2</count>
<value>value_2</value>
</node>
<node segment="1">
<count>3</count>
<value>value_3</value>
</node>
<node segment="1">
<count>1</count>
<value>value_1</value>
</node>
</group>
</root>
The below one doesn't have all segments in place.
<?xml version="1.0" encoding="utf-8"?>
<root>
<group>
<node segment="1">
<count>2</count>
<value>value_2</value>
</node>
<node segment="1">
<count>1</count>
<value>value_1</value>
</node>
</group>
</root>
Mising part is:
<node segment="1">
<count>3</count>
<value>value_3</value>
</node>
I have worked on xslt script below which doesn't give me the correct output results. Please assist
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/group[not(node/count='3')]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<node>
<count><xsl:value-of select="'some_results'"></xsl:value-of></count>
<value><xsl:value-of select="'some_results'"/></value>
</node>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node[not(count='3')]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<count><xsl:value-of select="count"></xsl:value-of></count>
<value><xsl:value-of select="value"/></value>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
If element 'node' which has child element 'count'=3 present,- map as it is.
Otherwise the output would be:
<?xml version="1.0" encoding="utf-8"?>
<root>
<group>
<node segment="1">
<count>2</count>
<value>value_2</value>
</node>
<node segment="1">
<count>some_results</count>
<value>some_results</value>
</node>
<node segment="1">
<count>1</count>
<value>value_1</value>
</node>
</group>
</root>
Thanks,
Darius
Couldn't it be simply:
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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="group[not(node/count='3')]">
<xsl:copy>
<!-- add missing node-->
<node segment="1">
<count>some_results</count>
<value>some_results</value>
</node>
<!-- process existing nodes -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

SQL - Extract XML from multiple nodes

I've done a load of research and cannot seem to string together the SQL to extract the required data from an XML field.
<vItem>
<jobScript>
<node guid="7606bd90-98df-4572-accd-5b41ec5605dc">
<subNodes>
<node guid="17f8e275-d4f6-47c0-a5e4-80da658f4097">
<execute taskVersionGuid="5fc17d5c-7264-461f-ae38-753d703f3c99" />
</node>
<node guid="5fe2233c-9e3a-44be-aa20-aea2c8dcbd4a">
<execute taskVersionGuid="f55dc069-46ff-427e-920f-5f1c3fc3ad09" />
</node>
<node guid="ecd6a7b5-a3be-483c-acf8-64ba1c289088">
<execute taskVersionGuid="5220d97c-6e8f-400a-b814-aa7d84942c20" />
</node>
</subNodes>
</node>
</jobScript>
I'm trying to extract the taskVersionGuid from each node. In the scenario, there could be anywhere between 1 and 10 taskVersionGuids, however the example I have above has 3.
Any help with this would be appreciated.
Thanks
Edit
I have tried the below also:
declare #XML xml
set #XML =
'
<vItem>
<jobScript>
<node guid="7606bd90-98df-4572-accd-5b41ec5605dc">
<subNodes>
<node guid="17f8e275-d4f6-47c0-a5e4-80da658f4097">
<execute taskVersionGuid="5fc17d5c-7264-461f-ae38-
753d703f3c99" />
</node>
<node guid="5fe2233c-9e3a-44be-aa20-aea2c8dcbd4a">
<execute taskVersionGuid="f55dc069-46ff-427e-920f-
5f1c3fc3ad09" />
</node>
<node guid="ecd6a7b5-a3be-483c-acf8-64ba1c289088">
<execute taskVersionGuid="5220d97c-6e8f-400a-b814-
aa7d84942c20" />
</node>
</subNodes>
</node>
</jobScript>
</vItem>
'
select T.N.query('.')
from #XML.nodes('/vItem/jobScript/node/subNodes/node/execute') as T(N)
However, this results in the following:
<execute taskVersionGuid="5fc17d5c-7264-461f-ae38-753d703f3c99" />
<execute taskVersionGuid="f55dc069-46ff-427e-920f-5f1c3fc3ad09" />
<execute taskVersionGuid="5220d97c-6e8f-400a-b814-aa7d84942c20" />
Whereas I'm trying to receive the value of taskVersionGuid.
Thanks again.
Answer as below:
select T.N.value('#taskVersionGuid[1]', 'uniqueidentifier')
from #XML.nodes('/vItem/jobScript/node/subNodes/node/execute') as T(N)
What you need to do is turn your xml into a table so you can query it. below is an example of the query you will need to grab the values from the nodes.
DECLARE #xml AS XML = '<jobScript>
<node guid="7606bd90-98df-4572-accd-5b41ec5605dc">
<subNodes>
<node guid="17f8e275-d4f6-47c0-a5e4-80da658f4097">
<execute taskVersionGuid="5fc17d5c-7264-461f-ae38-753d703f3c99" />
</node>
<node guid="5fe2233c-9e3a-44be-aa20-aea2c8dcbd4a">
<execute taskVersionGuid="f55dc069-46ff-427e-920f-5f1c3fc3ad09" />
</node>
<node guid="ecd6a7b5-a3be-483c-acf8-64ba1c289088">
<execute taskVersionGuid="5220d97c-6e8f-400a-b814-aa7d84942c20" />
</node>
</subNodes>
</node>
</jobScript>'
SELECT a.value('.', 'varchar(max)')
FROM #xml.nodes('/jobScript/node/subNodes/node/execute/#taskVersionGuid') a(a)

XSLT 1.0: search an attribute and add to parent element (part-2)

Using XSLT 1.0 I need to transform this:
<wine id="_7" grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
<Method context="_5" attributes="gccxml(msgid=237)">
<Argument location="f0:13"/>
</Method>
<Field id="_3" context="_7" attributes="gccxml(msgid=239)"/>
<Field id="_4" line="19" attributes=""/>
<Method context="_8" attributes="">
<Argument location="f0:13"/>
</Method>
</wine>
into this:
XML Output-1:
<wine grape="chardonnay" msgid="239">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
<Method context="_5" attributes="gccxml(msgid=237)">
<Argument location="f0:13"/>
</Method>
<Field id="3" context="_7" attributes="gccxml(msgid=239)"/>
<Field id="4" line="19"/>
<Method context="_8">
<Argument location="f0:13"/>
</Method>
</wine>
The logic on how to accomplish 'XML Output-1' is as follows:
============================================================
- Logic 1:
----------
search for either 'Field[#context]' or 'Method[#context]' nodes that matches the wine[#id].
For instance:
<wine id="_7" grape="chardonnay">
<Field id="_3" context="_7" attributes="gccxml(msgid=239)"/>
To conclude:
The wine[id="_7"] matches Field[context="_7"], so add the attributes="gccxml(msgid=239)"
in 'wine' element (i.e. msgid="239")
Result:
<wine grape="chardonnay" msgid="239">
- Logic 2:
----------
If 'Field' or 'Method' nodes includes attributes="" (null attribute),
then delete the attributes="" in output.
For Instance:
1. <Field id="4" line="19"/>
2. <Method context="_8">
In addition, I need to transfer this:
XML Input-2
<wine id="_5" grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
<Field id="_9" line="19" attributes=""/>
<Method context="_5" attributes="gccxml(msgid=235)">
<Argument location="f0:13"/>
</Method>
<Field id="_3" context="_7" attributes="gccxml(msgid=239)"/>
<Field id="_4" line="19" attributes=""/>
</wine>
Into this
XML Output-2:
<wine grape="chardonnay" msgid="235">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
<Field id="_9" line="19"/>
<Method context="_5" attributes="gccxml(msgid=235)">
<Argument location="f0:13"/>
</Method>
<Field id="3" context="_7" attributes="gccxml(msgid=239)"/>
<Field id="4" line="19"/>
</wine>
search for either 'Field[#context]' or 'Method[#context]' nodes that
matches the wine[#id].
That's easy to do by using a key - XSLT's built-in mechanism to handle cross-references. Once that's done, it's just a rerun of your previous question:
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="msg" match="Field|Method" use="#context"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="msgid">
<xsl:value-of select="substring-before(substring-after(key('msg', #id)/#attributes, 'msgid='), ')')"/>
</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Added:
To remove empty attributes of Field and Method elemmnts, simply add an empty template that matches them:
<xsl:template match="#*[parent::Field or parent::Method][not (string())]"/>

how to select all parent node in xml data

i want to select in all parent node while i don't know how many nodes exist ?
*<TreeView>
<node text="a">
<node text="aa">
<node text="aaa" />
</node>
<node text="b">
<node text="bb" />
</node>
</node>
<node text="c" />*
</TreeView>
what i want is: a,aa,b
DECLARE #MyXML XML
SET #MyXML = '<TreeView>
<node text="a">
<node text="aa">
<node text="aaa" />
</node>
<node text="b">
<node text="bb" />
</node>
</node>
<node text="c" />*
</TreeView> '
SELECT #MyXML.value ('(//node/#text)[1]', 'VARCHAR(30)'),
#MyXML.value ('(//node/#text)[2]', 'VARCHAR(30)'),
#MyXML.value ('(//node/#text)[4]', 'VARCHAR(30)')