I need the following output for bizzare system which expects same xmlns declared in parent and child and refuses to work otherwise. I.e that's what expected:
<root xmlns="http://something">
<element xmlns="http://something" />
</root>
I can create xmlns in root with
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="root" namespace="http://something">
<xsl:element name="node" namespace="http://something" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
However it doesn't add xmlns into childnode because node's parent has the same xmlns. How to force XSLT to write xmlns disregarding parent?
The XML schema specification expressly prohibits attributes named xmlns, so an XSLT stylesheet cannot create such attributes directly using <xsl:attribute>. I can only see two options for you...
One option is to create dummy attributes using a different name (e.g. xmlnsx):
<xsl:template match="/">
<xsl:element name="root">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
<xsl:element name="node">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:template>
... and then replace all occurrences of the attribute xmlnsx with xmlns in some post-processing step (such as a SAX filter or other stream editor). However, this solution involves inserting a non-XSLT step into the pipeline.
The other option is pure, if ugly, XSLT. You could generate the required XML directly, using xsl:text and disable-output-escaping, like this:
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><root xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"><node xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"></root></xsl:text>
</xsl:template>
Note that the XSLT 1.0 specification is pretty loose when it comes to serialization, so a particular XSLT processor could still conceivable strip the redundant namespace declarations from this second solution. However, it worked in the four processors that I tried (namely Saxon, MSXML, MSXML.NET and LIBXML).
Related
Quick Question: Is there a way to increment the predicate of an XPATH, by using a variable, like itereating through an array in C? For example /XPATH/element[i]
I am trying to use an XSL to access data from an XML using XPATHS. The XML is the output of a database where the parent node is the table name and its children are the columns. The XSL must be able to convert the text value of the children into attributes with the column name of the element of the table name.
The problem I am trying to solve is that each table can have multiple rows which is outputted to the XML as sibling nodes with the same names. There could be infinite rows in any table so I am trying to use a for-each with the XPATH of the table name to process each row. This works but when I try to use the document function with the XPATH with a predicate to the first XPATH and then the next XPATH and then the next, I do not know how to do it. I can only access the first XPATH. I want a way to be able to access the next XPATH on each iteration of the for-each. Is there anything which can increment each loop and that the predicate and use to point to the next XPATH?
The XML code below is a sample which I am using for testing, it is called DB.xml:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
Below is the XSL I am using:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="dataset/rtbp">
<xsl:element name="RTBP">
<xsl:attribute name="CFMtype">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmtype" />
</xsl:attribute>
<xsl:attribute name="CFMid">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmid" />
</xsl:attribute>
<xsl:text>
</xsl:text>
<xsl:for-each select="/dataset/FunctionSet">
<xsl:element name="FunctionSet">
<xsl:attribute name="RTBP__CFMid">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/FUNCTIONSET__IDENTIFIER" />
</xsl:attribute>
<xsl:attribute name="RTBP_FunctionSet">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/RTBP__CFMID" />
</xsl:attribute>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The predicates are set to 1 at the moment but I wish it to be a variable which iterates on each loop so the XPATH changes to the next occurence of the table name.
The expected result is below:
<?xml version="1.0"?>
<RTBP CFMtype="dog" CFMid="1">
<FunctionSet RTBP__CFMid="1" RTBP_FunctionSet="1"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="2">
<FunctionSet RTBP__CFMid="2" RTBP_FunctionSet="2"/>
</RTBP>
As you may be able to tell the second table (FunctionSet) is a child of the first (RTBP) hence the for-each inside the for-each. I need a method that will put the first row of the FunctionSet into the First row of the RTBP and likewise for the second rows.
I am new to XML, XSL and Posting questions.
The purpose is to re-create a hierarchical XML from a flat XML
exported from a database using DBunit. The association could be done
by cmfid
You should definitely use a key based on matching the cfmid value - especially if you are expecting a large number of rows. Try:
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:key name="func" match="FunctionSet" use="RTBP__CFMID" />
<xsl:template match="/">
<root>
<xsl:for-each select="dataset/rtbp">
<RTBP CFMtype="{cfmtype}" CFMid="{cfmid}">
<xsl:for-each select="key('func', cfmid)">
<FunctionSet RTBP__CFMid="{RTBP__CFMID}" RTBP_FunctionSet="{FUNCTIONSET__IDENTIFIER}"/>
</xsl:for-each>
</RTBP>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
When the above is applied to the following test input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>124</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>256</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Canine</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Feline</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>256</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Hound</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
</dataset>
the result is:
<?xml version="1.0" encoding="utf-8"?>
<root>
<RTBP CFMtype="dog" CFMid="124">
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Canine"/>
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Hound"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="256">
<FunctionSet RTBP__CFMid="256" RTBP_FunctionSet="Feline"/>
</RTBP>
</root>
Note that your requested output format needlessly duplicates the cfmid value in both parent and child.
I think you're looking for something like (updated after quetion update) :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="rtbp">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
<xsl:apply-templates
select="//FunctionSet[RTBP__CFMID = current()/cfmid]"
mode="insertFunctionSet"/>
</xsl:copy>
</xsl:template>
<xsl:template match="FunctionSet"/>
<xsl:template match="FunctionSet" mode="insertFunctionSet">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The idea, here, is to handle differently the element FunctionSet in the context of rtbp element.
It should not be part of the output when you recursively loop over the whole tree (that's the goal of the <xsl:template match="FunctionSet"/>).
But it should be handled inside the rtbp element and so we apply the templates on the relevant FunctionSet in a specific mode at this point. That's the goal of the <xsl:template match="FunctionSet" mode="insertFunctionSet">...</xsl:template>
With your input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
The result is:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<rtbp cfmtype="dog" cfmid="1">
<FunctionSet FUNCTIONSET__IDENTIFIER="1" RTBP__CFMID="1"/>
</rtbp>
<rtbp cfmtype="cat" cfmid="2">
<FunctionSet FUNCTIONSET__IDENTIFIER="2" RTBP__CFMID="2"/>
</rtbp>
</dataset>
For anyone who had as little knowledge as me when I posted this question and whom wish to find out the same infomation here is my solution to the question. Short answer to the quick question 'can you increment a variable'. No! But you can set a variable and move the position with the following snippet:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
</xsl:for-each>
This snippet loops through rtbp tables in the source XML and moves the position one position more each loop. This creates an object which you can use inside a XPath to test for a condition of each occurence of the Xpath with the same URI path. Such as:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
<xsl:if test="/dataset/FunctionSet[$i]/cfmid = /dataset/rtbp[$n]/cfmid">
<!--code if condition is true-->
</xsl:for-each>
The [$variable name] is how you direct an XPath to the occurence of the element name. So when i = 1 it looks for the first occurence of the element name in the XPath and then when i = 2 it looks for the second occurence of the element name in the XPath.
The Key function is a good tool to search for a key condition inside a template. However I can only use 1 key function per a template. if you wish to have a multi condition test you have to use a choose when statement with multiple if statements being anded with eeach other. for example:
This is a snippet from my advanced code which has multiple for-each loops inside each other and choose when statements to decide if a XML element is a child of the parent via its Identifiers which are child elements of the parent elements in the example XML in my question.
With the position function and the XPath predicate entry combined with choose when statements with ands you can build up a complex XSL which can re-create a flat XML table list of a database into a hierarchical XML form.
Vincent's Key function answer worked for the small complexity of this question but this answer includes an answer about the XPath predicates so I think it is more relevant of an answer to the question. Please look at Vincent's answer and consider using Key Functions for your solution because it is very useful
Where unconditionally I would write, say,
<season boolean:warm="true">
I would set an attribute conditionally via xslt but I don't know what syntax to use
<season>
<xsl:if ...>
<xsl:attribute name="warm">
true
</xsl:attribute>
</xsl:if>
</season>
the program which uses the result of this xslt fails to handle this variant
If you want to use namespaces in XSLT you have to declare them. Lets assume your source is something like:
<months>
<month>January</month>
<month>August</month>
</months>
Since you require a prefix in your attribute, you have to declare the namespace. You didn't show the namespace, so I will use a random string. The XSL below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:boolean="put-your-namespace-here">
<xsl:template match="months">
<seasons><xsl:apply-templates/></seasons>
</xsl:template>
<xsl:template match="month">
<season>
<xsl:if test="text() = 'August'">
<xsl:attribute name="boolean:warm">true</xsl:attribute>
</xsl:if>
<xsl:value-of select="."/>
</season>
</xsl:template>
</xsl:stylesheet>
Will produce the following result:
<seasons xmlns:boolean="put-your-namespace-here">
<season>January</season>
<season boolean:warm="true">August</season>
</seasons>
I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<XmlTest>
<Pictures attr="Pic1">Picture 1</Pictures>
<Pictures attr="Pic2">Picture 2</Pictures>
<Pictures attr="Pic3">Picture 3</Pictures>
</XmlTest>
While this XSL does what is expected (output the attr of the first picture):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture" select="Pictures[1]">
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
It seems to be not possible to do the same inside the variable declaration using xsl:copy-of:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
Curious:
If I just select "$FirstPicture" instead of "$FirstPicture/#attr" in the second example, it outputs the text node of Picture 1 as expected...
Before you all suggest me to rewrite the code:
This is just a simplified test, my real aim is to use a named template to select a node into the variable FirstPicture and reuse it for further selections.
I hope someone could help me to understand the behavior or could suggest me a proper way to select a node with code which could be easily reused (the decission which node is the first one is complex in my real application). Thanks.
Edit (thanks to Martin Honnen):
This is my working solution example (which additionally uses a seperate template to select the requested picture node), using the MS XSLT processor:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPictureResultTreeFragment">
<xsl:call-template name="SelectFirstPicture">
<xsl:with-param name="Pictures" select="Pictures" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="FirstPicture" select="msxsl:node-set($FirstPictureResultTreeFragment)/*"/>
<xsl:value-of select="$FirstPicture/#attr"/>
<!-- further operations on the $FirstPicture node -->
</xsl:template>
<xsl:template name="SelectFirstPicture">
<xsl:param name="Pictures"/>
<xsl:copy-of select="$Pictures[1]"/>
</xsl:template>
</xsl:stylesheet>
Not nice, that it is in XSLT 1.0 not possible to output a node directly from a template, but with the extra variable it is at least not impossible.
Well with an XSLT 1.0 processor if you do
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
the variable is a result tree fragment and all you can do with that in pure XSLT 1.0 is output it with copy-of (or value-of). If you want to apply XPath you first need to convert the result tree fragment into a node set, most XSLT 1.0 processors support an extension function for that so try
<xsl:variable name="FirstPictureRtf">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:variable name="FirstPicture" select="exsl:node-set(FirstPictureRtf)/Pictures/#attr">
where you define xmlns:exsl="http://exslt.org/common" in your stylesheet.
Note that you will need to check whether your XSLT 1.0 processor supports the EXSLT extension function or a similar one in another namespace (as for instance the various MSXML versions do).
I have an XSLT like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan">
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:template match="/">
<xsl:apply-templates select="$fooDocument//*"/>
</xsl:template>
<xsl:template match="nodeInFooDocument">
<xsl:variable name="valueFromSource" select="//someSourceElement"/>
</xsl:template>
</xsl:transform>
In the second template, which matches nodes in the fooDocument.xml which is loaded with document(), I want to access nodes in the XML source the transformation is executed upon. This does not work with //someSourceElement, because apparently, XPath executes this path in the context of fooDocument.
A first workaround that comes to mind is this:
...
<!-- global variable -->
<xsl:variable name="root" select="/"/>
...
<!-- in the template -->
<xsl:variable name="valueFromSource" select="$root//someSourceElement"/>
...
But I cannot use this workaround, because actually, my variable is selected like this:
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
$someXPathString is not crafted in the XSLT file, but loaded from fooDocument (and contains an absolute path like the one used above). Still, I need to somehow change the XPath context back to the XML source. A very hacky workaround I found is this:
<xsl:for-each select="$root[1]">
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
</xsl:for-each>
The (useless) for-each loop changes the context back to the main XML source, thus the XPath evaluates correctly. But obviously, this is not an acceptable solution.
Is there a way to do this right, or can someone suggest a better workaround?
Even if you think your attempt with a for-each select="$root" to change the context document is not acceptable this is the right approach. So use that, there is no other way.
Have you considered doing all the computation that constructs $someXPathString using a series of global variables?
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:variable name="temp1"
.. some computation using fooDocument ..
</xsl:variable>
<xsl:variable name="temp2"
.. some computation using temp1 ..
</xsl:variable>
<xsl:variable name="someXPathString"
.. some computation using temp2 ..
</xsl:variable>
<xsl:variable name="root" select="xalan:evaluate($someXPathString)"/>
I am trying to transform XML that looks like:
<item attr1="value1" attr2="value2"><nestedItem attr1="value1" attr="value2"/></item>
To XML that looks like:
<item
attr1="value1"
attr2="value2">
<nestedItem
attr1="value1"
attr="value2"/>
</item>
I am working with a stylesheet:
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template name="newline">
<xsl:text disable-output-escaping="yes">
</xsl:text>
</xsl:param>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="text()|#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
I've tried calling my newline template from a few different places, but can't get newlines inserted between my attributes.
Thanks!
There is no support for the wanted serialization in XSLT 1.0 and 2.0 (and, to my knowledge, also in the forthcoming XSLT 3.0).
In case your XSLT processor allows serialization via a user-provided XmlWriter class, then you can implement such serialization.
For example, when using one or more specific overloads of the .NET XslCompiledTransform.Transform() method, one may pass as one of the arguments to the method, an instance of XmlWriter. Pass an instance of your own class that derives from XmlWriter.
The following template may not pretty and may not be best practice but it works for me. Give it a try. It will not output xmlns 'attributes' if there are any on the element.
<xsl:template match="*">
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:value-of select="name()"/><xsl:text>
</xsl:text>
<xsl:for-each select="#*">
<xsl:text> </xsl:text><xsl:value-of select="concat(name(),'=' ,'"', . ,'"')" /><xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text disable-output-escaping="yes">>
</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes"></</xsl:text><xsl:value-of select="name()"/><xsl:text disable-output-escaping="yes">>
</xsl:text>
</xsl:template>
When you use the Saxon serializer with indentation you can get output close to what you are looking for, but it might not be exactly what you want. If you're really fussy about the format then you will have to write your own serializer or adapt an existing one by tweaking the code. The usual philosophy in XML circles is that you shouldn't really care about distinctions that will be ignored once the data is parsed, and that includes things like the choice of quote character, the order of attributes, and the whitespace that separates attributes.