I am using Apache FOP for PDF generation.I want to use unparsed-text() function to read non-xml document in XSL file.
After writing that function i got this error.
This is my XSL file.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:f="Functions">
<xsl:variable name="properties" select="unparsed-text('file.properties')" as="xs:string"/>
<xsl:function name="f:getProperty" as="xs:string?">
<xsl:param name="key" as="xs:string"/>
<xsl:variable name="lines" as="xs:string*" select="
for $x in
for $i in tokenize($properties, '\n')[matches(., '^[^!#]')] return
tokenize($i, '=')
return translate(normalize-space($x), '\', '')"/>
<xsl:sequence select="$lines[index-of($lines, $key)+1]"/>
</xsl:function>
<xsl:template match=" EmployeeData">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple"
page-height="20cm" page-width="10.5cm" margin-left="0.2cm"
margin-right="0.2cm">
<fo:region-body margin-top="0.5cm" />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<xsl:variable name="lang" select="language" />
<fo:flow flow-name="xsl-region-body">
From Properties File <xsl:value-of select="f:getProperty('language')"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
How can i remove this error? Or give me any alternative for that if possible.
Thank you.
The error suggests you are using an XSLT 1.0 processor to run the XSLT. unparsed-text is only supported by XSLT 2.0 processors like Saxon 9.
Related
I am writing a program in java( pre-junior), I really need help with xslt transformation. It is necessary to make a csv file from xml.
I got this xslt filter:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="node()" name="conv">
<xsl:call-template name="loop"/>
</xsl:template>
<xsl:template name="loop">
<xsl:for-each select="./*[count(*) = 0]">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:if test="position() = last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="./*[(count(*) != 0) and (name()!='PARAMETRS')] ">
<xsl:call-template name="loop"/>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Source xml:
<Integration>
<PARAMETRS>
<ID>AZD</ID>
<DATE>2020-01-01</DATE>
</PARAMETRS>
<ORG>
<Thing>
<object>10220</object>
<type>U</type>
<dyn>
<items>
<val>988009</val>
<datebegin>2019-12-12</datebegin>
</items>
</dyn>
</Thing>
<Thing>
<object>10221</object>
<type>U</type>
<dyn>
<items>
<val>988010</val>
<datebegin>2019-12-13</datebegin>
</items>
<items>
<val>988011</val>
<datebegin>2019-12-14</datebegin>
</items>
</dyn>
</Thing>
</ORG>
</Integration>
In the output, I get comma-separated lines, and a few more lines (those same items) with the values below. and can't figure out how to concatenate the values ...
I would do it via value-of select = "concat" but my may have several dyn (1, 2, 3 ...), hence this is not suitable.
The output needs a csv separated by commas.
Please advise how to concatenate the item with its parent? Or there are simpler ways to parse xml with a different number of subsections(childs).
Expected output:
10220,U,988009,2019-12-12
10221,U,988010,2019-12-13,988011,2019-12-14
The output you show can be easily obtained using the following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/Integration">
<xsl:for-each select="ORG/Thing">
<xsl:value-of select="object"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="type"/>
<xsl:text>,</xsl:text>
<xsl:for-each select="dyn/items">
<xsl:value-of select="val"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="datebegin"/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that the output has a set of columns for each items; this is not an ideal CSV structure.
If you can use XSLT 2.0, it opens up new powerful functionality.
Oracle XML Developer Kit (XDK) supports XSLT 2.0
Here is the link: Using the XSLT Processor for Java
The approach below is doing the following:
Using string-join() function to concatenate all child elements values
on a different hierarchy level via .//*/(text()[1] expression.
xs:token casting removes white spaces.
XPath predicate [. != ''] removes empty sequence members.
XSLT 2.0
<?xml version='1.0'?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/Integration">
<xsl:for-each select="ORG/Thing">
<xsl:value-of select="string-join((.//*/(text()[1] cast as xs:token?))[. != ''],',')"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output
10220,U,988009,2019-12-12
10221,U,988010,2019-12-13,988011,2019-12-14
Based on the Marting Honnen great tip, here is even more concise XSLT 2.0 version without any loop.
XSLT 2.0
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/Integration">
<xsl:value-of select="ORG/Thing/string-join((.//*/(text()[1] cast as xs:token?))[. != ''],',')" separator="
"/>
</xsl:template>
</xsl:stylesheet>
Apologises if duplicate. I want to perform a concat operation on the values of the xml elements.
I have xml say Input.xml
<collection>
<one>
<part>1</part>
</one>
<two>
<part>2</part>
</two>
<three>
<part>3</part>
</three>
</collection>
I want the output like :
<collection>
<one>
<part>001</part>
</one>
<two>
<part>002</part>
</two>
<three>
<part>003</part>
</three>
</collection>
how to write xslt for it
Quick hacky XSLT , tested it online it seems to work.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|text()|comment()|processing-instruction">
</xsl:template>
<xsl:template match="//part">
<part>
<xsl:text>00</xsl:text>
<xsl:value-of select="."></xsl:value-of>
</part>
</xsl:template>
</xsl:stylesheet>
Your possible solution might be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="part">
<xsl:value-of select="format-number(., '000')"/>
</xsl:template>
<!-- identity copy -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Format the text in part to a 3 digit number format
I'm using fop to convert xml to pdf, so for that I have written an xslt code.
In the same xslt, I have used java code but somehow I get an error stating nosuchmethodexception : couldn't find method org.apache.xml.utils.NodeVector.input([ExpressionContext,]).
But my java code is a user defined code which is in a different package.
My xml has local_curr attribute.
My java class name is XMLData.
Package is com.pdf
Java Method is input which takes a String value and returns a String
xslt code :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:java="com.pdf.XMLData">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- Defining Page Layout -->
<fo:layout-master-set>
<fo:simple-page-master master-name="A3-portrait"
page-height="29.7cm" page-width="40.0cm" margin="2cm">
<fo:region-body margin-bottom="20mm" />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A3-portrait">
<fo:flow flow-name="xsl-region-body">
<fo:table table-layout="fixed" width="100%" border-width="1mm"
border-collapse="separate">
<fo:table-row>
<fo:table-cell background-color="#F79F81"
border-width="0.1mm" border-style="solid">
<fo:block wrap-option="wrap" font-size="15pt"
padding="5pt" text-align="right">
<xsl:value-of select="java:input(#local_curr)" />
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
The problem is that #local_curr probably does not return the type your java method is expecting.
You need to parse it for example as a string
<xsl:value-of select="java:input(string(#local_curr))" />
Or as a number
<xsl:value-of select="java:input(number(#local_curr))" />
The NoSuchMethodException was thrown because no method was found with the given type as argument.
Thanks for the help.
It is resolved. Actually I have to add my jar file in the batch file of fop which resolved my problem.
Thanks...
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>
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.