Position of node with attribute - xslt

How can i select the position of a node with a specific attribute in XSL.
XML:
<document type="async" page="tabTabel">
<tabs>
<table>
<row type="header">
<column type="text">href</column>
<column type="number">Mapnr.</column>
<column type="mapposition">Nr.</column>
<column type="text">Description</column>
<column type="text">Document</column>
<column type="date">Date</column>
</row>
<row type="data">
<column><![CDATA[]]></column>
<column><![CDATA[10]]></column>
<column><![CDATA[17]]></column>
<column><![CDATA[Documentation may 2013 .pdf]]></column>
<column><![CDATA[Documentation may 2013 .pdf]]></column>
<column><![CDATA[03-04-2014]]></column>
</row>
</table>
</tabs>
</document>
Current not-working XSLT:
<xsl:template match="tabs//row[#type='data']>
<xsl:variable name="mapnumber">
<xsl:value-of select="../row[#type='header']/column[#type='mapposition'][position()]" />
</xsl:variable>
</xsl:template>
I want the index number/position of the column with type 'mapposition'. How can I do this?

Try:
<xsl:variable name="mapnumber" select="count(../row[#type='header']/column[#type='mapposition']/preceding-sibling::column) + 1" />
In view of your edit, you'd probably want to do something like:
<xsl:template match="table">
<xsl:variable name="mapnumber" select="count(row[#type='header']/column[#type='mapposition']/preceding-sibling::column) + 1" />
<xsl:value-of select="row[#type='data']/column[$mapnumber]" />
</xsl:template>

This example might help you:
If you've the following XML
<row type='header'>
<column type='text'>a</column>
<column type='mapposition'>b</column>
<column type='number'>c</column>
</row>
To get the position of column with #type = 'mapposition', you can use this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="row[#type='header']">
<xsl:apply-templates select="column"/>
</xsl:template>
<xsl:template match="column">
<xsl:if test="#type='mapposition'"><xsl:value-of select="position()"/></xsl:if>
</xsl:template>
</xsl:stylesheet>

Related

XSLT: Check if string contains any value from another group of nodes

I have this XML:
<table tableName="Groups">
<item table="Groups">
<column columnName="GroupID">GROUP1</column>
<column columnName="GroupID">GROUP2</column>
</item>
</table>
<table tableName="Products">
<item table="Products">
<column columnName="ProductID">1</column>
<column columnName="ProductName">Product A</column>
<column columnName="Groups">"GROUP1","GROUP2"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">2</column>
<column columnName="ProductName">Product B</column>
<column columnName="Groups">"GROUP3","GROUP4"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">3</column>
<column columnName="ProductName">Product C</column>
<column columnName="Groups">"GROUP1","GROUP3"</column>
</item>
</table>
I'm trying to filter out products that has any groups that are present in the Groups-table.
I would like to end up with this.
<products>
<product>
<name>Product A</name>
<groups>"GROUP1","GROUP2"</groups>
</product>
<product>
<name>Product C</name>
<groups>"GROUP1","GROUP3"</groups>
</product>
</products>
So far I have this XSLT, but as I'm pretty new to XSLT, I have no idea how to make that test-statement more dynamic:
<products>
<xsl:for-each select="table[#tableName='Products']/item">
<xsl:if test="contains(column[#columnName = 'Groups'] = 'GROUP1' and contains(column[#columnName = 'Groups'] = 'GROUP2'">
<product>
<name><xsl:value-of select="column[#columnName = 'ProductName']"/></name>
<groups><xsl:value-of select="column[#columnName = 'Groups']"/></groups>
</product>
</xsl:if>
</xsl:for-each>
</products>
Try it this way:
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="/root">
<xsl:variable name="group-ids" select="table[#tableName='Groups']/item/column[#columnName='GroupID']" />
<products>
<xsl:for-each select="table[#tableName='Products']/item">
<xsl:if test="$group-ids[contains(current()/column[#columnName='Groups'], concat('"', ., '"'))]">
<product>
<name>
<xsl:value-of select="column[#columnName='ProductName']"/>
</name>
<groups>
<xsl:value-of select="column[#columnName='Groups']"/>
</groups>
</product>
</xsl:if>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/jxNakB6
However, if your processor happens to support the EXSLT str:tokenize() extension function, you could do something a bit simpler:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="exsl str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="grp-id" match="table[#tableName='Groups']/item/column[#columnName='GroupID']" use="concat('"', ., '"')" />
<xsl:template match="/root">
<products>
<xsl:for-each select="table[#tableName='Products']/item[key('grp-id', str:tokenize(column[#columnName='Groups'], ','))]">
<product>
<name>
<xsl:value-of select="column[#columnName='ProductName']"/>
</name>
<groups>
<xsl:value-of select="column[#columnName='Groups']"/>
</groups>
</product>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
XSLT 1.0 solution, using keys:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kContainsGroup" match="item[not(#table='Groups')]"
use="boolean(
/*/table[#tableName='Groups']/item/column
[contains(current()/column[#columnName='Groups'],
concat('"', ., '"'))]
)"/>
<xsl:template match="/">
<products>
<xsl:apply-templates select="key('kContainsGroup', 'true')"/>
</products>
</xsl:template>
<xsl:template match="item">
<product>
<name><xsl:value-of select="column[#columnName='ProductName']"/></name>
<groups><xsl:value-of select="column[#columnName='Groups']"/></groups>
</product>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML (fragment, made a well-formed XML document by enclosing it within a single top element <tables>):
<tables>
<table tableName="Groups">
<item table="Groups">
<column columnName="GroupID">GROUP1</column>
<column columnName="GroupID">GROUP2</column>
</item>
</table>
<table tableName="Products">
<item table="Products">
<column columnName="ProductID">1</column>
<column columnName="ProductName">Product A</column>
<column columnName="Groups">"GROUP1","GROUP2"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">2</column>
<column columnName="ProductName">Product B</column>
<column columnName="Groups">"GROUP3","GROUP4"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">3</column>
<column columnName="ProductName">Product C</column>
<column columnName="Groups">"GROUP1","GROUP3"</column>
</item>
</table>
</tables>
the wanted, correct result is produced:
<products>
<product>
<name>Product A</name>
<groups>"GROUP1","GROUP2"</groups>
</product>
<product>
<name>Product C</name>
<groups>"GROUP1","GROUP3"</groups>
</product>
</products>
Do note:
No XSLT conditional instruction (such as <xsl:if>) has been used.
No <xsl:for-each> instruction has been used
The solution is implemented in almost pure "push-style"

XSLT Apply Templates to derive specific parent & children records, children can be parent too

I need help with my XSLT. Here's my XML structure.
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
<where_used>
<component>subfield_2</component>
<type>Field</type>
</where_used>
<where_used>
<component>report_1</component>
<type>Report</type>
</where_used>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
<where_used>
<component>report_2</component>
<type>report</type>
</where_used>
</row>
<row>
<component>mainfield_3</component>
<type>Field</type>
</row>
</root>
I would like it to be transformed into the following:
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
</row>
<row>
<component>report_1</component>
<type>Report</type>
</row>
<row>
<component>report_2</component>
<type>report</type>
</row>
</root>
Basically, I am trying to get all the distinct dependencies of component mainfield_1. Here's my sample code but it is not enough to find any matching parent that has the same component name as the children.
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
</xsl:template>
If I run the above, I will not be able to get this.
<row>
<component>report_2</component>
<type>report</type>
</row>
Please help.
Consider using a key to look up the row items by component
<xsl:key name="rows" match="row" use="component" />
Then you can have a template for where_used nodes that refer to a separate row, allowing you to select that row instead
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="rows" match="row" use="component" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="* except where_used" />
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<xsl:apply-templates />
</row>
</xsl:template>
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
</xsl:stylesheet>
Note I have used the identity template too, to avoid having to explicitly copy existing nodes that don't need to be changed.

XSLT group by name and sum values

INPUT
<root>
<TL>
<msg>
<output_getquerydata>
<queries>
<query name="q1">
<parameters>
<parameter name="id">906OREA</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record>
<column name="actionState">sdss</column>
</record>
</queryResults>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL-3819</parameter>
<parameter name="prodCode">89CMID</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-01</column>
<column name="ExternalProductId"/>
</record>
<record id="2">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-02</column>
<column name="ExternalProductId"/>
</record>
<record id="3">
<column name="resource_externalId"/>
<column name="compartmentCode"/>
<column name="position"/>
<column name="ExternalProductId">316442</column>
</record>
</queryResults>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL-3819</parameter>
<parameter name="prodCode">91VPRM</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-01</column>
<column name="position">1</column>
<column name="ExternalProductId"/>
</record>
<record id="2">
<column name="resource_externalId"/>
<column name="compartmentCode"/>
<column name="position"/>
<column name="ExternalProductId">316495</column>
</record>
</queryResults>
</query>
</queries>
</output_getquerydata>
</msg>
<TL>
<id>65004</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>2700</Gross>
</TL>
<TL>
<id>65005</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>1700</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
<TL>
<id>65006</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>2100</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
<TL>
<id>65007</id>
<ArticleNr>91VPRM</ArticleNr>
<Gross>500</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
</TL>
</root>
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output encoding="ISO-8859-1" indent="yes" method="xml"/>
<xsl:key name="Article" match="TL" use="./ArticleNr"/>
<xsl:key name="prod" match="query[#name='q2']/queryResults/record" use="../../parameters/parameter[#name='prodCode']"/>
<xsl:template match="root">
<msglist>
<xsl:for-each select="./TL[./msg/output_getquerydata/queries/query/queryResults/record/column[#name='actionState'] !='finished'] ">
<xsl:variable name="distinctArticle" select="//TL[string(ArticleNr)][count(. | key('Article',ArticleNr)[1]) = 1]"/>
<msg>
<CMT>
<m>
<a>
<AP>
<xsl:variable name="position">
<!--variable to concatenate all products -->
<xsl:for-each select="$distinctArticle">
<xsl:variable name="pos" select="concat(key('prod', .//ArticleNr)/column[#name='ExternalProductId'][. != ''],';')"/>
<xsl:value-of select="$pos"/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="TL[generate-id() =
generate-id(key('Article', .//ArticleNr)[1])]">
<grossVolume>
<xsl:value-of select="sum(concat(.,';'))"/>
</grossVolume>
</xsl:for-each>
<product>
<xsl:choose>
<xsl:when test="substring($position, string-length($position))=';'">
<xsl:value-of select="substring($position, 1, string-length($position)-1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$position"/>
</xsl:otherwise>
</xsl:choose>
</product>
</AP>
</a>
</m>
</CMT>
</msg>
</xsl:for-each>
</msglist>
</xsl:template>
</xsl:stylesheet>
The output is almost good. The thing is I need in the grossVolume node to have the sum of all "Gross" nodes for the same ArticleNr.
SO, my output would be:
<grossVolume>6500;500</grossVolume>
<product>316442;316495</product>
Because for ArticleNr - 89CMID -, we have the sum values in gross 2700+1700+2100=6500.
Basically, for all "ArticleNr" nodes that have the same values, I need to sum all the numbers from their respective "Gross" nodes, and then produce an output where we concatenate these sums.
For the Product node output, I concatenate the required values, and it works, I need to have that similar information in the grossVolume node also.
Thank you!
You cannot do a sum() and concat() in the same instruction as it would throw an error for string and number combination. The sum will have to be stored in a local variable and then used to concatenate with other sums to form the value of <grossVolume>.
Please change the for-each loop for TL
<xsl:for-each select="TL[generate-id() = generate-id(key('Article', .//ArticleNr)[1])]">
<grossVolume>
<xsl:value-of select="sum(concat(.,';'))"/>
</grossVolume>
</xsl:for-each>
to the below snippet
<grossVolume>
<xsl:for-each select="TL[generate-id() = generate-id(key('Article', ./ArticleNr)[1])]">
<xsl:variable name="sumGross" select="sum(key('Article', ./ArticleNr)/Gross)" />
<xsl:value-of select="$sumGross" />
<xsl:if test="position() != last()">
<xsl:value-of select="';'" />
</xsl:if>
</xsl:for-each>
</grossVolume>
Output
<grossVolume>6500;500</grossVolume>

Sorting and moving elements into a new element

Even with all the good tips on this site, I still have some trouble with my xslt. I'm pretty new to it. I have this source file:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<row type="A">
<name>ABC</name>
</row>
<row type="B">
<name>BCA</name>
</row>
<row type="A">
<name>CBA</name>
</row>
</file>
and I want to add an element and sort the rows on type, to get this result
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>
I'm able to sort the rows using this:
<xsl:template match="file">
<xsl:copy>
<xsl:apply-templates select="#*/row"/>
<xsl:apply-templates>
<xsl:sort select="#type" data-type="text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
and I'm able to move the rows using this
<xsl:template match="file">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="*[not(name(.)='row')]" />
<details>
<xsl:apply-templates select="row" />
</details>
</xsl:copy>
</xsl:template>
but I'm not able to produce the correct answer when I try to combine them. Hopefully I understand more of XSLT when I see how things are combined. Since I'm creating a new element <details>, I think the sorting has to be done before the creation of the new <details> element. I have to use xslt 1.0.
Something like this seems to work:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="file">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="row[1]/preceding-sibling::*" />
<details>
<xsl:for-each select="row">
<xsl:sort select="#type" data-type="text"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</details>
<xsl:copy-of select="row[last()]/following-sibling::*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is the result I got:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>

Create Excel (SpeadsheetML) output with XSLT

I have this XML file and I want to create an XSL file to convert it to Excel. Each row should represent a logo. The columns will be the key attributes like color, id, description plus any other key for other logos.
<Top>
<logo>
<field key="id">172-32-1176</field>
<field key="color">Blue</field>
<field key="description"><p>Short Description</p></field>
<field key="startdate">408 496-7223</field>
</logo>
<logo>
<field key="id">111-111-111</field>
<field key="color">Red</field>
</logo>
<!-- ... -->
</Top>
The XSL file is something like this:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates/>
</Workbook>
</xsl:template>
<xsl:template match="/*">
<Worksheet>
<xsl:attribute name="ss:Name">
<xsl:value-of> select="local-name(/*)"/>
</xsl:attribute>
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<xsl:for-each select="*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="#key"/>
</Data>
</Cell>
</xsl:for-each>
</Row>
<xsl:apply-templates/>
</Table>
</Worksheet>
</xsl:template>
<xsl:template match="/*/*">
<Row>
<xsl:apply-templates/>
</Row>
</xsl:template>
<xsl:template match="/*/*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
<!-- <xsl:apply-templates/> -->
</xsl:template>
</xsl:stylesheet>
But data are not correctly placed under the columns and column names are repeating. How can this be done?
The columns could be in any order and also column stardate should be empty for second row in excel. Similarly for more .
You were very close. Try to be more specific when it comes to template matching - don't say template match"/*/*/*" when you can say template match="field".
Other than that, this is your approach, only slightly modified:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates select="Top" />
</Workbook>
</xsl:template>
<xsl:template match="Top">
<Worksheet ss:Name="{local-name()}">
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<!-- header row, made from the first logo -->
<xsl:apply-templates select="logo[1]/field/#key" />
</Row>
<xsl:apply-templates select="logo" />
</Table>
</Worksheet>
</xsl:template>
<!-- a <logo> will turn into a <Row> -->
<xsl:template match="logo">
<Row>
<xsl:apply-templates select="field" />
</Row>
</xsl:template>
<!-- convenience: <field> and #key both turn into a <Cell> -->
<xsl:template match="field | field/#key">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
</xsl:template>
</xsl:stylesheet>
Your "repeating column names" problem roots in this expression:
<xsl:for-each select="*/*">
In your context, this selects any third level element in the document (literally all <field> nodes in all <logo>s), and makes a header row out of them. I replaced it with
<xsl:apply-templates select="logo[1]/field/#key" />
which makes a header row out of the first <logo> only.
If a certain column order is required (other than document order) or not all <field> nodes are in the same order for all <logo>s, things get more complex. Tell me if you need that.