XSLT format-date for an attribute - java

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd created_at="2016-12-15T15:02:55Z">
<title created_at="2016-12-15T15:02:55Z">Empire Burlesque</title>
<artist created_at="2016-12-15T15:02:55Z">Bob Dylan</artist>
<cover created_at="2016-12-15T15:02:55Z"/>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
I want to format all occurrences of created_at attribute
input format YYYY-MM-DDTHH:MM:SSZ
output format YYYY-MM-DD HH:MM:SS
I am currently using this following xslt
<?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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- Edit dates to conform to dbunit format-->
<xsl:template match="#created_at">
<xsl:copy>
<xsl:call-template name="formatdate">
<xsl:with-param name="datestr" select="#created_at"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="formatdate">
<xsl:param name="datestr" />
<!-- input format YYYY-MM-DDTHH:MM:SSZ -->
<!-- output format YYYY-MM-DD HH:MM:SS -->
<xsl:variable name="datetext">
<xsl:value-of select="substring-before($datestr,'T')" />
</xsl:variable>
<xsl:variable name="timetext">
<xsl:value-of select="substring($datestr,12,18)" />
</xsl:variable>
<xsl:value-of select="concat($datetext, ' ', $timetext)" />
</xsl:template>
</xsl:stylesheet>
However as I debug through the transformation xslt it does not seem to enter the formatdate call-template. Is my xpath wrong? I found articles on modifying the node, but not the attribute. Any help would be much appreciated.
Thank you

Why not simply:
<xsl:template match="#created_at">
<xsl:attribute name="created_at">
<xsl:value-of select="substring(translate(., 'T', ' '), 1, 19)" />
</xsl:attribute>
</xsl:template>
Note: you cannot use xsl:copy if you want to change an attribute's value.

From your post, it sounds like all you need is simple string processing.
Why your code isn't working the way you want
You're handling the #created_at attributes with this template:
<xsl:template match="#created_at">
<xsl:copy>
<xsl:call-template name="formatdate">
<xsl:with-param name="datestr" select="#created_at"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
The kicker here is that you're using <xsl:copy>. When used with attributes, <xsl:copy> copies the entire attribute, name and value both. And since attributes can't contain any children, the children of your <xsl:copy> instruction are ignored -- so the XSLT processor never evaluates the <xsl:call-template name="formatdate"> instruction.
A different approach that works
Instead of using <xsl:copy>, you need to instead use <xsl:attribute> to create an attribute in a way where you can also specify the value. In this case, you already know the name of the attribute you want to create, so you could hard-code the name value as created_at. For a more flexible approach, you could instead give the name value as {name(.)} -- this just grabs the name of the attribute being processed, which is closer in behavior to what you probably thought <xsl:copy> would do. :)
It is also possible to produce the desired string in a single xsl:value-of expression, without relying on so many variables.
<xsl:template match="#created_at">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="concat(substring-before(., 'T'), ' ', substring-before(substring-after(., 'T'), 'Z'))"/>
</xsl:attribute>
</xsl:template>
Breaking down that select statement:
Use concat() to stitch together multiple bits of string.
Use substring-before(., 'T') to grab everything before the T -- that's the date portion.
' ' adds the single space in the middle.
substring-before(substring-after(., 'T'), 'Z') --
The inner expression substring-after(., 'T') grabs everything after the T -- that's the time portion.
However, there's that pesky Z on the end, so we use substring-before as the outer expression to lop that off.
No need for variables, and it gets the job done. Confirmed to work with XSLT 1.0.

Try this
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='xml' indent='yes'/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- Edit dates to conform to dbunit format-->
<xsl:template match="#created_at">
<xsl:call-template name="formatdate">
<xsl:with-param name="datestr" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="formatdate">
<xsl:param name="datestr" />
<!-- input format YYYY-MM-DDTHH:MM:SSZ -->
<!-- output format YYYY-MM-DD HH:MM:SS -->
<xsl:variable name="datetext">
<xsl:value-of select="substring-before($datestr,'T')" />
</xsl:variable>
<xsl:variable name="timetext">
<xsl:value-of select="substring($datestr,12,8)" />
</xsl:variable>
<xsl:attribute name="created_at">
<xsl:value-of select="concat($datetext, ' ', $timetext)" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

Related

XSLT - masking data - Conditional on other tags

I am trying to mask an xml document where some specific tags are present. I have created a java app which contains saxon9he as dependency.
<dependencies>
<dependency>
<groupId>net.sf.saxon</groupId>
<artifactId>saxon9he</artifactId>
<version>9.4.0.4</version>
</dependency>
</dependencies>
I have multiple use case, some are straight forward but some are conditional. Assuming the below given <Prsn> tag is present at multiple different locations:
Input xml snippet
<ns3:Prsn>
<ns3:FrstNm>BDMFN</ns3:FrstNm>
<ns3:Nm>BDMSN</ns3:Nm>
<ns3:BirthDt>2000-01-02</ns3:BirthDt>
<ns3:Othr>
<ns3:Id>GB1592102</ns3:Id>
<ns3:SchmeNm>
<ns3:Cd>CCPT</ns3:Cd>
</ns3:SchmeNm>
</ns3:Othr>
</ns3:Prsn>
Transformation that is needed
In this above provided XML, we have some tags [FrstNm, Nm, BirthDt] which we need to mask (remove the actual data from these tags and replace with # for each character), which by the way I have achieved so far.
Need Help
Tricky part is when we have tag <Othr><SchmeNm><Cd> which can have values [NIND, CCPT, CONCAT], we need to mask <Othr><id>, but any other value in <Othr><SchmeNm><Cd> apart from NIND, CCPT, CONCAT then no change in <Othr><id>.
Transformation.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='FrstNm']">
<xsl:copy>
<xsl:value-of select="replace(text(), '[A-Za-z]','#')" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='Nm']">
<xsl:copy>
<xsl:value-of select="replace(text(), '[A-Za-z]','#')" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='BirthDt']">
<xsl:copy>
<xsl:value-of select="replace(text(), '[0-9]','#')" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you want to do regex-based search and replace, the minimum XSLT version you need is XSLT 2.0.
Also, don't use local-name(). Register a prefix for the namespace URI and use that. The prefix does not have to match the XML document, as long as the namespace URI is the same.
Input:
<ns3:Prsn xmlns:ns3="some-namespace-uri">
<ns3:FrstNm>BDMFN</ns3:FrstNm>
<ns3:Nm>BDMSN</ns3:Nm>
<ns3:BirthDt>2000-01-02</ns3:BirthDt>
<ns3:Othr>
<ns3:Id>GB1592102</ns3:Id>
<ns3:SchmeNm>
<ns3:Cd>CCPT</ns3:Cd>
</ns3:SchmeNm>
</ns3:Othr>
</ns3:Prsn>
XSLT 2.0+:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:person="some-namespace-uri"
>
<xsl:output method="xml" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="person:FrstNm|person:Nm|person:BirthDt">
<xsl:copy>
<xsl:value-of select="replace(text(), '[A-Za-z0-9]', '#')" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<ns3:Prsn xmlns:ns3="some-namespace-uri">
<ns3:FrstNm>#####</ns3:FrstNm>
<ns3:Nm>#####</ns3:Nm>
<ns3:BirthDt>####-##-##</ns3:BirthDt>
<ns3:Othr>
<ns3:Id>GB1592102</ns3:Id>
<ns3:SchmeNm>
<ns3:Cd>CCPT</ns3:Cd>
</ns3:SchmeNm>
</ns3:Othr>
</ns3:Prsn>
If you only have XSLT 1.0 available, you can use translate(). But that requires that you either explicitly list all possible input characters:
<xsl:template match="person:FrstNm|person:Nm|person:BirthDt">
<xsl:copy>
<xsl:value-of select="tanslate(
text(),
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-add-everything-else',
'##################################################################################'
)" />
</xsl:copy>
</xsl:template>
or that you settle on something simpler:
<xsl:template match="person:FrstNm|person:Nm|person:BirthDt">
<xsl:copy>
<xsl:text>[redacted]</xsl:text>
</xsl:copy>
</xsl:template>
Tricky part is when we have tag <Othr><SchmeNm><Cd> which can have values [NIND, CCPT, CONCAT], we need to mask <Othr><id>, but any other value in <Othr><SchmeNm><Cd> apart from NIND, CCPT, CONCAT then no change in <Othr><id>.
That's easy. in XSLT 1.0+ this works:
<xsl:template match="
person:FrstNm|person:Nm|person:BirthDt|person:Id[
../person:SchmeNm/person:Cd = 'NIND' or
../person:SchmeNm/person:Cd = 'CCPT' or
../person:SchmeNm/person:Cd = 'CONCAT'
]
">
or even this:
<xsl:template match="
person:FrstNm|person:Nm|person:BirthDt|person:Id[
contains('|NIND|CCPT|CONCAT|', concat('|', ../person:SchmeNm/person:Cd, '|'))
]
">
In XSLT 2.0+ you can use sequences:
<xsl:template match="
person:FrstNm|person:Nm|person:BirthDt|person:Id[
../person:SchmeNm/person:Cd = ('NIND', 'CCPT', 'CONCAT')
]
">

Parsing nested XML File as node set

I have a problem. I have an xml file as below :
Main.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<msrsw>
<software>
<chapter>
<name> Hello world</sample>
<xref type="xml">C:\ABC\NestedXML.xml</xref>
</chapter>
</software>
</msrsw>
NestedXml.xml :
<?xml version="1.0" encoding="ISO-8859-1"?>
<msrsw>
<software>
<chapter>
<name> Nested XML </sample>
<age>14</age>
<country>Canada</country>
</chapter>
</software>
</msrsw>
I am using Apache FOP to produce PDF document (Using Java). FOP needs src and dest params. Src being Main.xml.
My XSLT is as below:
<xsl:param name="xmlFileName" />
<xsl:param name="XMLFile" select="document($xmlFileName)"/>
<xsl:template name="Chapters_1_2_Template">
<xsl:apply-templates select="$XMLFile/*" mode="chapter" />
</xsl:template>
<xsl:template match="node()" mode="chapter">
<xsl:for-each select="node()">
<xsl:if test="current()[name() = 'xref']">
<xsl:apply-templates select="current()[name() = 'xref']" mode="x" />
</xsl:if>
<xsl:if test="current()[name() = 'name']">
<xsl:apply-templates select="current()[name() = 'name']" mode="name" />
</xsl:if>
<xsl:if test="current()[name() = 'age']">
<xsl:apply-templates select="current()[name() = 'age']" mode="age" />
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="name" mode="name">
<xsl:value-of select="current()" />
</xsl:template>
<xsl:template match="age" mode="age">
<xsl:value-of select="current()" />
</xsl:template>
<xsl:template match="country" mode="country">
<xsl:value-of select="current()" />
</xsl:template>
<xsl:template match="xref" mode="x">
<xsl:param name="XMLFile" select="document(current())"/>
<xsl:call-template name="Chapters_1_2_Template" />
</xsl:template>
When I execute the above code, i get the following error in the line <xsl:apply-templates select="$XMLFile/*" mode="chapter" />:
Invalid token XMLFile.
When I remove $XMLFile - <xsl:apply-templates select="*" mode="chapter" /> , it works fine for me with Main.xml file's content.
When I just display the value of current() in xref template, it is parses the NestedXml.xml file and displayes string values in PDF.
Expected Output is:
Hello world
Nested XML
14
Canada
My requirement is to embed the node-set of NestedXML.xml within the<xref></xref> tags and recursively applyTemplates on that node-set.
But the document() function gives me the parsed String content of NestedXml.xml.
I do not want to use the document() function and just get the String value of NestedXml.xml. I need to parse all the tags of NestedXml.xml using the same XSLT.
Please suggest me where am I going wrong. Is this approach right? Is there any other way to do this?
Or it is not possible to do in this way using XSLT and that XSLT allows including only the parsed String values?
If it is that you want just some text nodes, the stylesheet needn't be that complex. Try this simpler stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="msrsw/software/chapter/name"/>
<xsl:apply-templates select="document(msrsw/software/chapter/xref)/*"/>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space()"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

CDATA XML masking with XSLT should return same XML with few masked fields

I have a requirement of masking few fields in XML of CDATA inside XML with XSLT.
So the resultant XML should be same like the input XML but few fileds are masked with XSLT.
I followed this link which is masking as expected but producing XML is in different format.
I tried many other solutions from SO, they are almost outputing the new XML/HTML in other format which is different from the input XML.
Please check the following example for better understading.
Input XML with CDATA content.
<XML>
<LogLevel>info</LogLevel>
<Content><![CDATA[ <Msg>
<AccountNo>2701000098983</AccountNo>
<ApplName>Testing</ApplName>
</Msg>]]></Content>
<Date>20140909</Date>
</XML>
Output XML should be:
<XML>
<LogLevel>info</LogLevel>
<Content><![CDATA[ <Msg>
<AccountNo>XXXXXXXXXX983</AccountNo>
<ApplName>Testing</ApplName>
</Msg>]]></Content>
<Date>20140909</Date>
</XML>
Edit:
I used the following XSLT
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:choose>
<xsl:when test="contains(.,'<AccountNo>')">
<!-- This is the CDATA that I want to mask and write back out as CDATA -->
<xsl:variable name="tcontent">
<xsl:value-of
select="substring-after(substring-before(.,'</AccountNo>'),'<AccountNo>') " />
</xsl:variable>
<xsl:text disable-output-escaping="yes"><![CDATA[<AccountNo></xsl:text>
<xsl:call-template name="maskVariable">
<xsl:with-param name="tvar" select="$tcontent" />
</xsl:call-template>
<xsl:text disable-output-escaping="yes"></AccountNo>]]></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="maskVariable">
<xsl:param name="tvar" />
<xsl:variable name="length" select="string-length($tvar)" />
<xsl:choose>
<xsl:when test="$length > 3">
<xsl:value-of
select="concat ('************', substring($tvar,$length - 1, 2))" />
</xsl:when>
<xsl:when test="$length > 1">
***
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
Output of using this XSLT is :
<LogLevel>info</LogLevel>
<Content><![CDATA[<AccountNo>************02</AccountNo>]]></Content>
<Date>20140909</Date>
Here in output, only masked output of is displaying.
How to make other part of the code to get displayed ?
Please give me some idea how to do it ?
Any help is highly appreciated.
Why don't you 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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Content">
<xsl:copy>
<xsl:value-of select="substring-before(.,'<AccountNo>')" />
<xsl:text><AccountNo></xsl:text>
<xsl:variable name="acct-num" select="substring-before(substring-after(.,'<AccountNo>'), '</AccountNo>')" />
<xsl:value-of select="concat('************', substring($acct-num, string-length($acct-num) - 2))" />
<xsl:text></AccountNo></xsl:text>
<xsl:value-of select="substring-after(.,'</AccountNo>')" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Applied to your input, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<LogLevel>info</LogLevel>
<Content> <Msg>
<AccountNo>************983</AccountNo>
<ApplName>Testing</ApplName>
</Msg></Content>
<Date>20140909</Date>
</XML>
Alternatively, you could use:
<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" cdata-section-elements="Content"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Content">
<xsl:copy>
<xsl:variable name="content">
<xsl:value-of select="substring-before(.,'<AccountNo>')" />
<xsl:text><AccountNo></xsl:text>
<xsl:variable name="acct-num" select="substring-before(substring-after(.,'<AccountNo>'), '</AccountNo>')" />
<xsl:value-of select="concat('************', substring($acct-num, string-length($acct-num) - 2))" />
<xsl:text></AccountNo></xsl:text>
<xsl:value-of select="substring-after(.,'</AccountNo>')" />
</xsl:variable>
<xsl:value-of select="$content" disable-output-escaping="yes"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
to produce:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<LogLevel>info</LogLevel>
<Content><![CDATA[ <Msg>
<AccountNo>************983</AccountNo>
<ApplName>Testing</ApplName>
</Msg>]]></Content>
<Date>20140909</Date>
</XML>
although this might not work with every processor (tested to work with Xalan 2.7.1: http://xsltransform.net/jyH9rMk).
The stuff inside the CDATA section is XML disguised as text. XSLT is good at transforming XML, it's not so good at transforming text, especially text with a complex grammar. So my approach would be: extract the text from the outer XML document, parse it as XML, transform it using XSLT (real XSLT that works on nodes rather than on markup), serialize it back to text, then stuff the text back into the original (outer) XML document.
Raw XSLT 1.0 can't do this within a single stylesheet. You need the functions parse() and serialize() to turn lexical XML into a node tree, and back again. These are available as extensions in some processors (such as Saxon), they become available as standard functions in XPath 3.0, and they can be written as simple extension functions (e.g. in Javascript) code in most other processors.

XSLT Transformation to output a element if not present in the source, if present leaving as it is

Basically I have source XML
<RootElement attr=yes>
<parentElement>
<ChildElement1>some value in str</ChildElement1>
<ChildElement2>some value in str</ChildElement2>
<ChildComplexType1>
<grandChildElement1>some value in str</grandChildElement1>
</ChildComplexType1>
</parentElement>
</RootElement>
I am using XSLT to change it to some other XML.
The consumer consuming the result XML expects as below
<RootElement attr=yes>
<parentElement>
<ChildElement1>some value in str</ChildElement1>
<ChildElement2>some value in str</ChildElement2>
<ChildComplexType1>
<grandChildElement1>some value in str</grandChildElement1>
<grandChildElement2>Default Value/ From Source XML</grandChildElement2>
</ChildComplexType1>
</parentElement>
</RootElement>
The problem is I am using the below Matches rule and it is not working.
Can anyone suggest a better rule that works ?
I believe this is a basic question, Apologize since I am new to XSLT.
<xsl:template match="#*|node()" priority="1">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*:parentElement"
priority="2">
<xsl:element name="{name()}"
namespace="http://namespace"
inherit-namespaces="no">
<xsl:namespace name="ns5"
select="'namespace'" />
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="*:ChildComplexType1">
<xsl:if test="not(*:grandChildElement2)" priority="3">
<grandChildElement2>defaultValue</grandChildElement2>
</xsl:if>
<xsl:apply-templates />
</xsl:template>
I think the problem is with the use of the "priority" attribute on the identity template
<xsl:template match="#*|node()" priority="1">
This has given it a higher priority than the template matching *::ChildComplexType1, and so this template is not ever matched.
Try removing the priorities, like so:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*:parentElement">
<xsl:element name="{name()}"
namespace="http://namespace"
inherit-namespaces="no">
<xsl:namespace name="ns5"
select="'namespace'" />
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="*:ChildComplexType1">
<xsl:copy>
<xsl:apply-templates />
<xsl:if test="not(*:grandChildElement2)">
<grandChildElement2>defaultValue</grandChildElement2>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that I added an xsl:copy to the ChildComplexType1 template too, but you seem to be doing something with namespaces that you have not mentioned in your question, so you might need to change that to an xsl:element instead.
Note, see http://www.w3.org/TR/xslt#conflict for information on template priority. In particular, note how the highest default priority is 0.5.
It seems as you are using the Identity Template.
You could look at this page here which describes the template quite thoroughly.
http://www.xmlplease.com/xsltidentity#s1.

transform int values to color objects using XSL and Java

I am new in XSL and I have an xml file with tags representing int rgb colors,
I want using XSL to convert them into Color object tags:
my xml contains these tags
<?xml version="1.0" encoding="UTF-8"?>
.
.
.
<ForeGroundColour>-16776961</ForeGroundColour>
.
.
.
</xml>
the desired xml is:
<ForeGroundColour>
<red>102</red>
<blue>102</blue>
<green>255</green>
<alpha>255</alpha>
</ForeGroundColour>
my XSL file is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:Color="java.awt.Color"
xmlns:Integer="java.lang.Integer" exclude-result-prefixes="Color Integer ">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<!-- identity template !-->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="//*[contains(name(),'ForegroundColour')]">
<xsl:copy>
<xsl:variable name="rgb" select="." />
<xsl:variable name="color" select="Color:new(Integer:parseInt($rgb))" />
<xsl:element name="red">
<xsl:value-of select="Color:getRed($color)" />
</xsl:element>
<xsl:element name="blue">
<xsl:value-of select="Color:getBlue($color)" />
</xsl:element>
<xsl:element name="green">
<xsl:value-of select="Color:getGreen($color)" />
</xsl:element>
<xsl:element name="alpha">
<xsl:value-of select="Color:getAlpha($color)" />
</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I have two problems:
1- when I run this code, I get the following exception:
Exception in thread "main" java.lang.VerifyError: (class: transformer, method: template$dot$1 signature: (Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;I)V) Expecting to find double on stack
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
at java.lang.Class.getConstructor0(Class.java:2699)
at java.lang.Class.newInstance0(Class.java:326)
at java.lang.Class.newInstance(Class.java:308)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:364)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:394)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:649)
at com.nyfix.ostp.server.workspacemigrator.XMLTransformer.transform(XMLTransformer.java:44)
at com.nyfix.ostp.server.workspacemigrator.XSLMigrator.migrate(XSLMigrator.java:45)
at com.nyfix.ostp.server.workspacemigrator.WorkspaceMigratorFactory.<init>(WorkspaceMigratorFactory.java:19)
at com.nyfix.ostp.server.workspacemigrator.WorkspaceMigratorFactory.main(WorkspaceMigratorFactory.java:45)
2- when I use the template just to test the inserted nodes,
<xsl:template match="//*[contains(name(),'ForegroundColour')]">
<xsl:copy>
<xsl:variable name="rgb" select="." />
<xsl:element name="red">
<xsl:value-of select="$rgb" />
</xsl:element>
<xsl:element name="blue">
<xsl:value-of select="$rgb" />
</xsl:element>
<xsl:element name="green">
<xsl:value-of select="$rgb" />
</xsl:element>
<xsl:element name="alpha">
<xsl:value-of select="$rgb" />
</xsl:element>
</xsl:copy>
</xsl:template>
the xml is transformed without indentation:
<ForegroundColour>
<red>-16776961</red>
<blue>-16776961</blue>
<green>-16776961</green>
<alpha>-16776961</alpha>
</ForegroundColour>
can anyone help me with this? thanks in advance
XSLT variables can contain only strings, booleans, numbers or node-sets - the Color:new(...) call returns a Java object, that cannot be assigned to an XSLT variable, hence the exception.
Possible solutions:
1) Create your own extension class with methods stringToRed, stringToBlue etc defined so that they accept a string, convert it internally to the Color object and then extract the wanted component (Red, Blue etc) as a number or string . These functions then can be used directly:
<xsl:element name="red">
<xsl:value-of select="MyColorFunctions:stringToRed(.)" />
</xsl:element>
2) 'Reverse-engineer' the algorithm that converts a color expressed as an integer to its component, and implement it directly in XSLT, without using extension function. Typically the red component will be the 8 least significative bits, the blu the next 8 bits and so on (or vice-versa), and you have to handle the negative values (convert from 2-complement to absolute) - so something like this:
<xsl:variable name="color">
<xsl:choose>
<xsl:when test="number(.) >=0">
<xsl:value-of select="number(.)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="4278190080+number(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<red>
<xsl:value-of select="floor($color mod 256)" />
</red>
<green>
<xsl:value-of select="floor(($color div 256) mod 256)" />
</green>
<blue>
<xsl:value-of select="floor(($color div 65536) mod 256)" />
</blue>
<alpha>
<xsl:value-of select="floor(($color div 16777216) mod 256)" />
</alpha>
The problem with this apprach is that XSLT use floating point arithmentic, so it is possible that rounding errors cause wrong values for the components in some cases.
it's a little bit late to answer the question, but it's for anyone who can have the same problem,
for the indenation issue, I had to use custom xsl templates to make sure the output xml is indentend in the way I want.
the template includes many dirty code that forces xml tags to have special spacing that simulates the indentation
hope this help :)

Categories