I'm getting a confusion on the way XSLT processors nodes, suppose I have an XML Doc like this:
<object>
<animal>
<man men="asd">man1</man>
<man>man2</man>
<man>man3</man>
<man>man4</man>
<cat>cat1</cat>
<cat>cat2</cat>
<cat>cat3</cat>
<cat>cat4</cat>
</animal>
<vehicule>
<car>car1</car>
<car>car2</car>
<car>car3</car>
<car>car4</car>
</vehicule>
</object>
When I have an XSLT without any template matching like the one below, it returns all text nodes and no attribute nodes, that's OK
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
</xsl:stylesheet>
But when I have one like the one below, it doesn't return anything:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="object">
</xsl:template>
</xsl:stylesheet>
Is it that if I have an explicit template for a parent node, I should have an explicit template for all child nodes of the parent nodes?
What you are seeing are simply the effects of the built-in rules, which output the text value of a node and apply the templates to all its children.
If you overwrite the built-in templates, well, your template takes effect. You want to apply the built-in rules for all children of object:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="object">
<xsl:apply-templates select="*" />
</xsl:template>
</xsl:stylesheet>
Your rule in #2 says to do nothing, so it did nothing. You need to write something in there. See xsl:copy and xsl:apply-templates.
http://cafeconleche.org/books/xmljava/chapters/ch17.html#d0e31297
As others have said, the XSLT default template rules are defined in such a way that, by default, they will match the top node of the document and then recursively process each of the child nodes, all the way to the bottom. Your template overrides the default rules for the root node, and doesn't have any instructions to do anything with it, so it doesn't go any further. If you instead had this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="object">
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
It would continue processing on downwards, using the default rules. You don't necessarily need to do anything specific to handle the child nodes as long as you send the processing their way.
Related
I am trying to make a simple selection just for a test, but it doesn't seem to work.
Here is the sml: http://pastebin.com/cwEcVEiL
And here is my xslt style:
<?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:tei="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="/tei:TEI/tei:text/tei:body">
TEST
</xsl:template>
</xsl:stylesheet>
With this style it just selects the entire xml document
but if I type match="/", then I outputs TEST once, as expected.
What is the problem?
For elements which aren't matched by any template at all, XSLT will apply built in default templates.
Unless you want this default behaviour, you should override these, e.g. :
<xsl:template match="/">
<xsl:apply-templates select="/tei:TEI/tei:text/tei:body" />
</xsl:template>
<xsl:template match="tei:body">
TEST
</xsl:template>
I have the following xml and want the output to not contain the xml declaration
i.e.
FROM
<?xml version="1.0" encoding="UTF-8"?>
<tns:MFTRNS xmlns:tns="MFTRNS" recordState="New" msgVersion="13.0">
<OSCONO>100</OSCONO>
<OSINOU>1</OSINOU>
<OSDLIX>155379</OSDLIX>
<OSPANR>AAG44780</OSPANR>
<OSWHLO>AAG</OSWHLO>
</tns:MFTRNS>
TO
<tns:MFTRNS xmlns:tns="MFTRNS" recordState="New" msgVersion="13.0">
<OSCONO>100</OSCONO>
<OSINOU>1</OSINOU>
<OSDLIX>155379</OSDLIX>
<OSPANR>AAG44780</OSPANR>
<OSWHLO>AAG</OSWHLO>
</tns:MFTRNS>
Can you get an xslt to do this and if so how?
The reason for doing this is that I want to wrap the xml in an envelope which cannot be done if the declaration is a part of the XML as it does not create a valid xml file
Thanks
If you only want to remove the declaration then a stylesheet as simple as this will do it:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:copy-of select="node()" />
</xsl:template>
</xsl:stylesheet>
But if your ultimate aim is to "wrap the xml in an envelope" then you might be better doing that directly in your XSLT, for example:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" />
<xsl:template match="/">
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
<xsl:copy-of select="node()" />
</soap:Envelope>
</xsl:template>
</xsl:stylesheet>
which will be safer than trying to combine the two files using non-XML-aware textual operations. For example, if your envelope declares a default namespace xmlns="http://example.com" then simply inserting the text of another XML document inside the envelope would change the semantics as it would move the non-prefixed elements like OSCONO into the envelope's default namespace when they should really be in no namespace. XSLT will spot this case and add the necessary xmlns="" overrides.
It's simple: you have to set the omit-xml-declaration attribute of your xsl:output element to yes.
I have the following XML
<?xml version="1.0"?>
<people><human><sex>male</sex><naxme>Juanito</naxme>
</human>
<human><sex>female</sex><naxme>Petra</naxme></human>
<human><sex>male</sex><naxme>Anaximandro</naxme></human>
</people>
and this XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:template match="/people/human[sex='male']">
<xsl:value-of select="naxme"/>
</xsl:template>
</xsl:stylesheet>
I'm expecting it to filter out the female, and it kind of works but I get odd values for the non-matching nodes:
Juanito
femalePetra
Anaximandro
I'm expecting the same output as with
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:template match="/people/human">
<xsl:if test = "sex='male'">
<xsl:value-of select="naxme"/>
</xsl:if> </xsl:template>
</xsl:stylesheet>
Thanks!
I'll expand on Daniels answer which covers a little of the why.
The reason you are getting two different outputs comes down to the built-in template rules, and how nodes and text are treated by default.
Summarising that link, if no other template exists, there are default templates that ensure every node - be it element, text, attribute, comment - will be encoutered, just to ensure that other nodes that do have rules can be processed correctly.
With this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/people/human[sex='male']">
<xsl:value-of select="naxme"/>
</xsl:template>
</xsl:stylesheet>
you have an explicit rule that says:
If you find a node that matches the XPath /people/human[sex='male] do this template.
Along with the default rule:
Find all nodes, and then process all of their children. If it is text, just output the text.
This default rule is why your template is being processed, since you have no explicit rule for the root node - / - it any every child and grandchild node are processed using the default rules, unless another exists. As such, each node is traversed using the defaults, except for the nodes matching /people/human[sex='male]. The result of this is that when you have a node that is "female" the text is being spat out instead of ignored.
However, contrast this with:
<xsl:template match="/people/human">
<xsl:if test = "sex='male'">
<xsl:value-of select="naxme"/>
</xsl:if>
</xsl:template>
Where the rule becomes:
If you find a node that matches the XPath /people/human do this template.
It just so happens that in that template, you have an extra condition that says, if it is male, then process it in some way, with no other conditions, so if a "female" node is encountered it is now blank in the output.
Lastly, the reason why Daniels answer works but could easily break, is that it changes the rule for processing text. Instead of now copying all text as in the default rules, it outputs nothing (as per the empty template. However, if you had any other templates which used xsl:apply-templates to process text and were expecting text, they would also now output nothing.
It's probably because of XSLT's built-in template rules. Try adding this template:
<xsl:template match="text()"/>
I only have access to xpath 1.0 commands and functions. I need to move the namespace declaration from the root node to a child node where that namespace starts to be used.
Source XML:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Accounts xmlns:test="http:example.com/test1">
<ParentAccount>10113146</ParentAccount>
<test1>test1</test1>
<test2>test2</test2>
<test:Siblings>
<test:CustomerNumber>10113146</test:CustomerNumber>
<test:CustomerNumber>120051520</test:CustomerNumber>
</test:Siblings>
</Accounts>
Desired XML:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Accounts x>
<ParentAccount>10113146</ParentAccount>
<test1>test1</test1>
<test2>test2</test2>
<test:Siblings xmlns:test="http:example.com/test1">
<test:CustomerNumber>10113146</test:CustomerNumber>
<test:CustomerNumber>120051520</test:CustomerNumber>
</test:Siblings>
</Accounts>
Any bright ideas?
Here's one way to do it.
When this XSLT:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Accounts">
<Accounts>
<xsl:apply-templates />
</Accounts>
</xsl:template>
</xsl:stylesheet>
...is applied against the provided XML:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Accounts xmlns:test="http:example.com/test1">
<ParentAccount>10113146</ParentAccount>
<test1>test1</test1>
<test2>test2</test2>
<test:Siblings>
<test:CustomerNumber>10113146</test:CustomerNumber>
<test:CustomerNumber>120051520</test:CustomerNumber>
</test:Siblings>
</Accounts>
...the wanted result is produced:
<?xml version="1.0"?>
<Accounts>
<ParentAccount>10113146</ParentAccount>
<test1>test1</test1>
<test2>test2</test2>
<test:Siblings xmlns:test="http:example.com/test1">
<test:CustomerNumber>10113146</test:CustomerNumber>
<test:CustomerNumber>120051520</test:CustomerNumber>
</test:Siblings>
</Accounts>
Explanation:
The explanation behind why this works starts with a section from the Namespaces in XML 1.0 spec:
The scope of a namespace declaration declaring a prefix extends from
the beginning of the start-tag in which it appears to the end of the
corresponding end-tag, excluding the scope of any inner declarations
with the same NSAttName part. In the case of an empty tag, the scope
is the tag itself.
Such a namespace declaration applies to all element and attribute
names within its scope whose prefix matches that specified in the
declaration.
In a nutshell, this means that when a namespace is declared on an element, it is, in effect, defined for use on all elements underneath that original scope. Additionally, should a namespace be used on an element without first being defined elsewhere, the appropriate definition occurs on that element.
So, using your document and my XSLT, let's see how this plays out:
The first template - The Identity Template - copies all nodes and attributes as-is from the source XML to the result XML.
The second template replaces the original <Accounts> element with a new one; incidentally, this new <Accounts> element does not define the http:example.com/test1 namespace. Finally, this template applies templates to all child elements of <Accounts>.
When the processor reaches <test:Siblings>, it sees a namespace that, although present in the original XML, has not been properly defined in the result document. As such, this definition is added to <test:Siblings>.
I encountered a peculiar difference in xslt behavior when the root element has a default namespace attribute as opposed to when it does not.
I am wondering why this difference occurs.
The XML input is
<root>
<content>xxx</content>
</root>
When the following transformation is applied
<?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:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="content">
<w>x</w>
</xsl:template>
</xsl:stylesheet>
the result is the expected
<?xml version="1.0" encoding="UTF-8"?>
<root>
<w>x</w>
</root>
But when the same transformation is applied to
<root xmlns="http://test.com">
<content>xxx</content>
</root>
the result is different and based on the application of just default templates (which effectively output the text node value 'xxx'):
<?xml version="1.0" encoding="UTF-8"?>
<root>xxx</root>
Addition
When this is the expected behavior in this case, then what match attribute value is needed to match the content element in the second case?
This is the most FAQ in XPath / XSLT.
An unprefixed element name is treated by XPath as belonging to "no namespace".
The W3C Xpath specification says:
if the QName does not have a prefix, then the namespace URI is null.
Therefore, in a document with a default namespace a refernce to an element with unprefixed name (say "someName") selects nothing, because there isn't any element in "no namespace" in the XML document, but someName means an element with name "someName", belonging to "no namespace".
The Solution:
If we want to select an element by name, we must prefix that name and the prefix must be associated with the default namespace.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://test.com" exclude-result-prefixes="x">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="x:content">
<w>x</w>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document with default namespace:
<root xmlns="http://test.com">
<content>xxx</content>
</root>
produces the wanted, correct result:
<root>
<w>x</w>
</root>
so what exactly is your question? If you're simply looking for an explanation, following is a brief one. What you're observing is the proper behavior according to the specification. When you put a namespace on something, the parser essentially treats it as a different element entirely (than an element of the same name but no namespace). Therefore, in the second situation, when you say <xsl:template match="content">, it doesn't match the <content> element in the XML file because it falls under the http://test.com namespace (by way of the namespace declaration on its parent). Therefore, the default templates take over.