xslt copy of attribute from wrong element - xslt

I'm using this template:
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:uuid="http://uuid.util.java"
exclude-result-prefixes="uuid">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="changeSet[createTable]">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="createTable"/>
<xsl:copy-of select="*[not(self::createTable)]"/>
</xsl:copy>
<xsl:if test="createTable/#remarks">
<xsl:element name="changeSet">
<xsl:attribute name="id" select="uuid:new-uuid()"/>
<xsl:attribute name="author">system</xsl:attribute>
<xsl:element name="setTableRemarks">
<xsl:copy-of select="createTable//(#tableName,#remarks)" />
</xsl:element>
<xsl:for-each select="createTable/column[#remarks]">
<xsl:element name="setColumnRemarks">
<xsl:copy-of select="../#tableName" />
<xsl:attribute name="columnName" select="#name"/>
<xsl:copy-of select="#remarks"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="createTable/#remarks"/>
<xsl:template match="createTable/column/#remarks"/>
</xsl:transform>
to transform xml like this:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="system (generated)" id="1538720867962-1">
<createTable remarks="Journal event detail attribute mapping" tableName="AS_JOURNALEVENTDETAILATTRMAP">
<column name="JOURNALEVENTTYPEID" remarks="Journal event type identifier" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="JOURNALEVENTDETAILATTRID" remarks="Journal event detail attribute identifier" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="LISTORDER" remarks="Order in list" type="NUMBER(9, 0)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
I expect that result will be:
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="system (generated)" id="1538720867962-1">
<createTable tableName="AS_JOURNALEVENTDETAILATTRMAP">
<column name="JOURNALEVENTTYPEID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="JOURNALEVENTDETAILATTRID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="LISTORDER" type="NUMBER(9, 0)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet id="c85f187d-f917-4948-8c48-7b1a132dd79e" author="system">
<setTableRemarks remarks="Journal event detail attribute mapping" tableName="AS_JOURNALEVENTDETAILATTRMAP"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTTYPEID"
remarks="Journal event type identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTDETAILATTRID"
remarks="Journal event detail attribute identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="LISTORDER"
remarks="Order in list"/>
</changeSet>
</databaseChangeLog>
but instead of that I get this:
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="system (generated)" id="1538720867962-1">
<createTable tableName="AS_JOURNALEVENTDETAILATTRMAP">
<column name="JOURNALEVENTTYPEID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="JOURNALEVENTDETAILATTRID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="LISTORDER" type="NUMBER(9, 0)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet id="c85f187d-f917-4948-8c48-7b1a132dd79e" author="system">
<setTableRemarks remarks="Order in list" tableName="AS_JOURNALEVENTDETAILATTRMAP"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTTYPEID"
remarks="Journal event type identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTDETAILATTRID"
remarks="Journal event detail attribute identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="LISTORDER"
remarks="Order in list"/>
</changeSet>
</databaseChangeLog>
note: the problem is in element setTableRemarks and attribute remarks. It's coppied from another element not from createTable as I guess it should be. Is something in my template wrong or is there another problem?
saxon library used for this is 9.8.0-14 I also tried with 9.9.0-1 but nothing has changed.

Instead of doing this...
<xsl:copy-of select="createTable//(#tableName,#remarks)" />
Do this...
<xsl:copy-of select="createTable/(#tableName,#remarks)" />
Using // is shorthand for /descendant-or-self::node()/ and so is going to search all descendant nodes of createTable, not just the node itself. As this will select multiple attributes with the same name, only the last one will be copied (as adding an attribute to a node where an existing one with the same name already exists should replace it).

Related

xslt copy attribute from external document

I've following xml
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="system (generated)" id="1538720867962-1">
<createTable tableName="AS_JOURNALEVENTDETAILATTRMAP">
<column name="JOURNALEVENTTYPEID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="JOURNALEVENTDETAILATTRID" type="NUMBER(9, 0)">
<constraints primaryKey="true" primaryKeyName="PK$AS_JOURNALEVENTDETATTRMAP"/>
</column>
<column name="LISTORDER" type="NUMBER(9, 0)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet id="c529c6ea-45c2-4ec2-8c9d-7bc935434d21" author="system">
<setTableRemarks remarks="this is wrong"
tableName="AS_JOURNALEVENTDETAILATTRMAP"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTTYPEID"
remarks="Journal event type identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="JOURNALEVENTDETAILATTRID"
remarks="Journal event detail attribute identifier"/>
<setColumnRemarks tableName="AS_JOURNALEVENTDETAILATTRMAP"
columnName="LISTORDER"
remarks="Order in list"/>
</changeSet>
</databaseChangeLog>
and the exact same document with name fixedremarks.xml but there is little change <setTableRemarks remarks="this is ok" tableName="AS_JOURNALEVENTDETAILATTRMAP"/>
with following template I'm trying to fix attribute remarks inside setTableRemarks but without success - I don't know how to properly copy that attribute from external xml.
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="originalChangeLog" select="document('/tmp/fixedremarks.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:key name="remarkTableName" match="setTableRemarks" use="#tableName"/>
<xsl:template match="changeSet[setTableRemarks]">
<xsl:variable name="currentRemarkTable" select="setTableRemarks/#tableName"/>
<xsl:comment select="$currentRemarkTable"/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:element name="setTableRemarks">
<xsl:attribute name="remarks"
select="$originalChangeLog/key('remarkTableName', $currentRemarkTable)"/>
<xsl:attribute name="tableName" select="setTableRemarks/#tableName"/>
</xsl:element>
<xsl:copy-of select="*[not(self::setTableRemarks)]"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
can somebody tell me how to map properly remark from external document?
I think you just want the identity transformation template you have plus
<xsl:template match="changeSet/setTableRemarks[key('remarkTableName', #tableName, $originalChangeLog)]/#remarks">
<xsl:attribute name="{name()}" select="key('remarkTableName', ../#tableName, $originalChangeLog)/#remarks"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/6qVRKwR has an online sample (there the secondary XML is inlined as a variable for a self-contained example but if you keep your <xsl:variable name="originalChangeLog" select="document('/tmp/fixedremarks.xml')"/> it will work as well.

xslt concat value of array

I'd like to apply template in which specified element contains value of array prefixed with some constant.
<xsl:variable name="coreTables"
select="('TAB1', 'TAB2')" />
<xsl:template match="node()[not(self::*)]">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="databaseChangeLog">
<xsl:comment> CORE TABLES </xsl:comment>
<xsl:apply-templates select="changeSet[createTable/#tableName=$coreTables]"/>
<xsl:comment> CORE SEQUENCES </xsl:comment>
<xsl:apply-templates select="changeSet[createSequence/#sequenceName='SEQ_'[$coreTables]]"/>
</xsl:template>
this is sample xml:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd
http://www.liquibase.org/xml/ns/dbchangelog">
<changeSet id="1" author="a">
<createTable tableName="TAB1">
<column></column>
</createTable>
</changeSet>
<changeSet id="1-1" author="a">
<createSequence sequenceName="SEQ_TAB1" />
</changeSet>
<changeSet id="4" author="A">
<createTable tableName="TAB4">
<column></column>
</createTable>
</changeSet>
</databaseChangeLog>
So with with last apply-templates I'd like to match all nodes of createSequence where attribute sequenceName is SEQ_+value of some coreTables. But I don't know how to write this select or if it's even possible like this.
I'm using xslt 2 and saxon 9.8he.
There a number of ways you could do this. Here's a couple...
<xsl:apply-templates
select="changeSet[createSequence/#sequenceName = (for $i in $coreTables return concat('SEQ_', $i))]"/>
<xsl:apply-templates
select="changeSet[createSequence[starts-with(#sequenceName, 'SEQ_') and substring-after(#sequenceName, 'SEQ_') = $coreTables]]"/>

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>

xslt 2 copy parent element based on input array with loop

I have following xml:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog>
<changeSet id="1" author="a">
<createTable tableName="TABLE1">
<column></column>
</createTable>
</changeSet>
<changeSet id="2" author="A">
<createTable tableName="TABLE2">
<column></column>
</createTable>
</changeSet>
<changeSet id="3" author="A">
<createTable tableName="TABLE3">
<column></column>
</createTable>
</changeSet>
<changeSet id="4" author="A">
<createTable tableName="TABLE4">
<column></column>
</createTable>
</changeSet>
</databaseChangeLog>
This is my xslt:
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="tables" select="('TABLE1','TABLE4')"/>
<xsl:template match="databaseChangeLog">
<xsl:for-each select="changeSet/createTable">
<xsl:if test="#tableName=$tables">
<xsl:value-of select="../changeSet"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:transform>
I'd like to select entire changeSet elements in which the tableName attribute will match one of $tables array value. So in this case there should be output like:
<changeSet id="1" author="a">
<createTable tableName="TABLE1">
<column></column>
</createTable>
</changeSet>
<changeSet id="4" author="A">
<createTable tableName="TABLE4">
<column></column>
</createTable>
</changeSet>
I'm using saxon 9.8he for transformations.
Instead of doing xsl:value-of which only outputs the string value of a node, you should use xsl:copy-of. And you also need to just select .. to get the parent (doing ../changeSet will try to get a sibling element named changeSet):
<xsl:template match="databaseChangeLog">
<xsl:for-each select="changeSet/createTable">
<xsl:if test="#tableName=$tables">
<xsl:copy-of select=".."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
Note you can simplify it to this...
<xsl:template match="databaseChangeLog">
<xsl:for-each select="changeSet/createTable[#tableName=$tables]">
<xsl:copy-of select=".."/>
</xsl:for-each>
</xsl:template>
Or even just this...
<xsl:template match="databaseChangeLog">
<xsl:copy-of select="changeSet[createTable/#tableName=$tables]" />
</xsl:template>

Position of node with attribute

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>