xslt - create empty file using xslt 1.0 - xslt-1.0

I am trying to create an empty file through xslt.
The input sample is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Businessman>
<siblings>
<sibling>John </sibling>
</siblings>
<child> Pete </child>
<child> Ken </child>
</Businessman>
When the input contains any presence of 'child' tags, it should produce the file AS IS. When the input does not have any 'child' tag, I need an empty file (0 byte file) created.
This is what I tried:
<?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" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="#*|node()">
<xsl:choose>
<xsl:when test="/Businessman/child">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This gives the file unchanged when there is any 'child' tag present. But did not produce any empty file when there is no 'child' tag.
The file I need to test will look like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Businessman>
<siblings>
<sibling>John </sibling>
</siblings>
</Businessman>
Any help would be great!
Thanks

If you want the processor to go to the trouble of opening the output file, you have to give it something to write to the output file. Try an empty text node. And you only need to make the decision 'copy or not?' once.
One way to make the decision just once and produce empty output if the condition is not met would be to replace your template with:
<xsl:template match="/">
<xsl:choose>
<xsl:when test="/Businessman/child">
<xsl:copy-of select="*"/>
</xsl:when>
<xsl:otherwise>
<xsl:text/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This works as expected with xsltproc. (If you find yourself getting a file containing an XML declaration and nothing else, try adjusting the parameters on xsl:output.)
But when I have found myself with a similar situation (perform this transform if condition C holds, otherwise ...), I have simply added a template for the document node that would look something like this for your case:
<xsl:choose>
<xsl:when test="/Businessman/child">
<xsl:apply-templates/>
</
<xsl:otherwise>
<xsl:message terminate="yes">No children in this input, dying ...</
</
</
That way I get no output at all rather than zero-length output.

Simple enough - Just don't try to do everything in one template, don't forget to omit the xml declaration and get the xpath right:
<?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" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Businessman[child]" priority="9">
<xsl:element name="Businessman">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Businessman" priority="0" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

XSLT 1.0: How to determine the position of a node based on the value

thanks for taking the time!
I am trying to get the position of a node based on its value, so that I can then get corresponding entries. Please let me show you with an example, it will be easier to explain.
Here is the source XML:
<?xml version="1.0" encoding="UTF-8"?>
<sampleSourceXML>
<Table>
<Header>
<Column>Name</Column>
<Column>Surname</Column>
<Column>Title</Column>
</Header>
<Body>
<Row>
<Entry>James</Entry>
<Entry>Bond</Entry>
<Entry>Mr</Entry>
</Row>
<Row>
<Entry>Harry</Entry>
<Entry>Potter</Entry>
<Entry>Mr</Entry>
</Row>
</Body>
</Table>
</sampleSourceXML>
This is the resulting XML that I would like to achieve:
<?xml version="1.0" encoding="UTF-8"?>
<sampleResultXML>
<Person>
<FirstName>James</FirstName>
<LastName>Bond</LastName>
<Title>Mr</Title>
</Person>
<Person>
<FirstName>Harry</FirstName>
<LastName>Potter</LastName>
<Title>Mr</Title>
</Person>
</sampleResultXML>
I tried it with this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:element name="sampleResultXML">
<xsl:for-each select="sampleSourceXML/Table/Rows/Row">
<xsl:element name="Person">
<xsl:element name="FirstName">
<xsl:value-of select="./Entry[1]/text()"></xsl:value-of>
</xsl:element>
<xsl:element name="LastName">
<xsl:value-of select="./Entry[2]/text()"></xsl:value-of>
</xsl:element>
<xsl:element name="Title">
<xsl:value-of select="./Entry[3]/text()"></xsl:value-of>
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This works.
But! The source XML can have a different order. For example the Title element can be above the Name element. So the hardcoded position in my XSLT (e.g. Entry[3]) will not work any more.
The header order and the entry order are always corresponding. So if the Column Title is in position 3, so will be the corresponding row.
So I need a way to get the position of, let's say, the Title element dynamically. So I have to go through the Column elements until I reach the one with value "Title". This position I thought I'd save in a variable (e.g. Position_Title) so that I can fill the value later like so:
<xsl:element name="Title">
<xsl:value-of select="./Entry[$Position_Title]/text()"></xsl:value-of>
</xsl:element>
However I haven't been able to find a way to do this...
Do you have any ideas how I can achieve this?
Thanks!
Nick
EDIT: Thanks to Michael for the help! Here is the final XSLT I'm using:
<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:template match="/sampleSourceXML">
<xsl:variable name="cols" select="Table/Header/Column"/>
<sampleResultXML>
<xsl:for-each select="Table/Body/Row">
<Person>
<xsl:for-each select="Entry">
<xsl:variable name="i" select="position()"/>
<xsl:variable name="elementName">
<xsl:choose>
<xsl:when test="$cols[$i]='Name'">
<xsl:value-of select="'FirstName'"/>
</xsl:when>
<xsl:when test="$cols[$i]='Surname'">
<xsl:value-of select="'LastName'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$cols[$i]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elementName}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</Person>
</xsl:for-each>
</sampleResultXML>
</xsl:template>
</xsl:stylesheet>
So I need a way to get the position of, let's say, the Title element dynamically. So I have to go through the Column elements until I reach the one with value "Title". This position I thought I'd save in a variable (e.g. Position_Title) so that I can fill the value later like so:
I think it could be much simpler if you do it from the opposite direction. 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:template match="/sampleSourceXML">
<xsl:variable name="cols" select="Table/Header/Column" />
<sampleResultXML>
<xsl:for-each select="Table/Body/Row">
<Person>
<xsl:for-each select="Entry">
<xsl:variable name="i" select="position()" />
<xsl:element name="{$cols[$i]}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</Person>
</xsl:for-each>
</sampleResultXML>
</xsl:template>
</xsl:stylesheet>

Using XSLT to transform date format from yyyy-mm-dd to dd-mm-yy

I am reading an XML document in which there is a date tag as shown below.
<uabDate>2014-07-23</uabDate>
Now I am reading this XML from XSL and trying to parse it, but I want to translate that date value into a format like 23-07-14.
What I have tried is given below.
Here I am calling the template that will store uabDate in a variable businessDate and then calling the template
<xsl:call-template name="convertDateToDDMMYYYY_template">
<xsl:with-param name="b1"
select="$s2Date/uabDate"/>
</xsl:call-template>
and here is the definition of the named template:
<xsl:template name="convertDateToDDMMYYYY_template">
<xsl:param name="b1"/>
<xsl:variable name="DateVar">
<xsl:value-of select="concat(
substring(./b1,1,4),
'-',
substring(./b1,6,2),
'-',
substring(./b1,9,2))"/>
</xsl:variable>
</xsl:template>
You wrote your template wrong. First of all you are putting the result into a variable, called DateVar, and don't output it.
Secondly you got a parameter called b1 and parameters should be called using $b1.
And third issue is you are formatting the date same as the input. Instead of changing the date format.
So for example if I use this input XML:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<uabDate>2014-07-23</uabDate>
</data>
And this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="uabDate">
<xsl:copy>
<xsl:call-template name="convertDateToDDMMYYYY_template">
<xsl:with-param name="b1" select="."/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="convertDateToDDMMYYYY_template">
<xsl:param name="b1"/>
<xsl:value-of select="concat(substring($b1,9,2),'-',substring($b1,6,2),'-',substring($b1,1,4))"/>
</xsl:template>
</xsl:stylesheet>
It creates this output:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<uabDate>23-07-2014</uabDate>
</data>

Passing a parameter does not seem to work

I need a parameter to be send to a template to help with processing the right nodes in an external second xml file (this to keep the amount of templates small).
I've found that the following code send the parameter and processes it correctly:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
This is text1
<xsl:apply-templates mode="my-mode">
<xsl:with-param name="testParam" select="'TEST_PARAMETER'"/>
</xsl:apply-templates>
This is text2
</xsl:template>
<xsl:template match="DialStats" mode="my-mode">
<xsl:param name="testParam" />
<br></br>
This is text 3<br></br>
<xsl:value-of select="$testParam" /><br></br>
This is text 4<br></br>
</xsl:template>
</xsl:stylesheet>
When I implement the second file I've found that the following no longer passes the parameter:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
This is text1
<xsl:apply-templates select="document('file://D:/DATA/Marquee/DATA/MarqueeStats.xml')" mode="my-mode">
<xsl:with-param name="testParam" select="'TEST_PARAMETER'"/>
</xsl:apply-templates>
This is text2
</xsl:template>
<xsl:template match="DialStats" mode="my-mode">
<xsl:param name="testParam" />
<br></br>
This is text 3<br></br>
<xsl:value-of select="$testParam" /><br></br>
This is text 4<br></br>
</xsl:template>
It calls the template correctly, as I can see both the lines with 'This is text x". So the parameter is gone. I did find some articles suggesting the match= needs to be set, so I matched that with the xml root:
<DialStats>
<Countries Country="Denmark">
<Products Product="MR">
. . .
Any suggestions on how to resolve this?
BTW this is just the basics I need to get right as a lot more code need to be added.

XSLT Template is never called even so the match exists

I want to know why the template match="*" mode="Value_Or_Null_with_comma" is not called and executed?
The following includes my XSL code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="urn:example-x-
com:abc" xmlns:ns3="http://Example1.com" xmlns:ns4="http://Example2.com" exclude-result-
prefixes="abc" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:output method="text" />
<xsl:template match="ns3:productionOrder">
<xsl:apply-templates select="ns4:ProductionOrder/ns4:productionOrderData/ns4:ProductionOrderData"
mode="Technical"></xsl:apply-templates>
</xsl:template>
<xsl:template match="node()">
<xsl:if test="/ns3:AddProductionOrder">
<xsl:apply-templates select="ns3:productionOrder"></xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="Value_Or_Null_with_comma">
<xsl:choose>
<xsl:when test=". and ./text()">
<!--test="$Parameter and $Parameter/text()"-->
<xsl:choose>
<xsl:when test="number(.) = .">
<xsl:value-of select="."></xsl:value-of>
</xsl:when>
<xsl:otherwise>'<xsl:value-of select="."></xsl:value-of>'</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>NULL</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="ns4:ProductionOrderData" mode="Technical">
<xsl:text>Exec RecipeData_List,
</xsl:text>
<apply-templates select="ns4:materialNumber" mode="Value_Or_Null_with_comma"></apply-templates>
<!--,-->
<xsl:text></xsl:text>NULL x1,
<xsl:text></xsl:text>NULL x2,
</xsl:template>
</xsl:stylesheet>
The according XML file is:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="Test MODE_method call.xsl"?>
<ns3:AddProductionOrder xmlns:ns3="http://Example1.com">
<ns3:productionOrder>
<ns4:ProductionOrder xmlns:ns4="http://Example2.com">
<ns4:productionOrderData>
<ns4:ProductionOrderData>
<ns4:Comment1>Mele1</ns4:Comment1>
<ns4:materialNumber>10001895</ns4:materialNumber>
<ns4:maxFimeAllowed>0.0 </ns4:maxFimeAllowed>
</ns4:ProductionOrderData>
</ns4:productionOrderData>
</ns4:ProductionOrder>
</ns3:productionOrder>
</ns3:AddProductionOrder>
The result is (but it's wrong, it does not display the contents of the material number):
Exec RecipeData_List,
NULL x1,
NULL x2,
The result should be:
Exec RecipeData_List,
10001895,
NULL x1,
NULL x2,
One more hint:
I'm using here mode "Technical" because later I would transform more data from ns4:ProductionOrderData, but this is not content of the posted code, it's just a shorter version with the same problem that Value_Or_Null_with_comma is not executed.
Thank you for your help!

Setting xpath as value to xslt variable and pass it as a paramter to a JS Function

Could you explain how to set xpath of a node as the value to a xslt variable . I also want to display the value and then pass it as a parameter to a JS Function. Thanks for all your Help.
XPath expressions are not values, so they cannot be bound to variables. You can of course put an XPath expression in a string. There's no standard way in XSLT to execute an XPath expression held in a string, but many implementations have an extension function (such as xx:evaluate()) to do it.
It helps to explain what problem you are trying to solve, not what technique you are trying to use to solve it. Then we could suggest alternative and perhaps better approaches.
Sample Input XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<child>
<trial>test</trial>
</child>
</root>
Model code to pass XPath of current node to inline JS function:
<?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" xmlns:js="urn:js">
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="JScript" implements-prefix="js">
<![CDATA[
function test(something)
{
//code here
}
]]>
</msxsl:script>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="trial">
<xsl:copy>
<xsl:variable name="xpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="name()"/>
<xsl:if test="not(position()=last())">
<xsl:value-of select="'/'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="js:test($xpath)"/> <!--Passed value $xpath to funciton test-->
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
having return value as
return (something);
will output:
<?xml version="1.0" encoding="utf-8"?>
<root>
<child>
<trial>root/child/trial</trial>
</child>
</root>