Split an .xml-file with XSLT - xslt

I have written an XSL-file, that reads some filenames from the source file and uses this filenames, to split another file (which is opened in the XSL-file via the document() function). The filenames are used to create several output files and certain parts of the loaded file are written to these output files.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Root">
<xsl:apply-templates select="//Link"/>
</xsl:template>
<xsl:template match="Link">
<xsl:result-document href="{#url}" method="xml">
<xsl:apply-templates select="document('Input.xml')//Node"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="Node">
<xsl:copy-of select="."/>
<xsl:if test="following-sibling::*[1][self::NextPart]">
<!-- write some test node -->
<xsl:element name="FoundNextPart"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The sourcefile looks something like this
<Root>
<SomeNode>
<Link url="part_0.xml"/>
<Link url="part_1.xml"/>
<Link url="part_2.xml"/>
</SomeNode>
</Root>
The Input.xml file will have a structure like this
<Root>
<Node>
<PartContent>
<ImportantContent>0</ImportantContent>
</PartContent>
</Node>
<Node>
<PartContent>
<ImportantContent>0</ImportantContent>
</PartContent>
</Node>
<NextPart/>
<Node>
<PartContent>
<ImportantContent>1</ImportantContent>
</PartContent>
</Node>
<Node>
<PartContent>
<ImportantContent>1</ImportantContent>
</PartContent>
</Node>
<NextPart/>
</Root>
My problem is now with the
<xsl:template match="Node">
I want to copy the content of the Input.xml up to the first appearance of the
<NextPart/>
node. Then I want to somehow break out of the current nodeset (//Node of the Input.xml) and continue with the next //Link. But for this next Link (file) I want to copy the content of the Input.xml between the first and the second appearance of the
<NextPart/>
node.
I'm not sure if this is feasible in any way. Also I'm not sure if my approach can be used for this.
I've read something like using
<xsl:call-template name="copy">
to use the following-sibling of the current node as a parameter. But anyway I have to pass the current count of the
<NextPart/>
so that I know, which content to copy!?

How about processing and grouping that Input.xml once with e.g.
<xsl:variable name="groups">
<xsl:for-each-group select="document('Input.xml')/Root/*" group-ending-with="NextPart">
<group>
<xsl:copy-of select="current-group()[self::Node]"/>
</group>
</xsl:for-each-group>
</xsl:variable>
in a global variable, then in your template you do
<xsl:template match="Link">
<xsl:variable name="pos" select="position()"/>
<xsl:result-document href="{#url}" method="xml">
<xsl:copy-of select="$groups/group[$pos]/Node"/>
</xsl:result-document>
</xsl:template>
to output the Node elements grouped earlier.

Related

Rename the root node in xml using xslt

I am new to xslt and was trying to find a solution to my problem below.
I have a xml, and using xslt I need to do the following
just rename the root node to something else (without copying the entire structure of root node into another tag)
Get rid of the namespaces information in nodes/elements/attributes
Below is my XML
<lbl:ABCD>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lbl="http://abc.xyz.com/abc/xsd/sdf/aaaa"
xsi:schemaLocation="http://abc.ayx.com/sdf/xsd/label/efr efr.xsd">
<lbl:Title>title</lbl:Title>
<lbl:date>01/Aug/2018</lbl:date>
<lbl:id>12345</lbl:id>
<lbl:location>0362</lbl:location>
<lbl:Status>aaaa</lbl:status>
<lbl:hashnum>11801792113759009</lbl:hashnum>
</lbl:ABCD>
The output I want to achieve is
<shp>
<Title>title</Title>
<date>01/Aug/2018</date>
<id>12345</id>
<location>0362</location>
<Status>aaaa</status>
<hashnum>11801792113759009</hashnum>
</shp
>
But so far I have been able to manage below (which is not how I need it to be)
<?xml version="1.0" encoding="utf-16"?>
<shp xmlns:lbl="http://abc.xyz.com/abc/xsd/sdf/aaaa">
<lbl:ICNLabel
xsi:schemaLocation="http://abc.ayx.com/sdf/xsd/label/efr efr.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lbl:Title>title</lbl:Title>
<lbl:date>01/Aug/2018</lbl:date>
<lbl:id>12345</lbl:id>
<lbl:location>0362</lbl:location>
<lbl:Status>aaaa</lbl:Status>
<lbl:hashnum>11801792113759009</lbl:hashnum>
</lbl:ICNLabel>
</shp>
Can someone guide me to resolve this?
XML: (namespaces needs to address at root element)
<lbl:ABCD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lbl="http://abc.xyz.com/abc/xsd/sdf/aaaa"
xsi:schemaLocation="http://abc.ayx.com/sdf/xsd/label/efr efr.xsd">
<lbl:Title>title</lbl:Title>
<lbl:date>01/Aug/2018</lbl:date>
<lbl:id>12345</lbl:id>
<lbl:location>0362</lbl:location>
<lbl:Status>aaaa</lbl:Status>
<lbl:hashnum>11801792113759009</lbl:hashnum>
</lbl:ABCD>
XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:lbl="http://abc.xyz.com/abc/xsd/sdf/aaaa">
<xsl:template match="*">
<xsl:element name="{local-name()}"><!--avoiding namespaces-->
<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="comment()">
<xsl:comment>
<xsl:value-of select="."/>
</xsl:comment>
</xsl:template>
<xsl:template match="/*">
<xsl:element name="shp">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
OutPut:
<?xml version="1.0" encoding="UTF-8"?><shp>
<Title>title</Title>
<date>01/Aug/2018</date>
<id>12345</id>
<location>0362</location>
<Status>aaaa</Status>
<hashnum>11801792113759009</hashnum>
</shp>

xslt attributes are not getting copied

This is my xml, I have simple XSLT to update just one element and copy everything else same.
<?xml version='1.0' encoding='UTF-8'?>
<sip xmlns="urn:x-emc:ia:schema:sip:1.0">
<dss>
<holding>message</holding>
<id>message-2018-06-12-gthlZYrWZjJuQ</id>
<pdi_schema>urn:bhp:documentum:message.1.0</pdi_schema>
<production_date>2018-06-12T21:23:04.752+08:00</production_date>
<base_retention_date>2018-06-12T21:23:04.752+08:00</base_retention_date>
<producer>IA_Samples</producer>
<entity>IA</entity>
<priority>0</priority>
<application>IA</application>
</dss>
<production_date>2018-06-12T21:23:04.752+08:00</production_date>
<seqno>1</seqno>
<is_last>true</is_last>
<aiu_count>10</aiu_count>
<page_count>0</page_count>
<pdi_hash algorithm="SHA-256" encoding="base64">iLrzH22nT7Nr258E/oBve+dFDFFyUaMHGz2v9BoBSr0=</pdi_hash>
</sip>
Below is my XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:x-emc:ia:schema:sip:1.0" xmlns:exsl="http://exslt.org/common"
xmlns:emc="urn:x-emc:ia:schema:sip:1.0"
extension-element-prefixes="exsl" xmlns:f="Functions" exclude-result-prefixes="emc xs xsl f">
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()" />
</xsl:element>
</xsl:template>
<!-- template to copy attributes -->
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<xsl:template match="emc:base_retention_date">
<base_retention_date>2016-06-30</base_retention_date>
</xsl:template>
</xsl:stylesheet>
In the output xml the attributes of below elements are not getting copied
<pdi_hash algorithm="SHA-256" encoding="base64">iLrzH22nT7Nr258E/oBve+dFDFFyUaMHGz2v9BoBSr0=</pdi_hash>
What am I missing?
The attributes aren't being copied because you aren't copying them.
If you want to copy attributes using a template rule with match="#*" then you need to ensure that the template is actually invoked, by doing <xsl:apply-templates select="#*"/>.

XSLT2.0 giving empty output when Input XML is having namespcaes

The requirement is to find the duplicate element(BaseName) in XML and marked the parent element(Account) with isDuplicate attribute. The XSL is working correctly when the input XML RootElement has no namespaces. When the root element has namespace then I get empty object. I am not sure why the namespace is causing XSL to generate empty output. Any help to get the right output would be greatly appreciated.`
Input XML WITH NAMESPACE
<?xml version="1.0"?>
<objects xmlns="urn:s.sexmaple.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Account>
<Id>001A00F</Id>
<RecordTypeId>012A00</RecordTypeId>
<BaseName>EFGH</BaseName>
</Account>
<Account>
<Id>001A0</Id>
<RecordTypeId>012A0</RecordTypeId>
<BaseName>ABCD</BaseName>
</Account>
<Account>
<Id>001A</Id>
<RecordTypeId>012A</RecordTypeId>
<BaseName>ABCD</BaseName>
</Account>
</objects>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml"
version="1.0"
encoding="UTF-8"
indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="Accounts">
<objects>
<xsl:for-each select="//Account">
<xsl:sort select="BaseName" />
<xsl:apply-templates select="." />
</xsl:for-each>
</objects>
</xsl:variable>
<xsl:variable name="unqentity">
<objects>
<xsl:for-each select="$Accounts/objects/Account">
<xsl:choose>
<xsl:when test="not(following-sibling::Account/BaseName=./BaseName) and not(preceding-sibling::Account/BaseName=./BaseName) ">
<xsl:copy-of select="." />
</xsl:when>
<xsl:otherwise>
<Account>
<xsl:attribute name="isDuplicate">yes</xsl:attribute>
<xsl:for-each select="child::*">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
</Account>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</objects>
</xsl:variable>
<xsl:copy-of select="$unqentity" />
</xsl:template>
</xsl:stylesheet>
Output XML WHEN INPUT XML HAS NAMESPACE
<?xml version="1.0" encoding="UTF-8"?>
<objects/>
Output XML when Input has no Namespaces
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<Account>
<Id>001A00F</Id>
<RecordTypeId>012A00</RecordTypeId>
<BaseName>EFGH</BaseName>
</Account>
<Account isDuplicate="yes">
<Id>001A0</Id>
<RecordTypeId>012A0</RecordTypeId>
<BaseName>ABCD</BaseName>
</Account>
<Account isDuplicate="yes">
<Id>001A</Id>
<RecordTypeId>012A</RecordTypeId>
<BaseName>ABCD</BaseName>
</Account>
</objects>
When you have a namespace, it means the element within the namespace is not the same as an element without a namespace (or indeed an element in a different name space).
This means when you do this in your XSLT...
<xsl:for-each select="//Account">
You are looking for an Account element with no namespace, and so it will not match the Account element in your source XML, which is in the amusingly titled "urn:s.sexmaple.com" (which I suspect is a misspelling)
As you are using XSLT2.0 though, there is a simple way to get around this, by specifying a default namespace for any xpath expressions, using the xpath-default-namespace. Normally, this may be enough, but you have slightly complicated matters by creating new elements within a variable, which you then later try to select.
<xsl:for-each select="$Accounts/objects/Account">
This means when you create the objects and Account elements in the $Accounts variable, they will need to be part of the namespace too.
To cut to the chase, this is what your xsl:stylesheet element needs to look like
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns="urn:s.sexmaple.com"
xpath-default-namespace="urn:s.sexmaple.com">
So, the xpath-default-namespace="urn:s.sexmaple.com" is used to match elements in your source XML, whilst the xmlns="urn:s.sexmaple.com" is used to ensure the elements you create in the variable have this namespace and can be matched later on.
Having said all that, you have rather over-complicated your whole XSLT. Are you simply trying to add an IsDuplicate attribute to Account elements with the same BaseName? Well, create a key to look up duplicates, like so
<xsl:key name="account" match="Account" use="BaseName" />
Then you can look up duplicates like so:
<xsl:if test="key('account', BaseName)[2]">
<xsl:attribute name="isDuplicate">Yes</xsl:attribute>
</xsl:if>
Try this XSLT, which should give the same results
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="urn:s.sexmaple.com">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="account" match="Account" use="BaseName" />
<xsl:template match="Account">
<xsl:copy>
<xsl:if test="key('account', BaseName)[2]">
<xsl:attribute name="isDuplicate">Yes</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note how this only needs to use xpath-default-namespace as it is not creating whole new elements, just copying existing ones (which get their namespace copied across too).
The reason your input XML without a namespace works were the one with a namespace doesn't, isn't because of the input XML, but because of the XSLT stylesheet.
When your XML file has a default namespace, that namespace will need to be declared in the stylesheet itself.
For example, with the following XML:
<test xmlns="test.xml.schema">
<element>Content</element>
</test>
When I apply the following XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<out>
<namespace>None</namespace>
<xsl:apply-templates />
</out>
</xsl:template>
<xsl:template match="test">
<test out="True">hello</test>
</xsl:template>
<xsl:template match="*"/>
</xsl:stylesheet>
The output is just:
<out><namespace>None</namespace></out>
The <xsl:template match="test"> can't match on the test element in the input xml, as it is actually test.xml.schema:test while the match in the stylesheet, with no namespace is actually :test. Thus no match is possible.
However, when we just add a namespace for the input document adn modify the template, like so:
<xsl:stylesheet xmlns:t="test.xml.schema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<out>
<namespace>test.xml.schema</namespace>
<xsl:apply-templates />
</out>
</xsl:template>
<xsl:template match="t:test">
<test out="True">hello</test>
</xsl:template>
<xsl:template match="*"/>
</xsl:stylesheet>
The output becomes:
<out xmlns:t="test.xml.schema">
<namespace>test.xml.schema</namespace>
<test out="True">hello</test>
</out>
Its important to note that the namespace abbreviation in the input document and XSL don't need to be the same (eg. blank vs. "t"), but the namespaces themselfs do: (e.g both blank and "t" must be bound to test.xml.schema).
Also note, that using a default namespace in XSLT can be fraught with issues. So its best to use declared namespaces in XSLT.

How to iterate each xml tag having different names and values ? How to start iterating from a particular tag ? in xslt 2.0

I have the below xml text,
<SUBSCRIBER>
<Anumber>639081000000</Anumber>
<FirstCallDate>20130430104419</FirstCallDate>
<SetyCode>TNT</SetyCode>
<Status>ACT</Status>
<RoamIndicator/>
<PreloadCode>P1</PreloadCode>
<CreationDate>20130116100037</CreationDate>
<PreActiveEndDate/>
<ActiveEndDate>20130804210541</ActiveEndDate>
<GraceEndDate>20140502210541</GraceEndDate>
<RetailerIndicator/>
<OnPeakAccountID>9100</OnPeakAccountID>
<OnPeakSmsExpDate>20130804210504</OnPeakSmsExpDate>
<UnivWalletAcc/>
<UnliSmsOnCtl>20130606211359</UnliSmsOnCtl>
<UnliSmsTriCtl/>
<UnliSmsGblCtl/>
<UnliMocOnCtl>20130606211359</UnliMocOnCtl>
<UnliMocTriCtl/>
<UnliMocGblCtl/>
<UnliSurfFbcCtl>20130606212353</UnliSurfFbcCtl>
</SUBSCRIBER>
How can I iterate/parse over each xml tag name and get the value ( I need the name of the tag and value in a different variables) ? And also, How can I start iterating from particular tag name ? Ex: I would like to start iterating UnivWalletAcc
Please advise.
So far, I have tried the following,
<xsl:template match="SUBSCRIBER">
<xsl:variable name="tagName">
<xsl:value-of select="/*/*/name()"/>
</xsl:variable>
<xsl:variable name="tagValue">
<xsl:value-of select="/*/*/text()"/>
</xsl:variable>
<xsl:value-of select="$tagName"/>
<xsl:value-of select="$tagValue"/>
</xsl:template>
As an alternative to Veenstra's solution, instead of the xsl:if in the SUBSCRIBER/* template, you can control the iteration in the apply-templates:
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="UnivWalletAcc,
UnivWalletAcc/following-sibling::*" />
</data>
</xsl:template>
With the following XSLT you can loop through all childs of the node SUBSCRIBER:
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<!-- Identity template that will loop over all nodes and attributes -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- Template to match the root and create new root -->
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="#*|node()" />
</data>
</xsl:template>
<!-- Template to loop over all childs of SUBSCRIBER node -->
<xsl:template match="SUBSCRIBER/*">
<!-- This will test if we have seen the UnivWalletAcc node already, if so, output something, otherwise, output nothing -->
<xsl:if test="preceding-sibling::UnivWalletAcc or self::UnivWalletAcc">
<tag>
<tagName><xsl:value-of select="name()" /></tagName>
<tagValue><xsl:value-of select="." /></tagValue>
</tag>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

XSLT - how to apply a template to every node of the type?

I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>