Image resizing to fit on PDF page using Saxon HE Apache FOP XSL-FO XSLT 2.0 - xslt-2.0

We are transforming XML to PDF using Saxon HE/ Apache FOP (XSLT 2.0). I would like to resize some of the images down to fit on the page. I understand to use the content-height and content-width "scale-down-to-fit" or "scale-to-fit" attributes but the height and width of the image is not available in the XML.
I need a way to access the image file dimensions in the XSLT.
Here is how a graphic might be depicted in the XML:
<graphic boardno="abcd.jpg" unitmeasure="in" inschlvl="0" delchlvl="0 />
Previously they were using xalan extensions (for v1.0 XSLT, we are upgrading to v.2)
xmlns:ii="xalan://ImageInfo" and xmlns:file="xalan://FileUtils"
and then in the XSL-FO:
<xsl:variable name="curGraphicUri">
<xsl:value-of select="unparsed-entity-uri(#boardno)" />
</xsl:variable>
<xsl:variable name="imgProp">
<xsl:value-of select="ii:setInputFile(string($curGraphicUri),72)" />
</xsl:variable>
<xsl:variable name="curFunctionStatus">
<xsl:value-of select="substring-after(substring-before($imgProp,';'),'status:')" />
</xsl:variable>
So (I'm thinking) if I knew the equivalent functions in FOP, or another available extension, I should be able to figure out the rest. I have searched these forums and I haven't found a similar question.

With scale-down-to-fit (see https://www.w3.org/TR/xsl11/#content-height), you don't need to know the original size of the graphic because the XSL-FO formatter will know that anyway. If the full-size graphic will fit in the available space, then it will be rendered at 100% scale, otherwise the graphic will be reduced until it does fit within the available height/width.
If you were using a formatter that also supports allowed-height-scale (see https://www.w3.org/TR/xsl11/#allowed-height-scale) and allowed-width-scale, then you could also control the scale factors that are used when the image is scaled down to fit. This can be useful, for example, to avoid Moiré patterns (see https://en.wikipedia.org/wiki/Moir%C3%A9_pattern) or to keep a consistent scale (or set of scales) between related graphics (e.g., a selection of screenshots of the menus of an application).

Section 3.1 of the EXPath Binary module gives you an example of how to do exactly this:
http://expath.org/spec/binary
Saxon (PE and higher) implements this library. (For Saxon-HE, however, you're out of luck).

Related

XSLT pipeline : Error XPDY0002 - The context item for axis step fn:root(...)/element() is absent

Please, I need some help dealing with saxon api :)
I create a pipeline with 2 XsltTransform of the same xslt and when i run transform i get this error :
2019-01-24 11:32:15,673 [pool-2-thread-1] INFO e.s.e.x.XsltListener - file
2019-01-24 11:32:15,674 [pool-2-thread-1] INFO e.s.e.x.XsltListener - Error
XPDY0002 while evaluating xsl:message content: The context item for axis
step fn:root(...)/element() is absent
here is my xslt :
<xsl:stylesheet exclude-result-prefixes="#all" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="supp" as="xs:string" select="root()/*/name()"/>
<xsl:template match="/">
<xsl:message select="$supp"/>
<file/>
</xsl:template>
the first XsltTransform work fine but It seems that i have no context node during the second XstTransform running.
I use :
transformer1.setSource(source) : source is a SAXSource
transformer1.setDestination(transformr2)
transformr2.setDestination(serialiser)
According to documentation (XsltTransform.setInitialContextNode):
This value is ignored in the case where the XsltTransformer is used as the Destination of another process. In that case the initial context node will always be the document node of the document that is being streamed to this destination.
Thanks for your Help
In general in XSLT 3 you need to distinguish between the initial match selection https://www.w3.org/TR/xslt-30/#dt-initial-match-selection which is used to decide which template to apply first and the global context item https://www.w3.org/TR/xslt-30/#dt-global-context-item that is used to evaluate global parameters and variables. I think you seem to expect that in your second stylesheet the result of your first acts as both but it seems, at least in your setup, Saxon does not assume that but only sets your initial match selection to the result of the first stylesheet. So try moving the <xsl:variable name="supp" as="xs:string" select="root()/*/name()"/> into the template e.g.
<xsl:template match="/">
<xsl:variable name="supp" as="xs:string" select="root()/*/name()"/>
<xsl:message select="$supp"/>
<file/>
</xsl:template>
I am not sure there is another way, at least in the case of chaining two streaming transformations you can't the second stylesheet expect to have access to the whole result tree of the first to be used to evaluate global parameters or variables.

Sending multiple emails from within Orbeon Forms

On Orbeon Forms 4.8, I've rebuilt email-form.xpl so that it constructs different emails depending on various conditions in the form. That works nicely, and it's even embedding form information in the emails it creates. I've got to the point where I have created a nicely structured set of structures, as expected by the Orbeon Email processor. They are all wrapped up in a tag, so I have something like ....
When it comes to actually calling the email processor, I need to work through that XML structure calling the email processor for one message at a time. The code below is what I have to do this and I can see from the debug that it is receiving my messages correctly, but once I check inside the processor all of the XML tags have been stripped away, and the email processor won't accept the input (which I know would only send the first message if it worked at all) because it says it is an incomplete content model.
<p:processor name="oxf:pipeline">
<p:input
name="config"
href="#messages"
transform="oxf:unsafe-xslt"
debug="LOOPING THROUGH EMAIL MESSAGES - MESSAGES">
<p:config xsl:version="2.0">
<p:param type="input" name="messages"/>
<xsl:message>
XXXXX
<xsl:value-of select="messages/message"/>
XXXXX
</xsl:message>
<xsl:for-each select="/*/message">
<p:processor name="oxf:email">
<p:input name="data">
<message>
<xsl:value-of select="messages/message"/>
</message>
</p:input>
</p:processor>
</xsl:for-each>
</p:config>
</p:input>
</p:processor>
Clearly I did something wrong, but I don't see what it was.
To print the XML via XSLT, use <xsl:copy-of> instead of <xsl:value-of>. The latter always takes the string value of the XML, which only looks at the text nodes. The former makes a copy of the XML tree.
The email processor can only send one email at a time. So What's needed is iterating over the <message> elements you have created via XSLT. The iteration must be done in XPL, so you must use <p:for-each>.

xslt how to read the document-node()

I have a xml file in which one of the element has the CDATA as the value. I put the CDATA value into a variable which I can see is value type of document-node(1) when i debug my code from oXygen. How do I iterate the document-node()?
copy can give me a new xml file. but what I need is not a new file. I only need to read certain nodes and generate a report based on the values on those nodes. so I directly copy the CDATA to my variable and thought I can manipulate it.
I tried to use substring to read the variable things but failed.
I tried to use document(variable) to open the variable but Oxygen give me the debug-error of FODC0002:I/O error reported by xml parser processing file.
here the file is my variable which looks like a xml file
I did google search for the error but only got bench of non-closed questions like Oxygen throw I/O error when use document().
Would anybody let me know what's going wrong? or give me a better solution?
I also tried parse-xml() but I got the following error from Saxon:
F[Saxon-EE9.5.1.5] the processing instruction target matching "[xX][mM][lL]" is not allowed
F[Saxon-EE9.5.1.5] FODC0006: First argument to parse-xml() is not a well formed and namespace-well-formed XML document.
my code to use parse-xml is as below:
<xsl:template match="data"
<xsl:for-each select="parse-xml(root/outsideData)//nodeLevel1/nodeLevel2">
Could anyone give me a sample about how to use parse-xml()? I did google search but didn't find useful samples.
Thanks very much!
A piece of my data is like the following:
<root>
<outsideData id="123">
<child1 key="124375438"/>
<![CDATA[ <?xml version=1.0 encoding="UTF-8"?><insideData xmlns:xlink="http://www.w3.org/1999/xlink">
<nodeLevel1>
<nodeLevel21>packing</nodeLevel21>
<nodeLevel22 ref="12343-454/560" xlink:href="URN:X-MN:DD%3FM=B888%26SDC=A%26CH=79% .../>
</nodeLevel1>
]]>
</outsideData>
</root>
I want to get the inside CDATA <nodeLevel22> #ref and #xlink which will get DD-FM-B888-26-79
My variables are:
<xsl:for-each select="/root/outsideData">
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<xsl:variable name="Data">
<xsl:value-of
select="normalize-space(substring-after($insideData,'?>'))"
disable-output-escaping="yes"/>
</xsl:variable>
</xsl:foreach>
From the debug I can see that the variable insideData and Data are both value type of document-node(1)
Martin's solution works for me very well :)
But I'm still wondering why the following doesn't work:
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<ref>
<xsl:value-of select="substring-before(substring-after($insideData, '<nodeLevel22 ref'),>/>')"/>
</ref>
Here I got empty <ref/>
If you do <xsl:variable name="varName"><xsl:value-of select="..."/><xsl:variable> then you are creating a temporary document fragment that contains a single text with the string contents of the item(s) selected in the value-of. That does not make sense in most cases, doing <xsl:variable name="varName" select="..."/> is usually sufficient.
As for parsing the contents of the outsideData element with parse-xml, there is indeed not only the escaped XML document inside that element but white space as well, thus if you try to parse the contents as XML you get that error as white space before the XML declaration is not allowed. The whole approach of stuffing the XML into a CDATA section with an element with mixed contents is flawed in my view, if you want to store escaped XML into a CDATA then you should make sure that you use a single element that contains nothing but the CDATA section which then only contains the XML markup with no leading white space.
If you can't change the creation of the input data then you will need to make sure you pass in only that part of the string contents of the element to parse-xml that is a well-formed XML document, so you need some way to strip the white space before the XML declaration doing e.g.
<xsl:for-each select="/root/outsideData">
<xsl:variable name="xml-string" select="replace(., '^\s+', '')"/>
<xsl:variable name="xml-doc" select="parse-xml($xml-string)"/>
<!-- now output data e.g. -->
<xsl:value-of select="$xml-doc//nodeLevel1/nodeLevel22/#ref"/>
...
</xsl:for-each>
Untested but should show the right direction as far as trying to use parse-xml.

XSLT 2.0: filter duplicates

I have this line in a template:
<xsl:for-each select="//img/#src[not(# = preceding::#)]">
to generate a list of filenames of pictures :
<img src="mypic1.jpg"/>
Now I need to filter duplicate pictures from the list of pictures.
<xsl:for-each select="//img[not(node() = preceding::img)]">
does not work because it works on the whole image node.
How can I change the line to work on src attributes?
So the question is about the last part of the line in question.
You could just use distinct-values(//img/#src), e.g.
<xsl:for-each select="distinct-values(//img/#src)">
<li><xsl:value-of select="."/></li>
<xsl:for-each>
Alternatively you can use for-each-group
<xsl:for-each-group select="//img" group-by="#src">
<li><xsl:value-of select="#src"/></li>
<xsl:for-each-group>

Umbraco 4 Document Type not showing on page

I am a newbie with Umbraco so please excuse if I am missing something obvious. I have trawled the internet, but I can't find a solution to my problem.
I am mainly using the Umbraco.TV videos to guide me through this, and in particularly, this one: http://umbraco.com/help-and-support/video-tutorials/umbraco-fundamentals/datatypes/using-content-and-media-pickers/TVPlayer.
The others videos and tutorials successfully got me through setting up a responsive nav and sub-nav, but on this issue, I'm stuck.
I am building a site which requires multiple CTAs. The text for these need to be entered by persons who have no coding experience so I've set up Document Types containing RichText Editor fields for them to enter the required text and images.
Tab: Generic Properties
H2 (h2), Type: Richtext editor
Article Description (articleDescription), Type: Richtext editor
Article Feature Image (articleFeatureImage), Type: Media Picker
Label (label), Type: Label
In the Template I have then inserted an Umbraco page field which references the relevant Document Type:
<div class="rel-art">
<umbraco:Item field="relatedContent1" runat="server" />
</div>
However, when I view the page in a browser, all I'm getting on the page is the Document Type ID:
<div class="rel-art"> 1202 </div>
I have tried setting it up and inserting it as a xslt:
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<xsl:template match="/">
<xsl:value-of select="umbraco.library:GetXmlNodeById($currentPage/data [#alias = 'relatedContent1'])/#nodeName"/>
</xsl:template>
</xsl:stylesheet>
My "Visualise XSLT" button isn't working though so I don't know what is would say at this point. I don't get a modal window. (I'm working remotely onto my client's system so that may have an impact.)
Now, if I insert the accompanying Macro into my template:
<div class="rel-art">
<umbraco:Macro Alias="RelatedContent1" runat="server"></umbraco:Macro>
</div>
that results in not even the ID showing when I view the page:
<div class="rel-art"> </div>
I have tried with and without #nodeName, but no difference.
What is happening? What am I doing wrong? The only thing I can think of is that I had some trouble getting another Document Type to show up on the page until I changed its only Property Type from RichText Editor to Textstring. But I tried that with this one as well, and that didn't make any difference.
I am using Umbraco v4.9, if that helps.
Any help is gratefully received!
Thanks! :-)
Trine,
It is not very clear, but I noticed that the returned value is ' 1202 ' (having spaces). Could you please check your code with a static value i.e.
<xsl:value-of select="umbraco.library:GetXmlNodeById(1202)/#nodeName"/>
if this works fine, then the problem is your 'relatedContent1'. You can Trim its value and use it in GetXmlNodeById function.
It appears as if you are trying to display an image chosen with a media picker. If this is the case then you should use the GetMedia method:
<xsl:value-of select="umbraco.library:GetMedia($currentPage/data[#alias='relatedContent1'], 'false')/data[#alias='umbracoFile']" />
Check out How to use umbraco.library GetMedia in XSLT by Lee Kelleher for more details and for a more fool proof approach for getting media.

Resources