I was given the following XML:
<root>
<items>
<item>
<title>Item</title>
<details>
<data xmlns="http://some_url">
<length>10</length>
<weight>1.2</weight>
</data>
</details>
</item>
</items>
</root>
Following XPath does not work meaning nothing is printed like the "data" element does not exists:
/root/items/item/details/data
But when I remove "xmlns" namespace attribute of "data" element it's content is printed.
How should the xpath expression look like to work without deleting "xmlns" namespace attribute of "data" element?
I'm using SAXON and XSL 1.0.
This is one of the most FAQ in XPath / XSLT:
XPath interprets an unprefixed element name as belonging to "no namespace" and this is the reason elements with unprefixed names belonging to a default (nonempty) namespace aren't selected when only their unprefixed name is specified as a node-test in an XPath expression.
The solution is either:
Create a namespace binding where a prefix (say "x") is associated with the default namespace, then specify x:elementName instead of elementName.
Use long, ugly and unreliable expressions like: *[name() = 'elementName']
Here is an XSLT transformation using the above method1. :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://some_url">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select=
"/root/items/item/details/a:data/a:weight"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied (using Saxon 6.5.4 or any other compliant XSLT 1.0 processor) on the provided XML document:
<root>
<items>
<item>
<title>Item</title>
<details>
<data xmlns="http://some_url">
<length>10</length>
<weight>1.2</weight>
</data>
</details>
</item>
</items>
</root>
The correct/wanted node is selected and its string value is copied to the output:
1.2
In XPath, you have to assign a prefix to the namespace. How you do that depends on the XPath software/library you are using, but assuming you associate the namespace URI http://some_url with the namespace prefix someUrl, you can change your XPath expression as follows:
/root/items/item/details/someUrl:data
Related
I'm porting old transformations to a new transformation platform (based on Saxon 9.9). I have an issue where the new platform creates slightly different results than the old platform (based on an ancient bug-ridden Oracle XSLT1+ implementation).
Consider the following source document:
<root xmlns="http://root.invalid">
<model xmlns="http://root.invalid">
<keys>
<id xmlns:foo="http://foo.invalid" foo:nil="true"/>
</keys>
<instance>
<id xmlns:foo="http://foo.invalid" foo:nil="true"/>
<whatever type="String">whatever</whatever>
</instance>
</model>
</root>
When the following transformation is applied:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:root="http://root.invalid"
exclude-result-prefixes="root"
>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root:id">
<id xmlns:foo="http://foo.invalid" foo:nil="true" type="String">1234</id>
</xsl:template>
</xsl:stylesheet>
The following (correct) output is generated:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://root.invalid">
<model>
<keys>
<id xmlns:foo="http://foo.invalid" xmlns="" foo:nil="true" type="String">1234</id>
</keys>
<instance>
<id xmlns:foo="http://foo.invalid" xmlns="" foo:nil="true" type="String">1234</id>
<whatever type="String">whatever</whatever>
</instance>
</model>
</root>
The problem here is that in the new platform I get a xmlns="" namespace definition that didn't exists in the old platform (that is breaking existing consumers of the output transformation).
Can you explain why the xmlns="" appers here and is there any chance I could get:
<id xmlns:foo="http://foo.invalid" foo:nil="true" type="String">1234</id>
instead (with Saxon 9.9 based XSLT)?
If you want your result element(s) to be in that namespace then use e.g. xmlns="http://root.invalid" on the xsl:stylesheet or at least on the literal result id elements e.g. <id xmlns="http://root.invalid" .../>. Or use an xsl:copy and add the new attribute and text e.g. <xsl:copy><xsl:copy-of select="#*"/><xsl:attribute name="type">string</xsl:attribute>1234</xsl:copy> in your template matching the id elements.
You are creating the id element using the literal result element in the stylesheet:
<id xmlns:foo="http://foo.invalid" foo:nil="true" type="String">1234</id>
and the in-scope namespaces for this element also include
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:root="http://root.invalid"
The specification for literal result elements says that
(a) the expanded name (uri + local) of the element in the result tree must be the same as the expanded name of the literal result element: that is (no namespace, "id").
(b) the in-scope namespace bindings of the element in the result tree should include all in-scope namespaces of the literal result element, other than any excluded namespace bindings. The excluded namespace bindings are those for "xsl" and "root"; this leaves the binding for "foo" which is therefore present in the result.
The reason the xmlns="" is generated is that it is the only way to meet the requirement that the id element should be in no namespace. If it were not generated, the id element would be in the namespace "http://root.invalid".
If you don't want the xmlns="", that probably means you want the id element to be in the namespace "http://root.invalid", and you can achieve this by adding the declaration xmlns="http://root.invalid" to the literal result element in the stylesheet.
I can't comment on why the Oracle XDK got this wrong.
This rule, based on the answer by #MartinHonnen, produced the wanted results:
<xsl:template match="root:id">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="type">string</xsl:attribute>
<xsl:text>1234</xsl:text>
</xsl:copy>
</xsl:template>
i've already a new problem.
I have to merge lists via xsl, problem on this is that the key in the lookup-list must be concatinated by 2 values.
The lists can be relative large with thousands,ten-thousands in some cases even more of entries in both lists. In advance of large sizes of this lists, i have to look on performance and memory. It could be that this will later implemented in an web-service-client, so it must run quick and resource-saving.
Merging the exisiting Elements in List1 and List2 is done and was not complicated, but now i have to check both lists on non-exisiting elements in other list.
I tried to negate the for-each select statement but failed and it is presumably the wrong way.
InputXML-example
<ROOT>
<getObjectListResponse>
<item>
<Key>1111111:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>2222222:bbbb</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>3333333:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
</getObjectListResponse>
<LookupList>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</LookupList>
</ROOT>
The first part, find the existing parts in both lists is already done.
The second part is to find non-existing parts in List 1 to List 2 and List 2 to List 1.
I wanna like to do this in for-each, so you get only non-exisiting entries from List1 which does not exists in List2.
My Problem ist to lookup in for-each context with an concatinated key, from all DATA in LookupList.
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/*[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
But everything i tried fails, with no results or wrong result.
How can this be done?
I tried this and some others, but nothing will work.
Thanks in advance
I would use keys for the cross-reference, here is an XSLT 3.0 (as supported by Saxon 9.8 all editions or Altova XMLSpy/Raptor) stylesheet as obviously one sample is a good use case for a composite key:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output indent="yes"/>
<xsl:key name="data" match="DATA" composite="true" use="KeyPart1, KeyPart2"/>
<xsl:key name="item" match="item" use="Key"/>
<xsl:template match="ROOT">
<xsl:copy>
<items-not-in-data>
<xsl:copy-of select="getObjectListResponse/item[not(key('data', (substring-before(Key, ':'), substring-after(Key, ':'))))]"/>
</items-not-in-data>
<data-not-in-items>
<xsl:copy-of select="LookupList/DATA[not(key('item', concat(KeyPart1, ':', KeyPart2)))]"/>
</data-not-in-items>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For your sample data I get
<ROOT>
<items-not-in-data/>
<data-not-in-items>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</data-not-in-items>
</ROOT>
Of course XSLT 3.0 and a composite key is not mandatory, you could as well use XSLT 2.0 and use a single key value concat(KeyPart1, KeyPart2).
I want to display XML document with XSLT by following this example from w3schools: http://www.w3schools.com/xml/xml_xsl.asp. The XSLT transformation to HTML will be done in the browser when opening the XML document.
Now, I have difficulties a) getting the local name and 2) getting the namespace of the attribute content of type QName in two separate expressions.
Example
<service xmlns:ns3="http://www.mycompany.com/" name="ns3:PersonService">
<serviceInterface name="ns3:PersonServiceInterface">
<operation>...</operation>
</serviceInterface>
Questions
What XPATH expression will return PersonService as content of attribute name? <xsl:value-of select="#name"/> returns ns3:PersonService but i don't want the namespace prefix.
What XPATH expression will return http://www.mycompany.com/ as the namespace of the attribute name?
1.What XPATH expression will return PersonService as content of attribute name?
Assuming that service is a child of the top element (you haven't provided a complete and well-formed XML document), use:
substring-after(/*/service/#name, ':')
2.What XPATH expression will return http://www.mycompany.com/ as the namespace of the attribute name?
Under the same assumptions as above, use:
/*/service/namespace::*[name() = substring-before(../#name, ':')]
XSLT-based verification:
<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:value-of select="substring-after(/*/service/#name, ':')"/>
============
<xsl:value-of select=
"/*/service/namespace::*[name() = substring-before(../#name, ':')]"/>
</xsl:template>
</xsl:stylesheet>
this XSLT transformation, when applied on the following XML document (the provided one, but completed):
<t>
<service xmlns:ns3="http://www.mycompany.com/"
name="ns3:PersonService">
<serviceInterface name="ns3:PersonServiceInterface">
<operation>...</operation>
</serviceInterface>
</service>
</t>
evaluates the two XPath expressions and copies the results of these evaluations to the output:
PersonService
============
http://www.mycompany.com/
I have a input xml with a default namespace. eg as below.
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="aaa">
<subroot>
<country>aaa</country>
<country>bbb</country>
<country>ccc</country>
</subroot>
</root>
While transforming I use xpath-default-namespace="aaa" because otherwise xpaths will not match. Again I have to read a lookup xml using xsl key function. eg as below
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xpath-default-namespace="aaa" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="LookupDoc" select="document('lookup.xml')" />
<xsl:key name="ObjectType-lookup" match="lookup" use="#att1" />
<xsl:template match="//country">
<countrynew>
<xsl:apply-templates select="$LookupDoc/*">
<xsl:with-param name="curr-code" select="string(.)" />
</xsl:apply-templates>
</countrynew>
</xsl:template>
<xsl:template match='lookups'>
<xsl:param name="curr-code" />
<xsl:value-of select="key( 'ObjectType-lookup' , normalize-space($curr-code))/#att2" />
</xsl:template>
with default namespace in stylesheet element xpath "//country" works fine. The problem arise when I read the lookup xml which doesn't have any namespace. eg:
<?xml version="1.0" encoding="UTF-8"?>
<x:lookups>
<lookup att1="aaa" att2="zzz"/>
<lookup att1="bbb" att2="yyy"/>
<lookup att1="ccc" att2="xxx"/>
</x:lookups>
Is there any way that I can specify in template maching "lookups" to ignore xpath-default-namespace or to match any namespace including no namespce?
Thank you
Is there any way that I can specify in template maching "lookups" to ignore xpath-default-namespace or to match any namespace including no namespce?
You can specify xpath-default-namespace anywhere in the stylesheet: an XPath expression will look up the tree and use the "nearest ancestor" value.
For any element in the stylesheet, this attribute has an effective value, which is the value of the [xsl:]xpath-default-namespace on that element or on the innermost containing element that specifies such an attribute
(From the XSLT 2.0 spec)
So you could say
<xsl:template match='lookups' xpath-default-namespace=''>
to override the default namespace specified on the xsl:stylesheet element. You can even specify it on a literal result element in the stylesheet, as xsl:xpath-default-namespace:
<something xsl:xpath-default-namespace="bbb" attr="{foo}" />
This would create a <something attr="xxx" /> where xxx is the value of the {bbb}foo child element of the current context node.
I did solve problem but sure there will be other ways. What I did was I move template match for lookup and key xsl function to a different document and in xsl stylesheed element I put xpath-default-namespace="". So for those xpath matching xsl use default namespace as none.
Still I'm curious weather there is a way to specify in template itself to use no namespace while matching.
I was hoping I could get a little assistance with an XSLT transform. I can't seem to get it right.
Here is a sample of the source xml document:
<?xml version="1.0" encoding="UTF-8"?>
<Locations>
<header>
<location>Location Field</location>
<job_function>Job Function Field</job_function>
<count>Count</count>
</header>
<data>
<location>2177</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>OPS</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>SLS</job_function>
<count>5</count>
</data>
<data>
<location>2179</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2179</location>
<job_function>SEC</job_function>
<count>1</count>
</data>
</Locations>
I want to transform it into the following format:
<Locations>
<data>
<PeopleSoftID>2177</PeopleSoftID>
<ADM>1</ADM>
<OPS>1</OPS>
<SLS>5</SLS>
<TotalCount>7</TotalCount>
</data>
<data>
<PeopleSoftID>2179</PeopleSoftID>
<ADM>1</ADM>
<SEC>1</SEC>
<TotalCount>2</TotalCount>
</data>
</Locations>
So basically, as you can see in the sample source document there are multiple elements that have the same value. In the destination document, there should now only be one record (<PeopleSoftID> element) per <location> element value in the source document. Since there were 3 <location> elements with the value of 2177, the destination document now has just 1 <PeopleSoftID> element that contains that value. The value of the <job_function> element in the source document becomes an element in the destination document. The value of that new element ends up being the sibling value of the <count> element from the source document. The <TotalCount> element in the destination document is the SUM of the values of all the new elements that are generated from the source <job_function> element.
I hope that explanation did not confuse anybody =).
I am a little new to XSLTs still so I am having trouble getting the logic right on this.
I can only use XSLT 1.0 too.
If I did not provide enough information let me know, and I will try to provide more as soon as I am able.
Thanks guys!
Read up on xsl:key and grouping with the Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<!--Group the data elements by their location values -->
<xsl:key name="data-by-location" match="data" use="location" />
<xsl:template match="Locations">
<xsl:copy>
<!--Get a distinct list of location values,
using the Muenchian Method -->
<xsl:for-each
select="data[generate-id() =
generate-id(key('data-by-location', location)[1])]">
<xsl:copy>
<PeopleSoftID>
<xsl:value-of select="location"/>
</PeopleSoftID>
<!--For every data element matching this location... -->
<xsl:for-each select="key('data-by-location',location)">
<!--Create an element using the job_function
as the element name -->
<xsl:element name="{job_function}">
<!--The value of the count element
as the value of the generated element-->
<xsl:value-of select="count"/>
</xsl:element>
</xsl:for-each>
<TotalCount>
<!--calculate the sum of all the count element values
for this location -->
<xsl:value-of select="sum(key('data-by-location',
location)/count)"/>
</TotalCount>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>