Change value of an XML attribute via xsl - xslt

I have an XML file:
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='discovery.iso' index='1'/>
<backingStore/>
<target dev='hdd' bus='ide'/>
<readonly/>
<alias name='ide0-1-1'/>
<address type='drive' controller='0' bus='1' target='0' unit='1'/>
</disk>
and want to change a bus='ide' to bus='scsi'
my template looks like, but when I run it...
<?xml version="1.0" ?>
<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:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="disk[#type='file']/target">
<xsl:copy>
<xsl:attribute name="bus">scsi</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I got an error then I run my Terraform:
xdr:DecodeUint: EOF while decoding 4 bytes - read
What I did miss?

Your stylesheet lacks a template that copies all nodes that are not matched by your given template:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>

Related

XSLT create new tag if exists else update tag if exists

Is there a way for me to update the label element to Selma if "LastName" exists and if the label LastName doesn't exist then add the "LastName" and "label" elements to the XML?
<xml>
<udfs>
<udf>
<desc>FirstName</desc>
<label>Sam</label>
</udf>
<udf>
<desc>LastName</desc>
<label>Selman</label>
</udf>
</udfs>
</xml>
Here's what I have right now:
<xsl:stylesheet>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="udf[desc='LastName']/fieldValue">
<xsl:value-of select="'Selma'"/>
</xsl:template>
<xsl:template match="udf[not(desc='LastName')]">
<desc>LastName</desc>
<label>Selma</label>
</xsl:template>
</xsl:stylesheet>
I think you want to do:
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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="udf[desc='LastName']/label">
<label>Selma</label>
</xsl:template>
<xsl:template match="udfs[not(udf/desc='LastName')]">
<xsl:copy>
<xsl:apply-templates/>
<udf>
<desc>LastName</desc>
<label>Selma</label>
</udf>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

xslt for converting one element to attribute among multiple

I have an xml with subelements and need to change one of the subelement to attribute using XSLT 1.0
<TransportationRequest>
<actionCode>01</actionCode>
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
The Expected xml is as below using the XSLT code:
<TransportationRequest actionCode="01">
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
First, close the root tag on your xml:
<TransportationRequest>
<actionCode>01</actionCode>
<ContractConditionCode>DC</ContractConditionCode>
<ShippingTypeCode>17</ShippingTypeCode>
<MovementTypeCode>3</MovementTypeCode>
<DangerousGoodsIndicator>false</DangerousGoodsIndicator>
<DefaultCurrencyCode>SAR</DefaultCurrencyCode>
</TransportationRequest>
then this xsl will do what you asked for:
<?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" encoding="UTF-8" />
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<!-- Copy every node as is -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TransportationRequest">
<!-- transform the TransportationRequest node in a special way -->
<xsl:element name="TransportationRequest">
<xsl:attribute name="actionCode"><xsl:value-of select="actionCode" /></xsl:attribute>
<!-- don't transform the actionCode node (is in the attribute) -->
<xsl:apply-templates select="#*|node()[name()!='actionCode']"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
You could to it like this :
<?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="TransportationRequest">
<xsl:copy>
<xsl:attribute name="actionCode">
<xsl:value-of select="actionCode"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="actionCode"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/naZYrpW/1

Filter out elements and replace element value in one step

I am trying to filter out elements, and rename element value, but I can't get it to work:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="xml">
<xsl:copy>
<xsl:for-each select="product[matches(code, 'C17.*[^V]$')]">
<xsl:copy>
<xsl:copy-of select="#*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Example input data:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<product>
<code>C17020</code>
<title>Apple</title>
</product>
<product>
<code>C1723V</code>
<title>Samsung</title>
</product>
</xml>
I want to leave <product>'s starting with C17, but not ending to V. I use C17.*[^V]$ regex for this. This part is working.
The problem is with renaming title function. If I add this step to a new XSLT with code:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
at the begin, then it works.
What I am doing wrong here?
The problem is you are doing <xsl:copy-of select="#*|node()"/> in your template matching xml. This will copy the attributes and child nodes, buy will not apply any templates. So your template matching title is just not used.
You need to use xsl:apply-templates here, but also include the identity template (the template you mention using in your new XSLT code) which ensures code gets copied too
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xml">
<xsl:copy>
<xsl:for-each select="product[matches(code, 'C17.*[^V]$')]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note you can actually simplify your XSLT. Rather than being explicit in what you want to copy, by using the identity template you can instead have templates to remove what you don't want to copy....
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="product[not(matches(code, 'C17.*[^V]$'))]" />
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Another thing to note is that matches and replace is for XSLT 2.0 only.

Copying all XML elements and updating one of them in XSL

I have a requirement in my project some thing like this,
I have to copy all the elements from an xml and for few elements I have to update if present else I have to add it.
For example in the below xml I have an element Extrensic name "taxIncluded"> , in translated xml I want the value of it be updated.If it is missing I have to include it.
input xml 1
<?xml version="1.0" encoding="UTF-8"?>
<InvoiceHeader>
<Item1>
Item description
</Item1>
<Extrensic name="taxIncluded">
<percentage>
10%
</percentage>
</Extrensic>
</InvoiceHeader>
output
<?xml version="1.0" encoding="UTF-8"?>
<InvoiceHeader>
<Item1>
Item description
</Item1>
<Extrensic name="taxIncluded">
<percentage>
20%
</percentage>
</Extrensic>
</InvoiceHeader>
input xml 2
<?xml version="1.0" encoding="UTF-8"?>
<InvoiceHeader>
<Item1>
Item description
</Item1>
</InvoiceHeader>
output
<?xml version="1.0" encoding="UTF-8"?>
<InvoiceHeader>
<Item1>
Item description
</Item1>
<Extrensic name="taxIncluded">
<percentage>
20%
</percentage>
</Extrensic>
</InvoiceHeader>
I tried creating xsl but it is not working as expected, I thought of including it here but it is a very big xsl, in the above xml example I added only a part of it.
Could some one please help me how to do it?
Here are two ways to do it with XSLT 2.0 ...
Method 1:
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="taxIncluded" select="20" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="InvoiceHeader[not( Extrensic[#name='taxIncluded'])]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<Extrensic name="taxIncluded">
<percentage>
<xsl:value-of select="$taxIncluded" />%
</percentage>
</Extrensic>
</xsl:copy>
</xsl:template>
<xsl:template match="Extrensic[#name='taxIncluded']/percentage">
<xsl:copy>
<xsl:value-of select="$taxIncluded" />%
</xsl:copy>
</xsl:template>
</xsl:transform>
Method 2:
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="taxIncluded" select="20" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="InvoiceHeader">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:variable name="Ex" as="element(Extrensic)?">
<xsl:if test="not( Extrensic[#name='taxIncluded'])">
<Extrensic name="taxIncluded" />
</xsl:if>
</xsl:variable>
<xsl:apply-templates select="$Ex" />
</xsl:copy>
</xsl:template>
<xsl:template match="Extrensic[#name='taxIncluded']">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<percentage>
<xsl:value-of select="$taxIncluded" />%
</percentage>
</xsl:copy>
</xsl:template>
</xsl:transform>

Xslt to translate one to another and adding a row

Say I have an XML like this. I want to introduce a <row3> element.
<create xmlns="urn:partner.com"
<objects xmlns:p0="urn:s.partner.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="p0:IED">
<row1 xmlns="urn:s.partner.com">BookingDataFromCastIron</row1>
<row2 xmlns="urn:s.partner.com">Csv</row2>
</objects>
</create>"
I am using the following XSLT. But it is giving the same XML as output. Am I missing anything?
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="objects">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:element name="type">IED</xsl:element>
</xsl:copy>
</xsl:template>
It is all to do with the namespaces, basically your template match above only hits object if there is no namespace in the source XML. However you do have a namespace set xmlns="urn:partner.com" so you need to use the following to remove the binding to the namespace: <xsl:template match="*:objects">.
I think you are looking for this, but it defiantly pushes you in the right direction:
<xsl:stylesheet version="2.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()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*:objects">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:element name="row3" xmlns="urn:s.partner.com">IED</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<create xmlns="urn:partner.com">
<objects xmlns:p0="urn:s.partner.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row1 xmlns="urn:s.partner.com">BookingDataFromCastIron</row1>
<row2 xmlns="urn:s.partner.com">Csv</row2>
<row3 xmlns="urn:s.partner.com">IED</row3>
</objects>
</create>