I am using XSLT to create XML file. A Date-time has milliseconds. I need to have the output XML without milliseconds.
Format needs to be YYYY-MM-DDTHH:MM:SS
For example:
XML shows date as: 2012-12-341T09:26:53.132-0500
But this needs to be: 2012-12-341T09:26:53
If all of the values are dateTime and have a ., you could use substring-before():
substring-before('2012-12-341T09:26:53.132-0500', '.')
Of you could use substring() to select the first 20 characters:
substring('2012-12-341T09:26:53.132-0500', 0, 21)
If you are using XSLT2, see this function: http://www.w3.org/TR/xslt20/#function-format-dateTime. This picture string should give you what you want:
format-dateTime($dateTime,'[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')
This XPath expression produces the wanted result regardless whether the string contains a dot or a hyphen or both dot and hyphen, or none, and doesn't rely on the number of digits used for year, month, day:
substring-before(concat(substring-before(concat(substring-after(.,'T'),
'.'),
'.'),
'-'),
'-')
Here is a simple XSLT transformation that uses this XPath expression:
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dt/text()">
<xsl:value-of select="substring-before(., 'T')"/>
<xsl:text>T</xsl:text>
<xsl:value-of select=
"substring-before(concat(substring-before(concat(substring-after(.,'T'),
'.'),
'.'),
'-'),
'-')
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this test XML document:
<t>
<dt>2012-12-341T09:26:53.132-0500</dt>
<dt>2012-12-355T09:34:56</dt>
<dt>2012-12-355T09:34:56-0500</dt>
<dt>2012-12-13T9:34:5-0500</dt>
<dt>2012-12-344T09:12:34.378-0500</dt>
</t>
the wanted, correct result is produced:
<t>
<dt>2012-12-341T09:26:53</dt>
<dt>2012-12-355T09:34:56</dt>
<dt>2012-12-355T09:34:56</dt>
<dt>2012-12-13T9:34:5</dt>
<dt>2012-12-344T09:12:34</dt>
</t>
Explanation:
Proper application of sentinels.
Related
I tried to do that with replace($val, 'amp;', ''), but seems like & is atomic entity to the parser. Any other ideas?
I need it to get rid of double escaping, so I have constructions like ᾰ in input file.
UPD:
Also one important notice: I have to make this substitution only inside of specific tags, not inside of every tag.
If you serialize there is always (if supported) the disable-output-escaping hack, see http://xsltransform.hikmatu.com/nbUY4kh which transforms
<root>
<foo>a & b</foo>
<bar>a & b</bar>
</root>
selectively into
<root>
<foo>a & b</foo>
<bar>a & b</bar>
</root>
by using <xsl:value-of select="." disable-output-escaping="yes"/> in the template matching foo/text():
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo/text()">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:template>
</xsl:transform>
To achieve the same selective replacement with a character map you could replace the ampersand in foo text() children (or descendants if necessary) with a character not used elsewhere in your document and then use the map to map it to an unescaped ampersand:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output use-character-maps="doe"/>
<xsl:character-map name="doe">
<xsl:output-character character="«" string="&"/>
</xsl:character-map>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo/text()">
<xsl:value-of select="replace(., '&', '«')"/>
</xsl:template>
</xsl:transform>
That way
<root>
<foo>a & b</foo>
<bar>a & b</bar>
</root>
is also transformed to
<root>
<foo>a & b</foo>
<bar>a & b</bar>
</root>
see http://xsltransform.hikmatu.com/pPgCcoj for a sample.
If your XML contains ᾰ and you believe that this is a double-escaped representation of the character with codepoint 8112, then you can convert it to this character using the XPath expression
codepoints-to-string(xs:integer(replace($input, '&#([0-9]+);', $1)))
remembering that if you write this XPath expression in XSLT, then the & must be written as &.
How to change all decimal value to zeroes in XSL
Example value:
from : 9876.123
to : 9876.000
Well,
floor(9876.123)
returns:
9876
and:
format-number(floor(9876.123), '#.000')
returns:
9876.000
I don't see why this would be useful, but in case you do want to preserve the number of decimal places, I would use:
format-number(floor($amount), translate($amount, '123456789', '000000000'))
In case the number of digits after the decimal point is unknown in advance, use:
concat(substring-before(., '.'),
'.',
translate(substring-after(., '.'), '123456789', '000000000'))
Here is a complete XSLT transformation example:
<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="d">
<xsl:value-of select=
"concat(substring-before(., '.'),
'.',
translate(substring-after(.,'.'), '123456789','000000000'))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<d>9876.1</d>
<d>9876.12</d>
<d>9876.123</d>
<d>9876.1234</d>
<d>9876.12345</d>
<d>9876.123456</d>
<d>9876.1234567</d>
<d>9876.12345678</d>
<d>9876.123456789</d>
</t>
the wanted, correct result is produced:
9876.0
9876.00
9876.000
9876.0000
9876.00000
9876.000000
9876.0000000
9876.00000000
9876.000000000
Update
Someone requested that integer values (not containing decimal point) are also processed correctly (copied intact).
I also added to this that negative values and / or currency denominations should also be processed correctly.
Although this falls outside the scope of the current problem, here is again a single XPath 1.0 expression that produces the wanted result:
concat(substring-before(concat(., '.'), '.'),
translate(., '+-$0123456789', ''),
translate(substring-after(.,'.'), '123456789','000000000'))
And here again is the complete transformation:
<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="d">
<xsl:value-of select=
"concat(substring-before(concat(., '.'), '.'),
translate(., '+-$0123456789', ''),
translate(substring-after(.,'.'), '123456789','000000000'))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<d>-$1.234</d>
<d>-1.234</d>
<d>-.234</d>
<d>9876</d>
<d>9876.1</d>
<d>9876.12</d>
<d>9876.123</d>
<d>9876.1234</d>
<d>9876.12345</d>
<d>9876.123456</d>
<d>9876.1234567</d>
<d>9876.12345678</d>
<d>9876.123456789</d>
</t>
the wanted, correct result is produced:
-$1.000
-1.000
-.000
9876
9876.0
9876.00
9876.000
9876.0000
9876.00000
9876.000000
9876.0000000
9876.00000000
9876.000000000
<?xml version="1.0" encoding="utf-8"?>
<Report p1:schemaLocation="Customer details http://reportserver?%2fCustomer details&rs%3aFormat=XML&rc%3aSchema=True" Name="Customer details" xmlns:p1="http://www.w3.org/2001/XMLSchema-instance" xmlns="Customer details">
<table2>
<Detail_Collection>
<Detail Col1="aaa" col1_SeqID="2" col1_Include="1"
Col2="aaa" col2_SeqID="1" col2_Include="1"
Col3="aaa" col3_SeqID="" col3_Include="0"
Col4="aaa" col4_SeqID="4" col4_Include="1"
Col5="aaa" col5_SeqID="" col5_Include="0"
... ... ...
... ... ...
... ... ...
Col50="aaa" col50_SeqID="3" col50_Include="1"
/>
<Detail_Collection>
</table2>
</Report>
The above xml is produced by SSRS for the RDL file. I want to transform the above xml file to CSV format using XSLT (customized format).
The RDL file (SSRS report) is very simple with 50 columns, and displays the data for all the columns depending on the user selection on the user interface.
The user interface has got the parameter selection for all the 50 columns (i.e they can select the order of the column, they can select a particular column to be included on the report or not, the fontstyle etc...). As mentioned the each column has 2 main functionalities i.e. they can be sorted and as well ordered by based on the selections.
For example from the report output i.e in the xml format given above you will see all the 50 columns exist on the xml format but I am also including the extra fiedls which are generally hided on the report.
The col1 is included on the report and is ordered (seqID) as the 2nd column on the csv file.
The col2 is also included on the report and is ordered as the 1st column on the csv file.
The col3 is not included on the report and the order selection is empty, so this is not included on the csv file.
...
...
like wise the col50 is included on the report but is ordered in as 3rd column in the csv file.
My main challenge here to create the xslt file for "CSV" and put the columns in the order selection which are selected per user basis.
The output in the CSV file after transformation will look as follows:
Col2 Col1 Col50 Col4
... ... ... ....
Any good idea to create this kind of xsl file is much appreciated and I thank you so much for understanding my question and trying to help me in this regard.
I. This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="Customer details">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="c:Detail">
<xsl:apply-templates select=
"#*[substring(name(), string-length(name())-5)
= '_SeqID'
and
number(.) = number(.)
]
">
<xsl:sort data-type="number"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select=
"../#*
[name()
=
concat('Col',substring-before(substring(name(current()),4),'_'))
]"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the provided one, made well-formed and unambiguous):
<Report
p1:schemaLocation="Customer details http://reportserver?%2fCustomer details&rs%3aFormat=XML&rc%3aSchema=True"
Name="Customer details"
xmlns:p1="http://www.w3.org/2001/XMLSchema-instance"
xmlns="Customer details">
<table2>
<Detail_Collection>
<Detail Col1="aaa1" col1_SeqID="2" col1_Include="1"
Col2="aaa2" col2_SeqID="1" col2_Include="1"
Col3="aaa3" col3_SeqID="" col3_Include="0"
Col4="aaa4" col4_SeqID="4" col4_Include="1"
Col5="aaa5" col5_SeqID="" col5_Include="0"
Col50="aaa50" col50_SeqID="3" col50_Include="1"
/>
</Detail_Collection>
</table2>
</Report>
produces the wanted, correct result:
aaa2,aaa1,aaa50,aaa4
Explanation:
We use that the XPath 1.0 expression:
__
substring($s1, string-length($s1) - string-length($s2) +1) = $s2
is equivalent to the XPath 2.0 expression:
ends-with($s1, $s2))
.2. Appropriate use of <xsl:sort>, substring(), name() and current().
.3. Using the fact that a string $s is castable to number if and only if:
__
number($s) = number($s)
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:c="Customer details">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="c:Detail">
<xsl:apply-templates select=
"#*[ends-with(name(),'_SeqID')
and . castable as xs:integer]">
<xsl:sort select="xs:integer(.)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select=
"../#*
[name()
eq
concat('Col',translate(name(current()),'col_SeqID',''))]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the same correct result is produced:
aaa2,aaa1,aaa50,aaa4
Update: #desi has asked that the heading should also be generated.
Here is the updated XSLT 1.0 transformation (as indicated, #desi is limited to use XSLT 1.0 only) that does this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="Customer details">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="c:Detail">
<xsl:for-each select=
"#*[substring(name(), string-length(name())-5)
= '_SeqID'
and
number(.) = number(.)
]
">
<xsl:sort data-type="number"/>
<xsl:value-of select=
"concat('Col',
substring-before(substring(name(current()),4),
'_')
)
"/>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:apply-templates select=
"#*[substring(name(), string-length(name())-5)
= '_SeqID'
and
number(.) = number(.)
]
">
<xsl:sort data-type="number"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select=
"../#*
[name()
=
concat('Col',substring-before(substring(name(current()),4),'_'))
]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the wanted, correct result is produced:
Col2 Col1 Col50 Col4
aaa2,aaa1,aaa50,aaa4
In XSL is function like CONTAIN, that if i have number with simbol like "123112'+:" then doesn't take it.
to be more precise:
<Number>111111</Number>
<Number>123123+</Number>
<Number>222222</Number>
<Number>222222+</Number>
answer:
111111
222222
I'm stuck with xslt 1.0 version
Another approach, exploiting number to boolean conversion.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:apply-templates select="Number[boolean(number()) or . = 0]"/>
</xsl:template>
<xsl:template match="Number">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
With input:
<Numbers>
<Number>111111</Number>
<Number>123123+</Number>
<Number>222222</Number>
<Number>222222+</Number>
</Numbers>
Correct result:
111111
222222
Quoting the spec:
The boolean function converts its
argument to a boolean as follows: a
number is true if and only if it is
neither positive or negative zero nor
NaN
Use the following XPath to select all the nodes that contains numbers. It will skip the ones with a plus sign in them.
Number[number(.)=number(.)]
Should work with XSLT 1.0
Yet another solution :)
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Number[not(.*.+1)]"/>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<t>
<Number>111111</Number>
<Number>123123+</Number>
<Number>222222</Number>
<Number>222222+</Number>
</t>
the wanted, correct result is produced:
<t>
<Number>111111</Number>
<Number>222222</Number>
</t>
Explanation: All Number elements for wich the expression:
not(.*.+1)
is true() are filtered out by the simple template rule:
<xsl:template match="Number[not(.*.+1)]"/>
This is possible only if the string value of the Number element cannot be converted to a number. In this case .*.+1 evaluates to NaN and boolean(NaN) is false() by definition.
If the string value of the Number element can be converted to a number $num, then the above expression is equivalent to:
not($num*$num+1)
and $num*$num+1 >= 1 for any number $num, so, boolean(.*.+1) in this case is always true().
Given an element with a value of:
<xml_element>Distrib = SU & Prem <> 0</xml_element>
I need to turn < or > into < or >
because a downstream app requires it in this format throughout the entire XML document. I would need this for quotes and apostrophes too. I am tryinging a character-map in XSLT 2.0.
<xsl:character-map name="specialchar">
<xsl:output-character character="'" string="'" />
<xsl:output-character character=""" string=""" />
<xsl:output-character character=">" string=">" />
</xsl:character-map>
The <xsl:character-map> instruction can be used to serialize a single character to any string. However this problem requires more than one character (an ampersand followed by another character to be replaced.
<xsl:character-map> cannot be used to solve such problems.
Here is a solution to this problem, using the XPath 2.0 replace() function:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select=
'replace(
replace(
replace(., "<", "<"),
">",
">"
),
"'",
"'"
)
'/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<xml_element>Distrib = SU & 'Prem <> 0</xml_element>
the wanted result is produced:
<xml_element>Distrib = SU & 'Prem <> 0</xml_element>