XSLT stylesheet empty paired tags required instead of self-closing tags for null values - xslt

I have an input which can sometimes have value and sometimes not. Like value1=ABC or value1=""
During my xsl transformation I have my code with the following line
<element name="test"><xsl:value-of select="$value1"/><element>
The output of the above code when value is present is
<element name="test">ABC</element>
When the value is not present, the output is
<element name="test"/>
Now, I want it to look like
<element name="test"></element>
instead of
<element name="test"/>
Is it possible to get the required output?
If yes, then how to do it?

Try adding <xsl:output method="html"/>.
Example...
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="test1"/>
<xsl:variable name="test2" select="'value'"/>
<element name="test1"><xsl:value-of select="$test1"/></element>
<element name="test2"><xsl:value-of select="$test2"/></element>
</xsl:template>
</xsl:stylesheet>
produces (when applied to any XML instance):
<element name="test1"></element>
<element name="test2">value</element>

Related

Filter elements by attribute values

I'd like to filter all elements with a duplicate attribute value.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<elements>
<element pos="1"/>
<element pos="2"/>
<element pos="2"/>
<element pos="3"/>
</elements>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/elements/element">
<xsl:if test="#pos != preceding-sibling::element/#pos">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
<xsl:template match="text() | #*"/>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<element pos="2"/>
<element pos="2"/>
<element pos="3"/>
Output (should be):
<?xml version="1.0" encoding="UTF-8"?>
<element pos="1"/>
<element pos="2"/>
<element pos="3"/>
What am I doing wrong? I don't get it. :-(
I would probably use an XSLT key to group the elements and identify duplicates. Perhaps something like this:
<xsl:key name="elements-by-pos" match="element" use="#pos"/>
<xsl:template match="elements/element">
<xsl:copy-of select="
.[generate-id(.) = generate-id(key('elements-by-pos', #pos)[1])]
"/>
</xsl:template>
But as to why your code doesn't work as you expect, I think it's because this XPath expression doesn't mean what you think it does:
#pos != preceding-sibling::element/#pos
This expression does not mean "are there no previous #pos which are equal to the current #pos?"; rather it means "are there any previous #pos which are unequal to the current #pos?"
You are using the != operator to compare a single #pos attribute with the set of #pos attributes which appear earlier in the document. The operator will return a true value if any member of the set satisfies the condition, i.e. if there is at least one preceding #pos attribute which is not equal to the current #pos. See the section on Boolean expressions in the XPath 1.0 spec for details.
Walking through your example:
For the first #pos (whose value is 1), there are no preceding
#pos attributes, so there are none which are unequal to the current
#pos.
For the second #pos (2) there is a previous #pos whose
value is unequal (i.e. the first #pos, whose value is 1).
The third #pos (whose value is also 2) will be copied because the first #pos 1 again satisfies the != condition.
The final #pos (3) appears because any of the
previous #pos attributes will satisfy the condition.
If you were to change that expression to not(#pos = preceding-sibling::element/#pos) then you would see the behaviour you want.

A sequence of more than one item is not allowed as the second argument of concat()

The below xsl works fine if I do not bring in the "other_location_postal_code" field, which is commented here.
This is because there are multiple records if I bring in that field.
How can I have this xsl evaluate each record so it would write this record twice, once for the one "other location postal code" and the other?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:e="http://www.taleo.com/ws/tee800/2009/01" xmlns:fct="http://www.taleo.com/xsl_functions" exclude-result-prefixes="e fct">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:param name="OUTBOUND_FOLDER"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:Requisition"/>
</source>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:variable name="job_id" select="e:ContestNumber"/>
<xsl:variable name="other_location_postal_code" select="e:JobInformation/e:JobInformation/e:OtherLocations/e:Location/e:NetworkLocation/e:NetworkLocation/e:ZipCode"/>
<job>
<job_id>
<xsl:value-of select="concat('<','![CDATA[',$job_id,']]','>')"/>
</job_id>
<other_location_postal_code>
<xsl:value-of select="concat('![CDATA[',$other_location_postal_code,']]')"/>
</other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>
I want it to come out like so:
<?xml version="1.0" encoding="UTF-8"?>
<source>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[77382]]></other_location_postal_code>
</job>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[37567]]></other_location_postal_code>
</job>
</source>
The initial XML looks like so:
<?xml version="1.0" encoding="UTF-8"?>
-<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
-<soapenv:Body>
-<ns1:getDocumentByKeyResponse xmlns:ns1="http://www.taleo.com/ws/integration/toolkit/2005/07" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-<Document xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07">
-<Attributes>
<Attribute name="count">1</Attribute>
<Attribute name="duration">0:00:00.088</Attribute>
<Attribute name="entity">Requisition</Attribute>
<Attribute name="mode">T-XML</Attribute>
<Attribute name="version">http://www.taleo.com/ws/tee800/2009/01</Attribute>
</Attributes>
-<Content>
-<ExportTXML xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07" xmlns:e="http://www.taleo.com/ws/tee800/2009/01">
-<e:Requisition>
<e:ContestNumber>15000005</e:ContestNumber>
-<e:JobInformation>
-<e:JobInformation>
-<e:OtherLocations>
-<e:Location>
<e:ZipCode>77002</e:ZipCode>
</e:Location>
-<e:Location>
<e:ZipCode>77050</e:ZipCode>
</e:Location>
</e:OtherLocations>
</e:JobInformation>
</e:JobInformation>
</e:Requisition>
</ExportTXML>
</Content>
</Document>
</ns1:getDocumentByKeyResponse>
</soapenv:Body>
</soapenv:Envelope>
The error message simply says that an argument to the concat function is a sequence of more than one node. So some of your variable selects two or more nodes and concat expects as single node for each of its arguments. You will need to decide what you want to output, either only the first node with $var[1] or the concatenation with string-join($var, ' ').
Note that you don't need all those attempts to output CDATA section, you can tell the XSLT processor the cdata-section-elements on the xsl:output direction.
As you have now posted more details here is one suggestion to map each ZipCode element to job element:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:e="http://www.taleo.com/ws/tee800/2009/01"
exclude-result-prefixes="e">
<xsl:output indent="yes" cdata-section-elements="job_id other_location_postal_code"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:OtherLocations/e:Location/e:ZipCode"/>
</source>
</xsl:template>
<xsl:template match="e:ZipCode">
<xsl:apply-templates select="ancestor::e:Requisition">
<xsl:with-param name="zc" select="current()"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:param name="zc"/>
<job>
<job_id><xsl:value-of select="e:ContestNumber"/></job_id>
<other_location_postal_code><xsl:value-of select="$zc"/></other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>

Two questions about XSLT

I have now two questions about xslt.
First, I want to describe all of elements which without any attribute. Like
<element id="p0" attribute="a1"/>
<element id="p1"/>
<element id="p2"/>
I need a group of elements without attribute="a1", so just need p1 and p2. With XSLT, should I write <xsl:if test="element[#attribute]=''">? Because when I test it, I found it doesn't work. So please help me.
The second is, I want to make the output result not in the same line. Like
right:
t11
t22
t33
wrong:
t11t22t33
which XSLT word should I write? Thanks a bunch.
All relative paths in an xsl:template are evaluated against the node that was matched by the template. So <xsl:if test="element[#attribute]=''"> is testing for the presence of a child of the current node called element that matches your condition. If this test is located in a template that matches element nodes, then it's not going to work. You should use . to refer to the current node.
The boolean expression element[#attribute]='' is looking for an element node that has an attribute called attribute and that is itself empty. It doesn't test anything about the content of the attribute attribute.
Basically, you need to understand the following template:
<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="element">
<xsl:text>element position=[</xsl:text>
<xsl:value-of select="count(preceding-sibling::element) + 1"/>
<xsl:text>] / </xsl:text>
<xsl:choose>
<xsl:when test=".[not(#attribute)]">
<xsl:text>no #attribute</xsl:text>
</xsl:when>
<xsl:when test=".[#attribute='']">
<xsl:text>empty #attribute</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>non-empty #attribute</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to this test document:
<root>
<element id="p0" attribute="a1">one</element>
<element id="p1" attribute="test"></element>
<element id="p1" attribute=""></element>
<element id="p2">three</element>
</root>
element position=[1] / non-empty #attribute
element position=[2] / non-empty #attribute
element position=[3] / empty #attribute
element position=[4] / no #attribute
This template also answers your second question about newlines.
With this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="no" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:for-each select="root/element[not(#attribute='a1')]">
<xsl:value-of select="#id"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied to this input:
<root>
<element id="p0" attribute="a1"/>
<element id="p1"/>
<element id="p2"/>
</root>
outputs:
p1
p2

Copy comments that are after the end of root tag using XSLT

I have an XML which needs to be reordered and stored in another file. I have done an xslt for that and it works fine.
However if there are comments after the xml ends then these comments are not copied. I need an xslt statement that copies comments which are present
after the root tag ends
Below is a code that explains the following
Original XML
<Company>
<Employee id="100" Name="John" >
<Salary value="15000"/>
<Qualification text="Engineering">
<State name="Kerala" code="02">
<Background text="Indian">
</Employee>
</Company>
<!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
XSLT Transformation code
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output indent="yes" omit-xml-declaration="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="Employee">
<xsl:copy>
<xsl:apply-templates select="Qualification"/>
<xsl:apply-templates select="Salary" />
<xsl:apply-templates select="Background"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output Obtained
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Employee>
<Qualification text="Engineering" />
<Salary value="15000" />
<Background text="Indian" />
</Employee>
</Company>
Output Required
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Employee>
<Qualification text="Engineering" />
<Salary value="15000" />
<Background text="Indian" />
</Employee>
</Company>
<!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
There's nothing wrong with your transformation. I ran the same XSLT over the same XML (corrected to make it well-formed) using xsltproc and I get the correct output including the trailing comments (though they have lost their original indentation and spacing due to the strip-space and the indent="yes" in the stylesheet)
<Company>
<Employee>
<Qualification text="Engineering"/>
<Salary value="15000"/>
<Background text="Indian"/>
</Employee>
</Company><!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
It looks like whatever processor or XML parser you're using (presumably a Microsoft one judging by the xmlns:msxsl="urn:schemas-microsoft-com:xslt") is ignoring comments after the end tag of the document element.

XPath - Selecting nodes with one similar and one different attribute

So I'm having trouble understanding why one XPath expression gets the nodes I want, while the other doesn't.
First, the xml:
<doc>
<source id="225" clientID="567" matterID="225" level="2" />
<source id="226" clientID="993" matterID="226" level="2" />
<dest id="185" level="7" />
<dest id="226" level="7" />
</doc>
The keys in my xsl template are defined as follows:
<xsl:key name="sourceId" match="//source" use="#id" />
<xsl:key name="destId" match="//dest" use="#id" />
<xsl:key name="destLevel" match="//dest" use="#level" />
What I'm looking for are the source nodes, that match dest nodes on id, but have a different level attribute. The apply template that I figured would work in my head is the following:
<xsl:apply-templates select="source[key('destId', #id) and not(key('destLevel', #level))]" mode="update" />
But that doesn't seem to work. A colleague suggested putting a not around an expression that matches the nodes I don't want, and after a lot of trial and error, I thought this might work, to no effect:
<xsl:apply-templates select="source[not(not(key('destId', #id)) or not(key('destLevel', #level)))]" mode="update" />
Can anyone please walk me through what I need in order to solve this?
Edit: I previously thought I'd solved this with the second query, but it seems I was mistaken.
====Solution====
Dimitre Novatchev has a detailed breakdown of different ways to solve this, but my ultimate solution was actually slightly different than his.
In essence, I created a virtual key with the concat() function that combined the two attributes. That way, I could find nodes that matched the id, but not the id-level combo.
Extra key:
<xsl:key name="destByIdAndLevel" match="//dest" use="concat(#id,'+',#level)" />
Changed apply-template call:
<xsl:apply-templates select="source[key('destId', #id) and not(key('destByIdAndLevel',concat(#id,'+',#level)))]" mode="update" />
I. 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="source">
<xsl:copy-of select=
"self::*[../dest[#id = current()/#id and not(#level=current()/#level)]]"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (an additional source element added to the provided XML document -- to verify more cases):
<doc>
<source id="185" clientID="567" matterID="225" level="7" />
<source id="225" clientID="567" matterID="225" level="2" />
<source id="226" clientID="993" matterID="226" level="2" />
<dest id="185" level="7" />
<dest id="226" level="7" />
</doc>
produces the wanted, correct result:
<source id="226" clientID="993" matterID="226" level="2"/>
II. Solution using one key:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kDestById" match="dest" use="#id"/>
<xsl:template match="/*">
<xsl:copy-of select=
"source[key('kDestById',#id)
and
not(#level=key('kDestById',#id)/#level)]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), again the same wanted, correct result is produced:
<source id="226" clientID="993" matterID="226" level="2"/>
III. Solution with two keys:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kDestById" match="dest" use="#id"/>
<xsl:key name="kDestByLevel" match="dest" use="#level"/>
<xsl:template match="source">
<xsl:copy-of select=
"self::*
[key('kDestById',#id)
and
key('kDestById',#id)
[not(count(.|key('kDestByLevel',current()/#level))
=
count(key('kDestByLevel',current()/#level))
)
]
]"/>
</xsl:template>
</xsl:stylesheet>
This again produces the wanted, correct result:
<source id="226" clientID="993" matterID="226" level="2"/>
source[key('destId', #id) and not(key('destLevel', #level))]
This gives source nodes which have an id the same as some dest nodes, and also have a level which is not the same as any dest nodes. Note that it doesn't require a particular dest node to have the same id but different level.
I think you are looking for something like this:
source[set:difference(key('destId', #id), key('destLevel', #level))]
set:difference() is from EXSLT.