Wondering if anyone can help with an XSLT issue I am facing.
I am trying to create an xslt script which will take take as input an xml document and change the values of several fields to "xxxx" I have managed to get this part working however I would now only like this to run if one field in the input xml is of a specific value (e.g. if username is jbond)
I like to have this condition within my XSLT if possible however I am having difficulty.
My current XML, XSLT, Output and expected outputs are as follows
XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<rootDoc>
<user>test</user>
<tel>12345</tel>
<zip>abcd</zip>
</rootDoc>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:if test="user = 'test'">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="tel/text()">XXXX</xsl:template>
<xsl:template match="zip/text()">XXXX</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<rootDoc/>
Expected:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl"?><rootDoc>
<user>test</user>
<tel>XXXX</tel>
<zip>XXXX</zip>
</rootDoc>
If you leave the identity transform alone, but add specific matches, XSLT will automagically find the closest match. You can customize the select="" or add more templates as necessary. hth
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="tel"><tel>XXXX</tel></xsl:template>
<xsl:template match="zip"><zip>XXXX</zip></xsl:template>
</xsl:stylesheet>
Putting the test into the identity transformation template does not make much sense, if you want to perform certain changes when a condition holds then the templates
<xsl:template match="tel/text()">XXXX</xsl:template>
<xsl:template match="zip/text()">XXXX</xsl:template>
should be changed to
<xsl:template match="rootDoc[user = 'test']/tel/text()">XXXX</xsl:template>
<xsl:template match="rootDoc[user = 'test']/zip/text()">XXXX</xsl:template>
which could be joined into
<xsl:template match="rootDoc[user = 'test']/tel/text() | rootDoc[user = 'test']/zip/text()">XXXX</xsl:template>
With a single condition, assuming it is the rootNode, you can use
<xsl:template match="/rootNode[not(user = 'test')]">
<xsl:copy-of select="."/>
</xsl:template>
then the other cases are handled by the identity transformation and the specialized templates.
Related
I need to update xml file with adding 2 to the value but within different tag
eg:
data.xml
<?xml version="1.0"?>
<data>
<tag1>2</tag1>
<tag2>6</tag2>
<tag10>7</tag10>
<string>nochange_string</string>
</data>
to updated_data.xml
<?xml version="1.0"?>
<newdata>
<tag1>4</tag1>
<tag2>8</tag2>
<tag10>9</tag10>
<string>nochange_string</string>
</newdata>
I am aware that I would need to call a method to add 2 to each tag except last 'string' tag but I am stuck to keep the tag# as it was but everything should be in the different tag called 'newdata'. How would I do this? Thanks in advance!
A method by using XSLT makes it simple.
Input XML
<?xml version="1.0"?>
<data>
<tag1>2</tag1>
<tag2>6</tag2>
<tag10>7</tag10>
<string>nochange_string</string>
</data>
XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/data">
<newdata>
<xsl:apply-templates select="#*|node()"/>
</newdata>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'tag')]">
<xsl:copy>
<xsl:value-of select=". + 2"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output XML
<?xml version='1.0' encoding='utf-8' ?>
<newdata>
<tag1>4</tag1>
<tag2>8</tag2>
<tag10>9</tag10>
<string>nochange_string</string>
</newdata>
I am trying to include parent nodes of the node I am copying.
Here is the sample file:
<A>
<B>
<C1>Text Value</C1>
<C2></C2>
<C3></C3>
<C4></C4>
</B>
</A>
I would like the output to be:
<A>
<B>
<C1>Text Value</C1>
</B>
</A>
Here is my xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:template match="/A/B/C1">
<xsl:copy>
<xsl:apply-templates select="/A/B/C1"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, my output does not show the parent nodes.
Edit:
C1 will always have the same Xpath. I would also like to include the Text of the node.
Here's one way to produce the result you show:
XSLT 1.0
<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:template match="*">
<xsl:copy>
<xsl:apply-templates select="*[descendant-or-self::C1] | C1/text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Edit: C1 will always have the same Xpath.
Well, then here's another:
<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:template match="/">
<A>
<B>
<xsl:copy-of select="A/B/C1"/>
</B>
</A>
</xsl:template>
</xsl:stylesheet>
how to replace the child tag name with xsl.
here the below is my structure of xml.
<Checkpax xmlns="http://xml.api.com/test">
<customerLevel>
<customerDetails>
<paxDetails>
<surname>MUKHERJEE</surname>
<type>A</type>
<gender>M</gender>
</paxDetails>
<otherPaxDetails>
<givenName>JOY</givenName>
<title>MR</title>
<age>11</age>
</otherPaxDetails>
<otherPaxDetails>
<title>MR</title>
</otherPaxDetails>
</customerDetails>
<staffDetails>
<staffInfo/>
<staffCategoryInfo>
<attributeDetails>
<attributeType>NA</attributeType>
</attributeDetails>
</staffCategoryInfo>
</staffDetails>
<productLevel>
<legLevel>
<legLevelIndicator>
<statusDetails>
<indicator>abc</indicator>
<action>1</action>
</statusDetails>
</legLevelIndicator>
</legLevel>
</productLevel>
<CustomerLevel>
<legLevel>
<legLevelIndicator>
<statusDetails>
<indicator>cde</indicator>
<action>1</action>
</statusDetails>
</legLevelIndicator>
</legLevel>
</CustomerLevel>
</customerLevel>
</Checkpax>
The below is my XSL file
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="customerLevel/productLevel/legLevel/legLevelIndicator/statusDetails">
<statusInformation>
<xsl:apply-templates select="#*|node()" />
</statusInformation>
</xsl:template>
</xsl:stylesheet>
here the statusDetails name should be changed as staffInformation inside the ProductLevel/LeglevelIndicator . Kindly give me the suggestion for doing this.
The below is expected result
<Checkpax xmlns="http://xml.api.com/test">
<customerLevel>
<customerDetails>
<paxDetails>
<surname>MUKHERJEE</surname>
<type>A</type>
<gender>M</gender>
</paxDetails>
<otherPaxDetails>
<givenName>JOY</givenName>
<title>MR</title>
<age>11</age>
</otherPaxDetails>
<otherPaxDetails>
<title>MR</title>
</otherPaxDetails>
</customerDetails>
<staffDetails>
<staffInfo/>
<staffCategoryInfo>
<attributeDetails>
<attributeType>NA</attributeType>
</attributeDetails>
</staffCategoryInfo>
</staffDetails>
<productLevel>
<legLevel>
<legLevelIndicator>
<statusInformation>
<indicator>abc</indicator>
<action>1</action>
</statusInformation>
</legLevelIndicator>
</legLevel>
</productLevel>
<CustomerLevel>
<legLevel>
<legLevelIndicator>
<statusDetails>
<indicator>cde</indicator>
<action>1</action>
</statusDetails>
</legLevelIndicator>
</legLevel>
</CustomerLevel>
</customerLevel>
</Checkpax>
the statusDetails name should be changed as staffInformation inside
the ProductLevel/LeglevelIndicator
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://xml.api.com/test"
exclude-result-prefixes="ns0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns0:productLevel/ns0:legLevel/ns0:legLevelIndicator/ns0:statusDetails">
<staffInformation xmlns="http://xml.api.com/test">
<xsl:apply-templates/>
</staffInformation>
</xsl:template>
</xsl:stylesheet>
A question about Martin's answer:
Martin Honnen's answer works great, but not with the root element. Let's say I have "cars" as a root element:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="foo">
<cars>
<car1 />
<car2 />
</cars>
</xsl:template>
</xsl:stylesheet>
And I want to obtain:
<xsl:template match="foo">
<cars>
<car1 />
<car2 />
<TANK />
</cars>
</xsl:template>
For this, I'd use:
<xsl:template match="cars" >
<xsl:copy>
<xsl:apply-templates/>
<TANK />
</xsl:copy>
</xsl:template>
Which outputs the exact input, without changing anything. I can try:
<xsl:template match="/" >
<xsl:copy>
<xsl:apply-templates/>
<TANK />
</xsl:copy>
</xsl:template>
But it will place the TANK node outside the stylesheet, like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="order">
<cars>
<car1/>
<car2/>
</cars>
</xsl:template>
</xsl:stylesheet><TANK/>
How to get the TANK element inside cars?
Original question:
I have a XSL that I use to transform an XML:
XML_format1 -> XSL1 -> XML_format2
I need to transform this first XSL file (using a second XSL) to obtain a third XSL file, which will output an XML with a third format. In short:
XSL1 -> XSL2 -> XSL3
XML_format1 -> XSL3 -> XML_format3
Using the following stylesheet, I am able to copy the first XSL's contents and also skip certain nodes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//skipThisNode"/>
</xsl:stylesheet>
My problem: in addition to this, I also need to change some nodes' structure (add something), from this:
<car>
<color>green</color>
<fuel>petrol</fuel>
</car>
To this:
<car>
<color>green</color>
<fuel>petrol</fuel>
<topSpeed>99</topSpeed>
</car>
LE: I could create a template to match the specific nodes that I need to add children to, like so:
<xsl:template match="car">
<color>
<!-- existing value-of -->
</color>
<fuel>
<!-- existing value-of -->
</fuel>
<topSpeed>
<!-- * new value-of * -->
</topSpeed>
</xsl:template>
But this seems like going over the top. Is there a simpler way of achieving what I want?
I would rather use
<xsl:param name="newSpeed" select="99"/>
<xsl:template match="car">
<xsl:copy>
<xsl:apply-templates/>
<topSpeed>
<xsl:value-of select="$newSpeed"/>
</topSpeed>
</xsl:copy>
</xsl:template>
that keeps the processing chain up, allowing you to add templates to transform or delete child and descendants of car elements when needed.
[edit]
I am not sure I understand your latest requirement as the input seems to be a complete stylesheet but then as the wanted output you have only shown a single template. So assuming you have the input as
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="order">
<cars>
<car1 />
<car2 />
</cars>
</xsl:template>
</xsl:stylesheet>
and you want to both transform the xsl:template's match attribute as well as the literal result elements in the template body I would use
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:template[#match = 'order']/#match">
<xsl:attribute name="{name()}">
<xsl:text>foo</xsl:text>
</xsl:attribute>
</xsl:template>
<xsl:template match="xsl:template[#match = 'order']/cars">
<xsl:copy>
<xsl:apply-templates/>
<TANK/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
that way I get (tested with Saxon 6.5.5) the result
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="foo">
<cars>
<car1/>
<car2/>
<TANK/></cars>
</xsl:template>
</xsl:stylesheet>
which is hopefully what you want (lacking proper indentation perhaps).
I have xml like below
<rnp xmsns="v1">
<ele1 line="1">
<ele2></ele2>
</ele1>
</rnp>
I want to change it to
<rnp xmsns="v2">
<ele1 line="1">
<ele2></ele2>
</ele1>
</rnp>
using xslt 1.0.
I am using below xsl.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="v2">
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="rnp">
<rnp>
<xsl:apply-templates select="*"/>
</rnp>
</xsl:template>
</xsl:stylesheet>
But this xsl does not copy the attributes so line attribute is not generated in output.
sample output
<?xml version="1.0" encoding="UTF-8"?><rnp xmlns="v2"><ele1>1
<ele2/>
</ele1></rnp>
How to change only the text of xmlns attrbiute using xslt? Is there any other way to change xmlns using xslt? I have only option of xslt 1.0.
Thanks.
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:param name="pNS" select="'v2'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[true()]">
<xsl:element name="{local-name()}" namespace="{$pNS}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to make it in the namespace "v1":
<rnp xmlns="v1">
<ele1 line="1">
<ele2></ele2>
</ele1>
</rnp>
produces the wanted, correct result:
<rnp xmlns="v2">
<ele1 line="1">
<ele2/>
</ele1>
</rnp>
Do note:
The desired new default namespace is passed to the transformation as an external parameter -- thus the smae transformation without any modification can be used in every case when the default namespace must be modified.
This unusual looking template match: <xsl:template match="*[true()]"> makes it possible to avoid the XSLT processors "recoverable ambiguity error" messages if we had coded it just as <xsl:template match="*"> and is shorter and more elegant than specifying a priority.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="v2">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<rnp xmlns="v2">
<ele1 line="1">
<ele2 />
</ele1>
</rnp>