I have xml with a tag which contains an attribute with a html content in it. I need to convert this html to xsl-fo . Here is my xslt code:
<xsl:template match ="rtf">
<fo:block-container>
<fo:block>
<xsl:call-template name ="ConvertHtmlToXslfo">
<xsl:with-param name ="content">
<xsl:value-of select ="#rtfAsHtml" disable-output-escaping ="yes"/>
</xsl:with-param>
</xsl:call-template>
</fo:block>
</fo:block-container>
</xsl:template>
<xsl:template name="ConvertHtmlToXslfo">
<xsl:param name ="content"></xsl:param>
<fo:block-container>
<xsl:apply-templates select="msxsl:node-set($content)"/> <!--here is the problem-->
</fo:block-container>
</xsl:template>
<xsl:template match ="div">
<fo:block-container>
<!--more code here-->
</fo:block-container>
</xsl:template>
<xsl:template match ="p">
<fo:block>
<!--more code here-->
</fo:block>
</xsl:template>
<xsl:template match ="span">
<fo:inline>
<!--more code here-->
</fo:inline>
</xsl:template>
But there is a problem, when in call apply-templates on this html content. the relevant templates doesn't recognize it.
Here is the xml Tag with Html attribute in it:
<rtf rtfAsHtml="<div ><p ><span>Hi!</span></p></div>"/>
Any idea how to convert this Html tag to xsl-fo ?
thank you!
The problem is that your xml/html content is escaped. You try to apply disable-output-escaping, but that only works upon writing to output file or stream. So, you are effectively just submitting the unescaped attribute contents against your templates, which doesn't do much indeed.
Not sure what kind of XSLT parser you are using, but if you use Saxon, you could try to apply saxon:parse:
http://saxonica.com/documentation9.4-demo/html/extensions/functions/parse.html
This does require the contents of the attribute to be well-formed. If not, you could try:
http://saxonica.com/documentation9.4-demo/html/extensions/functions/parse-html.html
HTH!
Related
I think it's a very simple question. But although I build very fancy xslt transformation, this simple one cannot be solved by me.
The problem is:
I want to add attributes to xsl-fo nodes, depending on xml data. These attributes have often a hyphen in it. How can I add these with an xslt transformation where xsl:attributes doesn't like the hyphenation character.
In a xml node I have got two attributes (name and value)
Example: name="font_size", value="7pt"
<Report>
<text content="I am a text">
<blockFormat name="font_size" value="7pt" />
</text>
</Report>
(I understand this is not wanted because you want to work with styles etceters. It's just an example with a simplified problem)
Now I want to make a xsl-fo block, and I want to place that attributes in the block element by using the xsl-function xsl:attribute
<fo:block>
<attribute name="{replace(#name,'_','-')}" select="#value" />
....
</fo:block>
goal to achieve after transformation
<fo:block font-size="7pt">
....
</fo:block
It doesn't function and I think this is because in xslt I can't put an hyphen in the attribute name, but in the fo-attribute it is needed.
Is there a way to use the xsl:attribute function for this?
And when not, what kind of working around do you suggest.
Thank you for helping!!!!
There are 1000 ways to do it, here is one (I didn't do anything with your Report element):
Input:
<Report>
<text content="I am a text">
<blockFormat name="font_size" value="7pt" />
</text>
</Report>
XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="1.0">
<xsl:template match="Report">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<fo:block>
<xsl:apply-templates select="blockFormat/#*"/>
<xsl:value-of select="#content"/>
</fo:block>
</xsl:template>
<xsl:template match="#name">
<xsl:attribute name="{translate(.,'_','-')}">
<xsl:value-of select="ancestor::blockFormat/#value"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#value"/>
</xsl:stylesheet>
Output:
<Report>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" font-size="7pt">I am a text</fo:block>
</Report>
Use #select instead of #value:
<fo:block>
<attribute name="{replace(#name,'_','-')}" select="#value" />
....
</fo:block>
See https://www.w3.org/TR/xslt20/#creating-attributes
Also, you need to be using XSLT 2.0 or 3.0 to use #select. If you're using XSLT 1.0, you'd have to do it as xsl:attribute/xsl:value-of/#select.
(It would also have helped understanding of your problem if you'd also shown the wrong result that you were getting.)
I am using XSL-FO and FOP .95, whenever i write a code in xsl-fo i have to use this statement to generate an empty space:
<fo:block>
<xsl:choose>
<xsl:when test="normalize-space(Seller_Name)!=''">
<xsl:value-of select="normalize-space(Seller_Name)"/>
</xsl:when>
<xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise>
</xsl:choose>
</fo:block>
I dont want to use these choose when conditions to generate an empty space to save the block collapse. is there any function or property which can be used here? I have tried line-feed-treatment and white-space-collapse but it didnt work. Please advise something.
IF you are happy with what you have above, why not template it. This would reduce the call to three lines:
<xsl:template name="blockwithblank">
<xsl:param name="field"/>
<fo:block>
<xsl:choose>
<xsl:when test="normalize-space($field)!=''">
<xsl:value-of select="$field"/>
</xsl:when>
<xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise>
</xsl:choose>
</fo:block>
</xsl:template>
That above is once in the whole stylesheet, then each of the calls is only three lines:
<xsl:call-template name="blockwithblank">
<xsl:with-param name="field" select="Seller_Name"/>
</xsl:call-template>
I am not sure you can shorten it more than three lines each call.
Use two templates: one for the regular case and the other, with a higher priority, for the empty case:
<xsl:template match="Seller_Name">
<fo:block>
<xsl:value-of select="normalize-space()"/>
</fo:block>
</xsl:template>
<xsl:template match="Seller_Name[normalize-space() = '']" priority="5">
<fo:block> </fo:block>
</xsl:template>
Your XSLT would need to have a xsl:apply-templates at the appropriate point, but it would make the current template shorter.
If you're doing this a lot with multiple elements, you could match on multiple elements in each of these templates and save a lot of repetition.
Below is my xml file.
<xml>
<top>
<main>
<firstname>John</firstname>
<lastname>John</lastname>
<table></table>
<chapter>
<firstname>Alex</firstname>
<lastname>Robert</lastname>
<p>Sample text chap</p>
<figure name="f1.svg"></figure>
<chapter>
<firstname>Rebec</firstname>
<lastname></lastname>
<p>Sample text</p>
<figure name="f2.svg"></figure>
</chapter>
</chapter>
</main>
</top>
</xml>
Desired output:
<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg
Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime.
I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations.
This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown.
And how do I match only 'lastname' and make it bold?
I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes.
Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.
XSLT:
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>
<xsl:template name="FetchNodes">
<xsl:param name="endIndex" />
<xsl:param name="startIndex" />
<xsl:param name="context" />
<xsl:if test="$startIndex <= $endIndex">
<xsl:if test="$context[$startIndex][name() = 'table']"">
<xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
</xsl:if>
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$endIndex" />
<xsl:with-param name="startIndex" select="$startIndex + 1"/>
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="table">
<xsl:value-of select="node()" />
</xsl:template>
With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.
Can anybody suggest something?
The problem it displaying "f1.svg" instead of "f2.svg" is because of this line
<xsl:variable name="ImageName">
<xsl:value-of select="$root/*/chapter/figure/#name" />
</xsl:variable>
You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first #name attribute regardless of your context. It should look this this
<xsl:variable name="ImageName">
<xsl:value-of select="#name" />
</xsl:variable>
Or better still, like this
<xsl:variable name="ImageName" select="#name" />
Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example
<xsl:template match="figure" mode="figure">
As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:
<xsl:choose>
<xsl:when test="self::lastname">
<fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()" />
</xsl:otherwise>
</xsl:choose>
EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.
For example, to turn a chapter into an fo:block do this
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
To output the firstname in bold, do this
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
Try this XSLT as a starting point, and build on it
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="main">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
<xsl:template match="lastname"/>
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>
I am generating a PDF using XSL-FO and XML. In a textbox, the user can enter data like "1", then he presses ENTER, then "2", ENTER, "3", etc. But in the XML and hence in the PDF, the output is "1234567". How can I preserve the line breaks? I already tried white-space-collapse, linefeed-treatment and white-space-treatment but that didn't help.
My XSL looks like:
<xsl:template match="AddCmt">
<fo:block keep-together="always"> Additional Comments
<fo:block-container border-style="solid" height="20mm" width="170mm" space-after="5mm">
<fo:block>
<xsl:attribute name="id">
<xsl:value-of select="../CMT_ID"/>
</xsl:attribute>
<xsl:value-of select="../ANS_CMT"/>
</fo:block>
</fo:block-container>
</fo:block>
</xsl:template>
When I enter the following:
hello
medhavi
saraswat
This is the XML I get:
<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type='text/xsl' href='e:\tmm-09.3\src\pmod\WorkOrder.xsl'?>
<Root>
<WorkOrders>
<Detail>Id="ANS_436_FLD_1" Label="qq">qq</Detail>
<Ans Checked="0" Id="ANS_436_FLD_2" Label="ww">ww</Ans>
<ID>ANS_436_FLD</ID>
<ANS_FLD>0|0</ANS_FLD>
<CMT_ID>ANS_436_CMT</CMT_ID>
<ANS_CMT>hello medhavi saraswat</ANS_CMT>
<Warning>
<Line>warning 11</Line>
<Line>22</Line>
<Line>33</Line>
<Line>44</Line>
<Line></Line>
<Line>66</Line>
<Line>77</Line>
<Line></Line>
</Warning>
It should work with the following xml (you should add all the attributes):
<xsl:template match="AddCmt">
<fo:block keep-together="always"> Additional Comments
<fo:block-container border-style="solid" height="20mm" width="170mm" space-after="5mm">
<fo:block wrap-option="wrap" linefeed-treatment="preserve" white-space-collapse="false" white-space-treatment="preserve">
<xsl:attribute name="id">
<xsl:value-of select="../CMT_ID"/>
</xsl:attribute>
<xsl:value-of select="../ANS_CMT"/>
</fo:block>
</fo:block-container>
</fo:block>
</xsl:template>
But as I mentioned in the comments, if your XML already has no linebreaks, there's no way your PDF will. You mentioned in your question there are no linebreaks in your XML, hence no linebreaks in the PDF.
Try checking out why there are no linebreaks in the XML. If you can provide any more information (a piece of your XML, the code you use to construct the XML, ...), please edit your answer and add the information.
I am using XSL FO to generate a PDF file containing a table with information. One of these columns is a "Description" column. An example of a string that I am populating one of these Description fields with is as follows:
This is an example Description.<br/>List item 1<br/>List item 2<br/>List item 3<br/>List item 4
Inside the table cell that corresponds to this Description, I would like the output to display as such:
This is an example Description.
List item 1
List item 2
List item 3
List item 4
I've learned from searching elsewhere that you can make line breaks in XSL FO using an <fo:block></fo:block> within another <fo:block> element. Therefore, even before I parse the XML with my XSL stylesheet, I replace all occurrences of <br/> with <fo:block/>, so that the literal value of the string now looks like:
This is an example Description.<fo:block/>List item 1<fo:block/>List item 2<fo:block/>List item 3<fo:block/>List item 4
The problem arises when the Description string I am using is obtained using <xsl:value-of>, example as follows:
<fo:block>
<xsl:value-of select="descriptionStr"/>
</fo:block>
In which case, the value that gets output to my PDF document is the literal value, so it looks exactly like the previous example with all the <fo:block/> literals. I've tried manually hard-coding the <fo:block/> in the middle of another string, and it displays correctly. E.g. if I write inside my stylesheet:
<fo:block>Te<fo:block/>st</fo:block>
It will display correctly as:
Te
st
But this does not seem to happen when the <fo:block/> is inside the value of an <xsl:value-of select=""/> statement. I've tried searching for this on SO as well as Google, etc. to no avail. Any advice or help will be greatly appreciated. Thank you!
You could also replace <br/> with
and add a linefeed-treatment="preserve" attribute to your <fo:block>.
Something like:
<fo:block linefeed-treatment="preserve">This is an example Description.
List item 1
List item 2
List item 3
List item 4</fo:block>
Edit
Some users may need to use \n instead of
depending on how they are creating the XML. See Retain the
during xml marshalling for more details.
This helped me and should be simplest solution (working with Apache FOP 1.1):
Why not replace your <br/> with Unicode character called line separator.
<xsl:template match="br">
<xsl:value-of select="'
'"/>
</xsl:template>
See https://en.wikipedia.org/wiki/Newline#Unicode
The following code worked:
<fo:block white-space-collapse="false"
white-space-treatment="preserve"
font-size="0pt" line-height="15px">.</fo:block>
It makes the xsl processor thinks this block contains a line of text, which actually has a 0pt font size.
You can customize line height by providing your own value.
You shouldn't use xsl:value-of instruction but xsl:apply-templates instead: for built-in rule for text node will just output their string value, and for empty br element you could declare a rule matching descriptionStr/br or descriptionStr//br (depending your input) in order to transform to empty fo:block.
Generating strings containing escaped XML markup is seldom the right answer, but if that's what you have to work with, then for input like this:
<Description><![CDATA[This is an example Description.<br/>List item 1<br/>List item 2<br/>List item 3<br/>List item 4]]></Description>
if you're using XSLT 2.0, you can use xsl:analyze-string to get the empty fo:block that you originally wanted:
<xsl:template match="Description">
<fo:block>
<xsl:analyze-string select="." regex="<br/>">
<xsl:matching-substring>
<fo:block />
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</fo:block>
</xsl:template>
but if you are using XSLT 2.0, you can more concisely use linefeed-treatment="preserve" as per #Daniel Haley and use replace() to insert the linefeeds:
<xsl:template match="Description">
<fo:block linefeed-treatment="preserve">
<xsl:value-of select="replace(., '<br/>', '
')" />
</fo:block>
</xsl:template>
If you are using XSLT 1.0, you can recurse your way through the string:
<xsl:template match="Description">
<fo:block linefeed-treatment="preserve">
<xsl:call-template name="replace-br" />
</fo:block>
</xsl:template>
<xsl:template name="replace-br">
<xsl:param name="text" select="." />
<xsl:choose>
<xsl:when test="not(contains($text, '<br/>'))">
<xsl:value-of select="$text" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($text, '<br/>')"/>
<xsl:text>
</xsl:text> <!-- or <fo:block /> -->
<xsl:call-template name="replace-br">
<xsl:with-param name="text" select="substring-after($text, '<br/>')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Try this:
<fo:block><fo:inline color="transparent">x</fo:inline></fo:block>
This code adds a block which contains transparent text, making it look like a new line.
Try using linefeed-treatment="preserve" and \n instead of <br> for a new line.
<fo:block linefeed-treatment="preserve" >
<xsl:value-of select="Description" />
</fo:block>
For XSLT 1.0 I'm using my XSLT Line-Break Template on GitHub.
For XSL-FO it supports
Line breaks
Line delimiters (vs Line breaks)
Series of pointers in a row
Ignore Pointer Repetitions (disable the Series of pointers in a row)
Any string as a pointer to insert a break or a delimiter ("\n" is default)
Line delimiters' height
Default Line delimiter height from a current font size.
Auto ignoring of the "\r" char when searching a break place.
Added support for XSLT 2.0 for a seamless migration.
something else...
For XSLT 2.0 and later consider to use approaches like
XSLT 2.0 xsl:analyze-string (RegEx)
XPath 2.0 tokenize + XSLT (RegEx)
passing sequences as a template parameter (XSLT 2.0)
and so on
I usually use an empty block with a height that can be changed if I need more or less space:
<fo:block padding-top="5mm" />
I know this isn't the best looking solution but it's funtional.
I had a text block that looks like this
<fo:table-cell display-align="right">
<fo:block font-size="40pt" text-align="right">
<xsl:text> Text 1 </xsl:text>
<fo:block> </fo:block>
<xsl:text> Text2 </xsl:text>
<fo:block> </fo:block>
<xsl:text> Text 3</xsl:text>
</fo:block>
NB: note the empty
</fo:block> on it's own is not a direct substitute for <br/> <br/> is an html unpaired abberation that has no direct equivalent in xsl:fo
</fo:block> just means end of block. If you scatter them through your text you wont have valid xml, and your xsl processor will sick up errors.
For the line break formatting you want, each block will occur on a new line. You need a <fo:block> start block and </fo:block> end block pair for each line.