XSLT output two documents based on array variable - xslt

I have xml file with structure like this:
<?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">
<changeSet author="system" id="65D7AFA9-17F1-4406-997E-D2B42A0E9008" dbms="oracle">
<sql>GRANT SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOG TO ${appUser}</sql>
<rollback>REVOKE SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOG FROM ${appUser}</rollback>
</changeSet>
<changeSet author="system" id="E2731435-DCEE-4B7E-BA60-20A87E75227A" dbms="oracle">
<sql>GRANT SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOGATTRIBUTE TO ${appUser}</sql>
<rollback>REVOKE SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOGATTRIBUTE FROM ${appUser}</rollback>
</changeSet>
<changeSet author="system" id="65D7AFA9-17F1-4406-997E-D2B42A0E9008" dbms="oracle">
<createTable ...>
</changeSet>
</databaseChangeLog>
I'd like to output all changeSets where sql contains one of tables to one document and all others (which doesn't contain the table name) to other document. I was thinking about appending stuff to variable and at the end print two variables, but that doesn't work (or I don't know how to process further).
<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:variable name="tables"
select="('CATALOG','CATALOGATTRIBUTE','EVENTTYPES')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="changeSet[sql]">
<xsl:variable name="changeSet" select="."/>
<xsl:variable name="sql" select="sql"/>
<xsl:for-each select="$tables">
<!--<xsl:variable name="selected" select="sql[contains(text(),concat('${dbSchema}.', ., ' '))]"/>-->
<xsl:variable name="tableName">
<xsl:value-of select="."/>
<xsl:value-of select="' '"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($sql, $tableName)">
<xsl:copy-of select="$changeSet"/>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:transform>
can I somehow gather all changeSet which match tables and then all changeSets which doe not?
ps: I'm using Saxon-HE-9.8.0-14 for processing.
Thanks

Consider the following simplified example:
XML
<databaseChangeLog>
<changeSet>
<sql>ALPHA,BRAVO,CHARLIE</sql>
</changeSet>
<changeSet>
<sql>BRAVO,CHARLIE,DELTA</sql>
</changeSet>
<changeSet>
<sql>DELTA,ECHO,FOXTROT</sql>
</changeSet>
<changeSet>
<sql>FOXTROT,GOLF,HOTEL</sql>
</changeSet>
</databaseChangeLog>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="tables" select="('BRAVO', 'GOLF')"/>
<xsl:template match="/databaseChangeLog">
<xsl:variable name="group1" select="changeSet[some $t in $tables satisfies contains(sql, $t)]" />
<xsl:copy>
<group1>
<xsl:copy-of select="$group1"/>
</group1>
<group2>
<xsl:copy-of select="changeSet except $group1"/>
</group2>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog>
<group1>
<changeSet>
<sql>ALPHA,BRAVO,CHARLIE</sql>
</changeSet>
<changeSet>
<sql>BRAVO,CHARLIE,DELTA</sql>
</changeSet>
<changeSet>
<sql>FOXTROT,GOLF,HOTEL</sql>
</changeSet>
</group1>
<group2>
<changeSet>
<sql>DELTA,ECHO,FOXTROT</sql>
</changeSet>
</group2>
</databaseChangeLog>
Demo: https://xsltfiddle.liberty-development.net/jyRYYiP
A better solution would be to use tokenize():
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="tables" select="('BRAVO', 'GOLF')"/>
<xsl:template match="/databaseChangeLog">
<xsl:variable name="group1" select="changeSet[tokenize(sql, ',') = $tables]" />
<xsl:copy>
<group1>
<xsl:copy-of select="$group1"/>
</group1>
<group2>
<xsl:copy-of select="changeSet except $group1"/>
</group2>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/jyRYYiP/1

Related

How to calculate positions of XML elements using XSL?

I have a list of items in XML (mind the duplicates):
<root>
<a>hello</a>
<a>bye</a>
<a>5</a>
<a>hello</a>
<a>8</a>
</root>
I want to translate it to this:
<root>
<a>4</a>
<a>3</a>
<a>1</a>
<a>4</a>
<a>2</a>
</root>
Essentially, I'm replacing values with their positions in a sorted list of all values (comparing text to text). I'm trying to do this using <xsl:key>, but can't figure out how exactly.
Try perhaps:
XSLT 2.0
<xsl:stylesheet version="2.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="sorted">
<xsl:perform-sort select="a">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of(distinct-values($sorted/a), .)"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Or maybe a bit more elegantly:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<xsl:variable name="sorted" as="xs:string*">
<xsl:perform-sort select="distinct-values(a)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of($sorted, .)"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Or even just simply:
<xsl:stylesheet version="2.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="sorted">
<xsl:perform-sort select="a">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of($sorted/a, .)[1]"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

XSLT transformation with multiple nodes

I have multiple occurence nodes which need to be generated at output using XSLT transformation. Could you please help me on this.
Following XSLT code only generate one node occurrence only. Could you please help me with below XSLT code how to generate multiple nodes elements in Input XML
Input XML
<?xml version="1.0" encoding="UTF-8" ?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" >
<soapenv:Body>
<ns1:getGenResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
<ns1:getGenReturn xsi:type="soapenc:Array" soapenc:arrayType="xsd:anyType[2]" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
</ns1:getGenReturn>
</ns1:getGenResponse>
<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Gen" xmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/>
<name xsi:type="xsd:string">ULM</name>
<mail xsi:type="xsd:string">ulm#gmail.com</mail>
</multiRef>
<multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:Gen" " xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">ABC</name>
<mail xsi:type="xsd:string">abc#gmail.com</mail>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>
XSLT Code used for this transformation
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" x
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:if test="//soap:Body/multiRef">
<xsl:element name="getGenResponse">
<xsl:element name="getGenReturn">
<xsl:element name="name"><xsl:value-of select="//name"/></xsl:element>
<xsl:element name="mail"><xsl:value-of select="//mail"/></xsl:element>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:template>
<!-- 'Copy ' node -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output from above XSLT
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
/getGenResponse>
Output expected
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
<getGenReturn>
<name>ABC</name>
<mail>abc#gmail.com<mail>
</getGenReturn>
/getGenResponse>
At you moment all you are doing is testing a multiRef element exists, and outputting only one new getGenReturn element.
All you really need to do is replace the xsl:if with xsl:for-each to select all the elements, then you will get one getGenReturn for each. And also change the xsl:value-of to use a relative path
<xsl:template match="/">
<xsl:element name="getGenResponse">
<xsl:for-each select="//soap:Body/multiRef">
<xsl:element name="getGenReturn">
<xsl:element name="name"><xsl:value-of select="name"/></xsl:element>
<xsl:element name="mail"><xsl:value-of select="mail"/></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
Or better still, do this, as xsl:element is not really needed here if you are using static names
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
Note, you don't actually need the identity template in this case. Try this XSLT:
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>

Transform an XML by grouping the fields

My knowledge to XSLT is limited but always eager to learn. I am currently working on a template that requires to transform the XML input. I've been trying to group the InvoiceNum fields and not getting anywhere.
I am getting an error: Envision.Utilities.XsltEngine-Object reference not set to an instance of an object.
Here's the input XML for reference:
<?xml version='1.0' ?>
<Request>
<Information>
<ImageID>987456321</ImageID>
<Contract>123456789</Contract>
<Lastname>MICKEYMOUSE</Lastname>
</Information>
<Document>
<InvoiceNum>123456823</InvoiceNum>
<Reference>AD20985224</Reference>
<InvoiceNum>100000123</InvoiceNum>
<Reference>AS20101387</Reference>
<InvoiceNum>858511825</InvoiceNum>
<Reference>GF96844</Reference>
<InvoiceNum>885154145</InvoiceNum>
<Reference>FGFD2018</Reference>
<InvoiceNum>25241111</InvoiceNum>
<Reference>SD88888</Reference>
<InvoiceNum>8571414</InvoiceNum>
<Reference>DF864841254</Reference>
</Document>
</Request>
Here's my XSLT format for reference:
What am I missing? Is there better way to format the XSLT template I have currently below? Any help is greatly appreciated.
<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:param name="cols" select="3" />
<xsl:template match="Request">
<table border="1">
<xsl:apply-templates select="InvoiceNum[position() mod $cols = 1]"/>
</table>
</xsl:template>
<xsl:template match="Document">
<xsl:variable name="group" select=". | following-sibling::InvoiceNum
[position() < $cols]" />
<xsl:for-each select="*">
<xsl:variable name="i" select="position()" />
<Invoice>
<InvoiceNumber>
<xsl:value-of select="InvoiceNum()"/>
</InvoiceNumber>
<xsl:for-each select="$group">
<xsl:value-of select="*[$i]"/>
</xsl:for-each>
</Invoice>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here's the XML output I'd like to have:
<InvCase>
<Invoices>
<InvoicemNumber>InvoiceNum1</InvoicemNumber>
<InvoicemNumber>InvoiceNum2</InvoicemNumber>
<InvoicemNumber>InvoiceNum3</InvoicemNumber>
</Invoices>
</InvCase>
It looks like you are trying to group in the InvoiceNum into groups of 3. The first issue you have is that in your template matching Request you do this...
<xsl:apply-templates select="InvoiceNum[position() mod $cols = 1]"/>
But InvoiceNum is not a child of Request, and so that selects nothing. You probably need to do this...
Additionally, you have a template matching Document, but this probably needs to match InvoiceNum (Doing following-sibling::InvoiceNum would not return anything if you were matching Document as the InvoiceNum elements are children of Document not following siblings).
Try this XSLT
<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:param name="cols" select="3" />
<xsl:template match="Request">
<InvCases>
<xsl:apply-templates select="Document/InvoiceNum[position() mod $cols = 1]"/>
</InvCases>
</xsl:template>
<xsl:template match="InvoiceNum">
<xsl:variable name="group" select=". | following-sibling::InvoiceNum[position() < $cols]" />
<Invoice>
<xsl:for-each select="$group">
<InvoiceNumber>
<xsl:value-of select="."/>
</InvoiceNumber>
</xsl:for-each>
</Invoice>
</xsl:template>
</xsl:stylesheet>

XSL Transformation using templates in sequence

How can I transform XML using two templates in sequence?
For example, I've one XML that has string XML in it, so I need to trasform this string XML into XML and after that I need to remove invoices node.
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<MT_STATEMENT_response >
<Statement_response>
<P_INVOICE>
<invoices>
<invoice>
<number>12345</number>
<status/>
</invoice>
<invoice>
<number>67890</number>
<status/>
</invoice>
</invoices>
</P_INVOICE>
</Statement_response>
</MT_STATEMENT_response>
transformer.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="/*">
<xsl:variable name="t1">
<xsl:apply-templates select="Statement_response" />
</xsl:variable>
<xsl:apply-templates mode="pass2" select="ext:node-set($t1)/*" />
</xsl:template>
<xsl:template match="Statement_response">
<xsl:value-of select="." disable-output-escaping="yes" />
</xsl:template>
<xsl:template match="invoices" mode="pass2">
<xsl:value-of select="." disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>
Expected
<invoice>
<number>12345</number>
<status/>
</invoice>
<invoice>
<number>67890</number>
<status/>
</invoice>
Using XSLT 3 and the parse-xml function (https://www.w3.org/TR/xpath-functions-31/#func-parse-xml) you could output those invoice elements escaped in the original input in the P_INVOICE element using
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:template match="/">
<xsl:copy-of select="parse-xml(//P_INVOICE)//invoice"/>
</xsl:template>
</xsl:stylesheet>

Facing Issue in XSLT Format

I am looking my xslt in below format:
<xml>
<apis>
<name>API Name</name>
<comment> Comment</comment>
<version>12</version>
</apis>
</xml>
XSLT Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:L7j="http://ns.l7tech.com/2012/08/jdbc-query-result" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<xml>
<xsl:apply-templates select="//L7j:col" />
</xml>
</xsl:template>
<xsl:template match="//L7j:col">
<api>
<xsl:element name="{#name}">
<xsl:value-of select="." /></xsl:element>
</api>
</xsl:template>
</xsl:stylesheet>
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<L7j:jdbcQueryResult xmlns:L7j="http://ns.l7tech.com/2012/08/jdbc-query-result">
<L7j:row>
<L7j:col name="name" type="java.lang.String">Policy for service #0b8bab6913cc588557b6973e94d1bfdd, WSTrustSoapService</L7j:col>
<L7j:col name="comment">
<![CDATA[NULL]]>
</L7j:col>
<L7j:col name="version" type="java.lang.Integer">18</L7j:col>
</L7j:row>
<L7j:row>
<L7j:col name="name" type="java.lang.String">Policy for service #0b8bab6913cc588557b6973e94d5893d, UUPRStub</L7j:col>
<L7j:col name="comment">
<![CDATA[NULL]]>
</L7j:col>
<L7j:col name="version" type="java.lang.Integer">16</L7j:col>
</L7j:row>
</L7j:jdbcQueryResult>
If I understand correctly, you want to do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:L7j="http://ns.l7tech.com/2012/08/jdbc-query-result"
exclude-result-prefixes="L7j">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="/L7j:jdbcQueryResult">
<xml>
<xsl:apply-templates/>
</xml>
</xsl:template>
<xsl:template match="L7j:row">
<apis>
<xsl:apply-templates/>
</apis>
</xsl:template>
<xsl:template match="L7j:col">
<xsl:element name="{#name}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>