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>
Related
Is it possible to sort nodes as follows:
Example XML
<record>
<id>0</id>
<sku>0</sku>
<name>Title</name>
<prop>456</prop>
<number>99</number>
</record>
If I apply this template
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="record/*">
<xsl:param select="." name="value"/>
<div>
<xsl:value-of select="concat(local-name(), ' - ', $value)"/>
</div>
</xsl:template>
</xsl:stylesheet>
Ouput:
<div>id - 0</div>
<div>sku - 0</div>
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
However, I would like all 0 values to be outputted last, as so:
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
<div>id - 0</div>
<div>sku - 0</div>
Is this possible by applying a sort to the <xsl:apply-templates/>?
There is an easy way of achieving this with XSLT-1.0. Just use a predicate on xsl:apply-templates checking if the content is zero:
<xsl:template match="record/*">
<div>
<xsl:value-of select="concat(local-name(), ' - ', .)"/>
</div>
</xsl:template>
<xsl:template match="/record">
<xsl:apply-templates select="*[normalize-space(.) != '0']" />
<xsl:apply-templates select="*[normalize-space(.) = '0']" />
</xsl:template>
This does not sort the output, but groups it the way you want it. The xsl:param is unnecessary.
As far as I see your question, the issue is not any sort at all.
You even didn't write what is the specific value, which you mentioned
it the title. It is rather an arbitrary sequence of child elements of record.
Try the following script, performing just that:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="name, prop, number, id, sku"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record/*">
<div><xsl:value-of select="concat(local-name(), ' - ', .)"/></div>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
I used XSLT 2.0, because initially you did't specify the XSLT version.
Could you move on to version 2.0? As you can see, it allows to write quite
an elegent solution (impossible in version 1.0).
I also changed your template matching record/*. You actually don't
need any param. It is enough to use . - the value of the current
element.
Edit
Another possibility is that you want the following sort:
First, elements with non-numeric value (in your case, only name),
maybe without any sort.
Then elements with numeric value, sorted descending on this value.
If this is the case, then change the template matching record to the
following:
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="*[not(. castable as xs:integer)]"/>
<xsl:apply-templates select="*[. castable as xs:integer]">
<xsl:sort select="." order="descending" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
And add:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
to transform tag.
But I still can't see anything, which can be called the specific value.
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 &.
We have a piece of code which returns XML in a format like:
Source XML:
<Root>
<Book>
<BookId>a</BookId>
<Description>aDescription</Description>
</Book>
<Book>
<BookId>b</BookId>
<Description>bDescription</Description>
</Book>
</Root>
I want to replace the special characters with the literal characters...
<
will be < etc.
I know I can use:
<xsl:character-map name="escapeMapper">
<xsl:output-character character="<" string="<"/>
<xsl:output-character character=">" string=">"/>
</xsl:character-map>
However here is the twist, I want to convert special characters first, then run the resulting XML through other templates. So, I want to run the source XML through a template replacing the special characters, putting the result in to a variable:
<xsl:variable name="vrtfPass1">
Now I can use the multi-pass technique and apply other templates using the variable as the source.
How can I convert the special characters into the literal characters?
It sounds like what you want to do is not convert < and > to < and >, but to parse the XML, yes? Are you able to use Saxon extension functions in what you're doing? If so, I think you could do something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://saxon.sf.net/">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="loaded">
<xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates select="$loaded/*" />
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Book/text()[normalize-space()]">
<xsl:copy-of select="saxon:parse(.)"/>
</xsl:template>
</xsl:stylesheet>
Though this assumes that the text children of Book would always be well-formed XML in its own right.
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.
I have a block as below.
<rightOperand>.*ifIndedx.*</rightOperand>
But i need to change the above snippet to the below one
<rightOperand>(?i)(?s).*ifIndex.*</rightOperand>
This translation needs to be done only when the right operand starts and ends with the string .*
please provide me some pointers .
You can do this my overriding the identity transform with an extra template just to match the text within rightOperand that matches your criteria
<xsl:template match="rightOperand/text()
[starts-with(., '.*')]
[substring(., string-length(.) - 1, 2) = '.*']">
Note that XSLT 1.0 does not have the ends-with function, which is why there is the extra work to check the ending with substring. If you were using XSLT 2.0 you could simplify this with ends_with though.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="rightOperand/text()
[starts-with(., '.*')]
[substring(., string-length(.) - 1, 2) = '.*']">
<xsl:text>(?i)(?s)</xsl:text><xsl:copy />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output:
<rightOperand>(?i)(?s).*ifIndedx.*</rightOperand>