Apache FOP keeping two elements together not working - apache

I have following xslt:
<xsl:template match="P">
<fo:block>
1.1 First Image
</fo:block>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="figure">
<fo:block margin-top="-0.30in" keep-with-previous.within-page="always">
<fo:external-graphic>
<xsl:attribute name="src"> <xsl:value-of select="abc.svg" />
</xsl:attribute>
</fo:external-graphic>
</fo:block>
</xsl:template>
Both these templates will be called in a recursive method.
My output is :
P tag is coming in one page where it has space. Since figures are big, it shows in the next page. I want them to be together.
Tried solutions: keep-with, break-after, page-break-after.
Keep-with is not working. Others are creating new page for every P tag though there is space in the previous tag.
I am generating PDF output using FOP1.0.
Please help.
Thanks in advance.

change
<xsl:template match="P">
<fo:block>
1.1 First Image
</fo:block>
<xsl:apply-templates />
</xsl:template>
to
<xsl:template match="P">
<fo:block keep-with-next.within-page="always">
1.1 First Image
</fo:block>
<xsl:apply-templates />
</xsl:template>

Related

Using XSLT1 how can I simply assign an incrementing value beginning with 1?

In my XSL script I have the following:
<body>
<xsl:for-each select="MeetingWorkBook/Meeting">
<xsl:apply-templates select="TFGW/BibleReadingItem/Readers/Reader"/>
<xsl:apply-templates select="AYFM/StudentItem/Students/Student"/>
</xsl:for-each>
</body>
The start of the template is:
<xsl:template match="Student | Reader">
<xsl:if test="self::Student">
<hr/>
</xsl:if>
<div>
<xsl:attribute name="id">
<xsl:text>containter-student-slip</xsl:text>
<xsl:variable name="pos" select="position()"/>
<xsl:value-of select="$pos"/>
</xsl:attribute>
I want the id to simply be a numerical value, starting at 1 and up.
I realise it is no good using position() because it is relative to both matching element types.
Using XSLT1 how can I simply assign an incrementing value beginning with 1?
If you want to use position(), you could probably* just change:
<xsl:for-each select="MeetingWorkBook/Meeting">
<xsl:apply-templates select="TFGW/BibleReadingItem/Readers/Reader"/>
<xsl:apply-templates select="AYFM/StudentItem/Students/Student"/>
</xsl:for-each>
to:
<xsl:for-each select="MeetingWorkBook/Meeting">
<xsl:apply-templates select="TFGW/BibleReadingItem/Readers/Reader | AYFM/StudentItem/Students/Student"/>
</xsl:for-each>
Alternatively*, it may be possible to use xsl:number.
(*) Of course, without a reproducible example these are merely guesses.

How to generate a cover image picture with asciidoctor-fopub

I'm trying to customize our developer guide pdf to have a decent looking first page. I'm totally fine with doing everything in photoshop as an image since asciidoc seems to be pretty limited in abilities here.
Unfortunately despite looking everywhere I can't seem to find a single way to customize the cover sheet or replace it with an image that works for asciidoctor-fopub.
The annoying thing is that this use case is specifically mentioned in the readme of the project https://github.com/asciidoctor/asciidoctor-fopub (under Custom XSL templates) but there is no mention on how to actually do it other than "its in the XSL". As someone who isn't familiar with docbook I have no idea where to begin.
asciidoctor-pdf has a tag for including an image but it barfs on our document and generates garbage (the ToC is painted over everything).
This can be done using division.xsl file under build/fopub/docbook/fo/
search for "Placeholder templates"
and paste below code there, you will be able to see cover image in your generated fopub
<!-- Placeholder templates -->
<xsl:template name="front.cover">
<xsl:call-template name="page.sequence">
<xsl:with-param name="master-reference">titlepage-cover-image</xsl:with-param>
<xsl:with-param name="content">
<fo:block text-align="center">
<fo:external-graphic src="url(path/to/cover.png)" content-height="250mm" content-width="176mm"/>
</fo:block>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
Change height and width as per your book requirement.
Once that is done you need to provide margin for image on the cover page to be clear. Add below in pagesetup.xsl
<fo:simple-page-master master-name="titlepage-cover-image"
page-width="{$page.width}"
page-height="{$page.height}"
margin-top="{$title.page.margin.top}"
margin-bottom="{$page.margin.bottom}">
<xsl:attribute name="margin-{$direction.align.start}">
<xsl:value-of select="$page.margin.inner"/>
<xsl:if test="$fop.extensions != 0">
<xsl:value-of select="concat(' - (',$title.margin.left,')')"/>
</xsl:if>
</xsl:attribute>
<xsl:attribute name="margin-{$direction.align.end}">
<xsl:value-of select="$page.margin.outer"/>
</xsl:attribute>
<xsl:if test="$axf.extensions != 0">
<xsl:call-template name="axf-page-master-properties">
<xsl:with-param name="page.master">titlepage-cover-image</xsl:with-param>
</xsl:call-template>
</xsl:if>
<fo:region-body margin-bottom="{$body.margin.bottom}"
margin-top="{$title.body.margin.top}"
column-gap="{$column.gap.titlepage}"
column-count="{$column.count.titlepage}">
<xsl:attribute name="margin-{$direction.align.start}">
<xsl:value-of select="$body.margin.inner"/>
</xsl:attribute>
<xsl:attribute name="margin-{$direction.align.end}">
<xsl:value-of select="$body.margin.outer"/>
</xsl:attribute>
</fo:region-body>
<fo:region-before region-name="xsl-region-before-first"
extent="{$region.before.extent}"
precedence="{$region.before.precedence}"
display-align="before"/>
<fo:region-after region-name="xsl-region-after-first"
extent="{$region.after.extent}"
precedence="{$region.after.precedence}"
display-align="after"/>
<xsl:call-template name="region.inner">
<xsl:with-param name="sequence">first</xsl:with-param>
<xsl:with-param name="pageclass">titlepage</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="region.outer">
<xsl:with-param name="sequence">first</xsl:with-param>
<xsl:with-param name="pageclass">titlepage</xsl:with-param>
</xsl:call-template>
</fo:simple-page-master>
Then add variables in fo-pdf.xsl to zeroed out your body top margin.
HTH.

XSLT1 error when get first occurence of a tag

This question (and my problem) is similar to XSLT1 Get the first occurence of a specific tag... But I have an "Undefined variable" error.
I have a XML with refpos attribute, that can be make by this first XSLT,
<xsl:template match="p[#class='ref']">
<xsl:copy>
<xsl:attribute name="refpos">
<xsl:value-of select="position()"/>
</xsl:attribute>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" name="idt">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
Then, I a main (second) XSLT I have,
<xsl:variable name="firstRef">
<xsl:value-of select="(//p[#class='ref'])[1]/#refpos"/>
</xsl:variable>
<xsl:template match="p[#refpos=$firstRef]">
<title><xsl:apply-templates /></title>
</xsl:template>
<xsl:template match="#*|node()" name="idt">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
But here this XSLT not works!!
PS: I also believe that XSLT1 allow us to do everything in one step.
XSLT 1.0 does not allow variable references in template match expressions (XSLT 2.0 does). You'll have to move the check from the predicate inside the template:
<xsl:template match="p">
<xsl:choose>
<xsl:when test="#refpos=$firstRef">
<title><xsl:apply-templates /></title>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="idt" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

how to display content that appears once outside the repeating nodes in the expression of every node in xslt

I've got data in XML that has some header information then a series of items. I'm using XSLT to translate that into a different format, also with a header area and a series of items.
However in the post translation result, I want one piece of data only found in the header to be included in every instance of the items, even though it will simply be repeating the same value. (this value may change so I cannot hard code it)
Sample data (significantly simplified)
<rss>
<channel>
<title>Playlist One</title>
<items>
<item>
<title>Video One</title>
</item>
<item>
<title>Video Two</title>
</item>
<item>
<title>Video Three</title>
</item>
</items>
</channel>
</rss>
My desired result is something like this:
playlist_header_title=Playlist One
playlist_title=Playlist One
video_title=Video One
playlist_title=Playlist One
video_title=Video Two
playlist_title=Playlist One
video_title=Video Three
My XSLT is very complicated (and unfortunately I inherited it from someone else so I'm not sure what everything does, I've self-taught myself online but am in a bit over my head)
Roughly the key pieces look like this:
<xsl:template name="rss" match="/">
<xsl:variable name="playlist_title">
<xsl:value-of select="string(/rss/channel/title)"/>
</xsl:variable>
<xsl:for-each select="rss">
<xsl:apply-templates name="item" select="channel/items/item"/>
</xsl:for-each>
</xsl:template>
Then there's a massive template called "item" that I won't include here, but basically it outputs all the item data as desired, I just can't figure out how to access the "playlist_title".
When I try to call (from inside the template "item")
<xsl:value-of select="string($playlist_title)"/>
it returns a blank. I assume this is because that variable was created outside the for-each loop and so it not available. (it will display the data correctly when I output it before the for-each loop in the result's version of the header, but that doesn't get me far enough)
I've tried using with-param in the apply-templates, and also tried changing it to call-template inside another loop also using with-param, but they also display a blank.
I've also tried sending in a string rather than pulling the playlist_title from the XML just to confirm I am able to pass any value into the template, but they too come out blank.
For instance:
<xsl:for-each select="channel/items/item">
<xsl:call-template name="item">
<xsl:with-param name="playlist_title">blah</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
<xsl:template name="item">
<xsl:param name="playlist_title" />
playlist_title=<xsl:value-of select="string($playlist_title)"/>
video_title=...
...
</xsl:template>
This did not return the value "blah" but just a blank. (my hope was to then replace "blah" with the playlist_title value pulled from the XML, but none of it is getting through)
I'm stumped! Thanks for any help.
In your first example, attempting to reference the $playlist_title inside of your template for item is failing because the variable does not exist inside of that template. It was created inside of the template match for rss and is locally scoped.
If you want to be able to use the $playlist_title in other templates, you need to declare it outside of the rss template in the "global scope".
For instance:
<xsl:variable name="playlist_title">
<xsl:value-of select="string(/rss/channel/title)"/>
</xsl:variable>
<xsl:template name="rss" match="/">
<xsl:for-each select="rss">
<xsl:apply-templates name="item" select="channel/items/item"/>
</xsl:for-each>
</xsl:template>
As for your second attempt, referencing the xsl:param playlist_title, it worked for me. Double-check to ensure that you don't have a type-o or other difference between the name of the parameter that was declared and the name of the parameter that you are referencing.
You might try something like this to achieve your desired output, using an xsl:variable:
<?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" indent="yes"/>
<xsl:variable name="playlist_title" select="/rss/channel/title" />
<xsl:template match="/">
<xsl:apply-templates select="rss/channel"/>
</xsl:template>
<xsl:template match="channel">
<xsl:text>playlist_header_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="items/item"/>
</xsl:template>
<xsl:template match="item">
<xsl:text>playlist_title=</xsl:text>
<xsl:value-of select="$playlist_title"/>
<xsl:text>
</xsl:text>
<xsl:text>video_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Or you can eliminate the xsl:variable and use an xsl:param:
<?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" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="rss/channel"/>
</xsl:template>
<xsl:template match="channel">
<xsl:text>playlist_header_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="items/item">
<xsl:with-param name="playlist_title" select="title"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:param name="playlist_title" />
<xsl:text>playlist_title=</xsl:text>
<xsl:value-of select="$playlist_title"/>
<xsl:text>
</xsl:text>
<xsl:text>video_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

Update attribute in XSL for-each

At work I've been given the fun task of generating PDFs with XSL. The XML structure I'm working with is similar to
<records>
<topLevel>
<topLevelID></topLevelID>
<secondLevel>
<secondLevelID></secondLevelID>
<thirdLevel>
</thirdLevel>
<thirdLevel>
</thirdLevel>
</secondLevel>
</topLevel>
<topLevel>
<topLevelID></topLevelID>
<secondLevel>
<secondLevelID></secondLevelID>
<thirdLevel>
</thirdLevel>
<thirdLevel>
</thirdLevel>
</secondLevel>
</topLevel>
</records>
I would try to give a more meaningful example of the XML, but I don't feel like approaching any legal boundaries that may exist. With that XML structure, I have to output a block of text in the PDF for every thirdLevel node. The XSL I have so far is like
<xsl:for-each select ="topLevel">
<xsl:variable name="topID" select="topLevelID"/>
<xsl:for-each select ="secondLevel">
<xsl:variable name="secondID" select="secondLevelID"/>
<xsl:for-each select="thirdLevel">
<fo:block-container position="absolute" height="12.8pt" width="220.8pt" left="160pt" display-align="auto">
<xsl:attribute name="top">
<xsl:value-of select="concat(193 + [whatshouldgohere]), 'pt')"/>
</xsl:attribute>
<fo:block font-size="7pt">
<xsl:call-template name="insertThirdLevel"/>
</fo:block>
</fo:block-container>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
Basically, I need to add some value to the top attribute to make the text for each thirdLevel node appear on its own line. I've tried using combinations of adding/multiplying by the ID (starts at 1 and increases by 1 for each set) and position(), but I can't seem to get it right.
I think you should really look into <xsl:apply-templates>, it can save you a lot of typing.
Simplified version:
<xsl:variable name="line-height" select="10" />
<xsl:template match="/records">
<xsl:apply-templates select="//thirdLevel" />
</xsl:template>
<xsl:template match="thirdLevel">
<xsl:variable name="top" select="193 + position() * $line-height" />
<fo:block-container top="{concat($top , 'pt')}">
<fo:block font-size="7pt">
<xsl:call-template name="insertThirdLevel"/>
</fo:block>
</fo:block-container>
</xsl:template>
<xsl:template name="insertThirdLevel">
Third Level!
</xsl:template>
Simplified output ("fo" namespace excluded):
<fo:block-container top="203pt">
<fo:block font-size="7pt">
Third Level!
</fo:block>
</fo:block-container>
<fo:block-container top="213pt">
<fo:block font-size="7pt">
Third Level!
</fo:block>
</fo:block-container>
<fo:block-container top="223pt">
<fo:block font-size="7pt">
Third Level!
</fo:block>
</fo:block-container>
<fo:block-container top="233pt">
<fo:block font-size="7pt">
Third Level!
</fo:block>
</fo:block-container>