I need to remove some unwanted fields over a big response XML using XSL 1.0, and want to use apply-templates instead of for-each. Below is the code I tried but is not giving expected results i.e., unwanted fields are removed successfully but few other fields outside match also disappeared. Am I using identity template incorrectly.
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:array name="BankDetails">
<one>field1</one>
<two>field2</two>
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<three>field3</three>
<four>field4</four>
<five>field5</five>
<six>field6</six>
</xsl:array>
</xsl:object>
Applied XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
xmlns:str="http://exslt.org/strings">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:object/xsl:array">
<xsl:for-each select="xsl:object[Time !='00000000']">
<xsl:copy-of select = "."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
What I got:
<?xml version="1.0" encoding="UTF-8"?><xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
</xsl:object>
What is expected:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:array name="BankDetails">
<one>field1</one>
<two>field2</two>
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
<three>field3</three>
<four>field4</four>
<five>field5</five>
<six>field6</six>
</xsl:array>
</xsl:object>
The problem with your attempt is that you are copying only xsl:object elements that have the right Time - but not doing anything (i.e. copy or apply templates) for the other elements that are also children of the context xsl:array element (one, two etc). If you do not apply templates to them, then the identity transform template will not be applied to them.
You are also not copying the context xsl:array element.
I would suggest you approach it from the opposite point-of-view:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:object[Time ='00000000']"/>
</xsl:stylesheet>
Related
I will need to skip first Worker node and copy rest of the nodes using XSLT 3.0
My source XML is
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<Employee>Emp</Employee>
<ID>Identifier</ID>
</Worker>
<Worker>
<Employee>12344</Employee>
<ID>1245599</ID>
</Worker>
<Worker>
<Employee>25644</Employee>
<ID>7823565</ID>
</Worker>
</Workers>
and desired output is
<?xml version="1.0" encoding="utf-8"?>
<Workers>
<Worker>
<Employee>12344</Employee>
<ID>1245599</ID>
</Worker>
<Worker>
<Employee>25644</Employee>
<ID>7823565</ID>
</Worker>
</Workers>
and XSLT that i have is
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Worker[position() = 1]"/>
</xsl:stylesheet>
Above XSLT produces the output that I was expecting but I'm looking to see if there is a better way to skip the first node without using postion()as I'm not sure how efficient my current code is to process large files( approximately 800 MB)
I had to use following to remove white spaces from my result XML
<xsl:strip-space elements="*"/>
Can anyone check my code and provide any suggestion to improvise my code please?
===============
With Michael Kay's suggestion, my code looks like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<!-- <xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template> -->
<!-- Removed above Identity Transformation -->
<xsl:mode streamable="yes" on-no-match="shallow-copy"/>
<xsl:template match="Workers">
<xsl:copy>
<xsl:apply-templates select="tail(Worker)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would write it
<xsl:template match="Worker[1]"/>
for readability, but it's all the same.
Match patterns with positional predicates can perform badly, so you're right to be cautious, but a simple one like this should be OK. In fact the main adverse consequence is probably that Saxon will allocate preceding-sibling pointers in the TinyTree, so that it can compute the node's sibling position.
Saxon effectively implements it as
<xsl:template match="Worker[not(preceding-sibling::Worker)]"/>
and you might prefer to write it that way. However, neither form is streamable.
To make it streamable, you could drop the unwanted nodes by not selecting them:
<xsl:template match="Workers">
<xsl:copy>
<xsl:apply-templates select="tail(Worker)"/>
</xsl:copy>
</xsl:template>
which might also be fractionally faster in the non-streaming case; and it saves memory because preceding-sibling pointers aren't needed.
when I execute the below stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*" >
<xsl:copy>
<xsl:variable name="manu" select="node()|#*"/>
<xsl:apply-templates select="$manu"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I'm getting the below output:-
<?xml version="1.0" encoding="UTF-8" ?>
<A1>
<B1>
<FILES>
<StudentData>
<Student>0001</Student>
<Student>0002</Student>
</StudentData>
</FILES>
</B1>
</A1>
Now I want to select only the StudentData:-
Expected Output:-
<StudentData>
<Student>0001</Student>
<Student>0002</Student>
</StudentData>
How to match this in XSLT?
There a lots of ways. i.e. you could match on root and then just do a copy of the element using the xpath to that element like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select="A1/B1/FILES/StudentData"/>
</xsl:template>
</xsl:stylesheet>
Why using an identity template? As far as I understand your problem, the simple solution is this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="StudentData" >
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
If you want to filter the empty lines out of your output, add a template matching the text nodes for that purpose:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="StudentData" >
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="text()"/>
<!-- this tricky template prevents empty white lines in output -->
</xsl:stylesheet>
I have XML
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
<documents xsi:nil="true"/>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
And I want to process it with XSLT to copy all XML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:template match="/">
<xsl:copy-of select="//getInquiryAboutListReturn/inquiryAbouts"/>
</xsl:template>
</xsl:stylesheet>
How could I copy all XML without <documents xsi:nil="true"/> or without xsi:nil="true"?
Desired output XML
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
This simple XSLT:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- TEMPLATE #1 -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- TEMPLATE #2 -->
<xsl:template match="*[#xsi:nil = 'true']" />
</xsl:stylesheet>
...when applied to the OP's source XML:
<?xml version="1.0"?>
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
<documents xsi:nil="true"/>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
...produces the expected result XML:
<?xml version="1.0"?>
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
EXPLANATION:
The first template -- the Identity Template -- copies all nodes and attributes from the source XML document as-is.
The second template, which matches all elements with the specified, namespaced attribute equalling "true", effectively removes those elements.
Using Visual Studio to perform the transform during development the resulting xml contains text from the source xml in the destination that was contained in tags that do not match my template criteria
I was expecting that my select Group in the first template to find any elements named Group that are immediate children of CrystalReport and pass them along in the apply template call. I understood that the match filter on my second template would only take in Group's that have an attribute of Level=1 and write them out. I'd expect everything else to be ignored.
Why does "not this" appear in my output?
source
<?xml version="1.0" encoding="utf-8" ?>
<!--
UPDATE: Note that adding the xmlns attribute causes all output
to disappear unless you use Chris's second solution.
-->
<CrystalReport xmlns="urn:crystal-reports:schemas:report-detail" >
<Group Level="1">
<GroupHeader>
<Section>
<Field FieldName="apple" />
</Section>
</GroupHeader>
<Group Level="2">
not this
</Group>
</Group>
</CrystalReport>
transform
<?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:strip-space elements="*"/>
<xsl:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1><xsl:value-of select="Section/Field/#FieldName"/></tag1>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<root>
<tag1>apple</tag1>
not this
</root>
You are facing the problem where the built in templates of the XML parser are coming into play. You are apply templates to all of the Group elements, but only catching one of them with your own templates. The other is handled by the default templates which out put the values of all nodes. I suggest that you change
<xsl:template match="/CrystalReport">
into
<xsl:template match="/">
This will override the root default templates which are producing the extra output. You can find more on the built in template rules at http://www.w3.org/TR/xslt#built-in-rule
Then override the basic text() template so you final XSLT looks a bit like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="CrystalReport/Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
<xsl:template match="text()"/>
UPDATE
Or even simpler you could just match the desired elements and use something like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group[#Level='1']/GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
This will leave the default text() templates in place which can be very handy.
Try
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:crystal="urn:crystal-reports:schemas:report-detail">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/crystal:CrystalReport">
<root>
<xsl:apply-templates select="crystal:Group[#Level='1']/crystal:GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="crystal:GroupHeader">
<tag1>
<xsl:value-of select="crystal:Section/crystal:Field/#FieldName"/>
</tag1>
</xsl:template>
I have an xml as below.
<?xml version="1.0" encoding="UTF-8"?>
<books xmlns="http://www.books.com/SRK">
<name>English</name>
</books
I required the following output after translation using xsl .
<?xml version="1.0" encoding="UTF-8"?>
<books>
<name>English</name>
</books>
I need an xsl to ignore the namespace.I have tried something but its not working with namespace.
I need your help.Your help would be appreciated.
This 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:template match="#*|node()[not(self::*)]">
<xsl:copy/>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<books xmlns="http://www.books.com/SRK">
<name>English</name>
</books>
produces the wanted, correct result:
<books>
<name>English</name>
</books>
Its working only if i include the above templates if i add some other templates other than the above one then the translation is not at all working .None of the template getting executed.
Probably you are missing the declaration of the namespace for the book element. Example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://www.books.com/SRK"
exclude-result-prefixes="b">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- #dimitre's answer templates -->
<xsl:template match="b:name">
<!-- your template for name -->
</xsl:template>
</xsl:stylesheet>
Moreover, make sure to use local-name() function to get the name of an element without the related namespace.
Example
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://www.books.com/SRK"
exclude-result-prefixes="b">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="b:input">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="b:name"/>
</xsl:element>
</xsl:template>
<xsl:template match="b:name">
<xsl:element name="{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
<lhs>
<xsl:apply-templates select="following-sibling::b:lhs/b:evaluate"/>
</lhs>
</xsl:template>
<xsl:template match="b:evaluate">
Something to evaluate...
</xsl:template>
</xsl:stylesheet>
gets:
<?xml version="1.0" encoding="UTF-8"?>
<input>
<name>English</name>
<lhs>
Something to evaluate...
</lhs>
</input>
Second Example
You can create a separate transform called local-identity.xsl containing #Dimitre solution. Then you can import it in your transform. Because you have a namespace, to match elements you must change all your XPaths including the prefix you will declare in the transform, as in the following example:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:brl="http://www.xyz.com/BRL"
exclude-result-prefixes="brl"
version="1.0">
<xsl:import href="local-identity.xsl"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/brl:rule">
<!-- do your staff, select using brl prefix -->
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>