XSLT split elements by splitter-element - xslt

I've tried to combine solutions to the similar questions asked here but all of them work in very specific cases. My case is the following: I have an arbitrary xml document which contains some tags, let's say <separator/>. What I want is to split parent elements of these tags like that:
INPUT:
<some_tag some_attr="some_value">
some text
<some_other_tag>some another text</some_other_tag>
<separator/>
some other content
</some_tag>
OUTPUT:
<some_tag some_attr="some_value">
some text
<some_other_tag>some another text</some_other_tag>
</some_tag>
<separator/>
<some_tag some_attr="some_value">
some other content
</some_tag>
Also, I am limited to XSLT 1.0 since Xalan is used in the project

Either use sibling recursion or use a key to find the nodes "belonging" to a separator. Additional care is needed to copy stuff following the last separator:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="sep" match="*[separator]/node()[not(self::separator)]" use="generate-id(following-sibling::separator[1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[separator]">
<xsl:apply-templates select="separator" mode="split"/>
<xsl:if test="separator[last()]/following-sibling::node()">
<xsl:copy>
<xsl:apply-templates select="#* | separator[last()]/following-sibling::node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="separator" mode="split">
<xsl:apply-templates select=".." mode="split">
<xsl:with-param name="separator" select="."/>
</xsl:apply-templates>
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*" mode="split">
<xsl:param name="separator"/>
<xsl:copy>
<xsl:apply-templates select="#* | key('sep', generate-id($separator))"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPqsHTc
Note that if you use Xalan Java then in the Java world this is much easier with Saxon 9 and XSLT 2/3's <xsl:for-each-group select="node()" group-adjacent="boolean(self::separator)"> or group-starting-with="separator".

Related

<it> is inside the <title> so we can not use "value-of"

<section>
<title>WHO applauds the efforts of test developers to <it>innovate and respond</it> to the needs of the population</title>
<xsl:template match="section/title">
<xsl:element name="head1">
<xsl:apply-templates select="upper-case(node())"/>
</xsl:element>
</xsl:template>
Use
<xsl:template match="section/title//text()">
<xsl:value-of select="upper-case(.)"/>
</xsl:template>
together with the identity transformation (e.g. declared by <xsl:mode on-no-match="shallow-copy"/> in XSLT 3 or spelled out as
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
in earlier versions).
You can then override it for any other transformations you want or need by adding specific templates e.g. <xsl:template match="section/title"><head1><xsl:apply-templates/></head1></xsl:template>.

XSLT. How to do 2 changes for 1 element? Ordering and renaming

I need to change order of elements. But also rename elements into elements.
So,
I have xml:
<transactionality>
<rollbackexttransactionid>
<rollbackvalidity>3600</rollbackvalidity>
<rollbackscheduledattemps>3</rollbackscheduledattemps>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbackexttransactionid>
<transactionalitylogspath>TransactionalityLogs</transactionalitylogspath>
<rollbacklifecycleevents>
<rollbacklifecycle>1200</rollbacklifecycle>
<rollbackscheduledattemps>2</rollbackscheduledattemps>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbacklifecycleevents>
<rollbackpayment>
<rollbackvalidity>1200</rollbackvalidity>
<rollbackscheduledattemps>2</rollbackscheduledattemps>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbackpayment>
</transactionality>
I need to do element transactionalitylogspath as first in to transactionality.
Rename all elements "rollbackscheduledattemps" to "rollbackscheduledattempts"
Rename rollbacklifecycleevents/rollbacklifecycle to rollbacklifecycleevents/rollbackvalidity
I would'like to have:
<transactionality>
<transactionalitylogspath>TransactionalityLogs</transactionalitylogspath>
<rollbackexttransactionid>
<rollbackvalidity>3600</rollbackvalidity>
<rollbackscheduledattempts>3</rollbackscheduledattempts>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbackexttransactionid>
<rollbacklifecycleevents>
<rollbackvalidity>1200</rollbackvalidity>
<rollbackscheduledattempts>2</rollbackscheduledattempts>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbacklifecycleevents>
<rollbackpayment>
<rollbackvalidity>1200</rollbackvalidity>
<rollbackscheduledattempts>2</rollbackscheduledattempts>
<manualcorrectionpath>TransactionalityLogs</manualcorrectionpath>
</rollbackpayment>
</transactionality>
I did:
<xsl:template match="transactionality">
<xsl:variable name="elements-after" select="rollbackexttransactionid|rollbacklifecycleevents|rollbackpayment"/>
<xsl:copy>
<xsl:copy-of select="transactionalitylogspath"/>
<xsl:copy-of select="$elements-after">
</xsl:copy-of >
</xsl:copy>
</xsl:template>
<xsl:template match="rollbackscheduledattemps">
<rollbackscheduledattempts>
<xsl:apply-templates select="#* | node()"/>
</rollbackscheduledattempts>
</xsl:template>
byt it doesn't work :(.
Help me please.
Here's how I would do it :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="transactionality">
<xsl:copy>
<xsl:apply-templates select="transactionalitylogspath"/>
<xsl:apply-templates select="*[local-name() != 'transactionalitylogspath']"/>
</xsl:copy>
</xsl:template>
<xsl:template match="rollbackscheduledattemps">
<rollbackscheduledattempts>
<xsl:value-of select="."/>
</rollbackscheduledattempts>
</xsl:template>
<xsl:template match="rollbacklifecycleevents/rollbacklifecycle">
<rollbackvalidity>
<xsl:value-of select="."/>
</rollbackvalidity>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here's a working example : https://xsltfiddle.liberty-development.net/6pS26mL

XSL code to rename the prefix without disturbing the attributes

I have given below the XSL codes i have tried and the part of input and output codes.If I use XSL1 to rename the namespace prefix values in the xml tags , unfortunately it collapses the attribute values and if I use XSL2 attributes are getting created a separate nodes. Can someone pls help to write an XSL to rename the prefixes but to keep the attributes in the same node.
XSL 1:​
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns0:*">
<xsl:element name="ubl:{local-name()}" namespace="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
XSL2:
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy></xsl:template>
<xsl:template match="#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="ns0:*">
<xsl:element name="ubl:{local-name()}" namespace="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
Input:
<ns5:LegalMonetaryTotal>
<ns2:LineExtensionAmount currencyID="EUR">35.38</ns2:LineExtensionAmount>
<ns2:TaxExclusiveAmount currencyID="EUR">35.38</ns2:TaxExclusiveAmount>
<ns2:TaxInclusiveAmount currencyID="EUR">37.5</ns2:TaxInclusiveAmount>
<ns2:PrepaidAmount currencyID="EUR">37.5</ns2:PrepaidAmount>
<ns2:PayableAmount currencyID="EUR">0.00</ns2:PayableAmount>
</ns5:LegalMonetaryTotal>
Output1:
<cac:LegalMonetaryTotal >
<cbc:LineExtensionAmount >EUR35.38</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount >EUR35.38</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount >EUR37.5</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount >EUR37.5</cbc:PrepaidAmount>
<cbc:PayableAmount >EUR0.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
Output2:
<cac:LegalMonetaryTotal >
<cbc:LineExtensionAmount >
<currencyID>EUR</currencyID>
35.38
</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount >
<currencyID>EUR</currencyID>
35.38
</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount >
<currencyID>EUR</currencyID>
37.5
</cbc:TaxInclusiveAmount>
<cbc:PrepaidAmount >
<currencyID>EUR</currencyID>
37.5
</cbc:PrepaidAmount>
<cbc:PayableAmount>
<currencyID>EUR</currencyID>
0.00
</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
But Expected output is: I have tried multiple options guys. Please help me to get the below format.
<cbc:LegalMonetaryTotal>
<cac:LineExtensionAmount currencyID="EUR">35.38</cac:LineExtensionAmount>
<cac:TaxExclusiveAmount currencyID="EUR">35.38</cac:TaxExclusiveAmount>
<cac:TaxInclusiveAmount currencyID="EUR">37.5</cac:TaxInclusiveAmount>
<cac:PrepaidAmount currencyID="EUR">37.5</cac:PrepaidAmount>
<cac:PayableAmount currencyID="EUR">0.00</cac:PayableAmount>
</cbc:LegalMonetaryTotal>
Regards,
Indu
The reason why your first stylesheet does not output any attributes is that you have no template that handles attributes. Therefore, the instruction:
<xsl:apply-templates select="#* | node()"/>
does not do anything for attributes.
Try changing your first template:
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
to the standard identity transform template:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
That will probably make it work. I say probably, because your stylesheet does not fit your input nor the expected output. Your stylesheet looks for:
<xsl:template match="ns0:*">
but your input has no elements with a ns0 prefix. And your stylesheet outputs a ubl prefix, while your expected output uses cbc and cac prefixes. And - as already mentioned in the comments to your question - all these prefixes need to be bound to namespaces in order for the input and the input to be well-formed XML documents.

XSLT - Best practice for moving specific attributes around

I was looking for some guidance on how best to approach my issue.
I have an XML document like the following but on a larger scale.
<NewDataSet>
<Table Attri1="Attri1Val" Attri2="Attri2Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri5="Attri5Val" Attri6="Attri6Val" Attri7="Attri7" />
</NewDataSet>
I need to move certain attributes from the Table node, for example Attri2 and Attri5 into elements within the Table node, however I need to leave the rest of the attributes as they are.
What would be the best way to approach this? The data scale is about 3-4 times that shown.
EDIT:
Expected output:
<NewDataSet>
<Table Attri1="Attri1Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri6="Attri6Val" Attri7="Attri7">
<Attri2>Attri2Val</Attri2>
<Attri5>Attri5Val</Attri5>
</Table>
</NewDataSet>
The complexity is not really the issue, more the scale of the data and what is the best way to deal with it.
Use
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table">
<xsl:copy>
<xsl:apply-templates select="#*[not(name() = 'Attri2') and not(name() = 'Attri5')]"/>
<xsl:apply-templates select="#Attri2 | #Attri5 | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table/#Attri2 | Table/#Attri5">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
[edit]
The name comparison of the attributes is a bit ugly but will probably do for your sample. What we really need is #* execpt (#Attri2, #Attri5), only that is XPath 2.0. With XPath 1.0 the equivalent is
<xsl:template match="Table">
<xsl:copy>
<xsl:variable name="all-attributes" select="#*"/>
<xsl:variable name="to-be-transformed" select="#Attri2 | #Attri5"/>
<xsl:apply-templates select="$all-attributes[count(. | $to-be-transformed) != count($to-be-transformed)]"/>
<xsl:apply-templates select="$to-be-transformed | node()"/>
</xsl:copy>
</xsl:template>
This generic transformation can handle any set of attributes, with any length, whose names can be specified externally to the transformation:
<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:param name="pToTransform" select="'|Attri2|Attri5|'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table">
<xsl:copy>
<xsl:apply-templates select=
"#*[not(contains($pToTransform, concat('|',name(),'|')))] | node()"/>
<xsl:apply-templates mode="makeElement"
select="#*[contains($pToTransform, concat('|',name(),'|'))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*" mode="makeElement">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document:
<NewDataSet>
<Table Attri1="Attri1Val" Attri2="Attri2Val"
Attri3="Attri3Val" Attri4="Attri4Val"
Attri5="Attri5Val" Attri6="Attri6Val"
Attri7="Attri7" />
</NewDataSet>
the wanted, correct result is produced:
<NewDataSet>
<Table Attri1="Attri1Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri6="Attri6Val" Attri7="Attri7">
<Attri2>Attri2Val</Attri2>
<Attri5>Attri5Val</Attri5>
</Table>
</NewDataSet>

How check document is available in xsl?

How i check select="document('02.xml')/*/Person"/ and select="document('04.xml')/*/Person"/ is available in xsl. when xsl is running
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/xmlResponse">
<xsl:copy>
<xsl:apply-templates select="document('02.xml')/*/Person"/>
<xsl:apply-templates select="document('04.xml')/*/Person"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XSLT processor dependent.
Check first the parser
<xsl:variable name="vendor" select="system-property('xsl:vendor')" />
and then make a choose.
Microsoft use embedded javascript custom function (http://dev.ektron.com/kb_article.aspx?id=482), use FileSystemObject and returns 1 or 0 that can be then tested in XSLT.
Saxon,Xalan:
<xsl:variable name="d03" select="document('03.xml')"/>
<xsl:choose>
<xsl:when test="$d03">
<xsl:apply-templates select="document('03.xml')/*/Person"/>
</xsl:when>
<xsl:otherwise>
<Person name="Matthew" missing="true"/>
</xsl:otherwise></xsl:choose>