XSL Second-pass template failing when attribute is selected - xslt

I'm wondering if anybody can shed any light on why a particular XSL template is failing to do what is intended. I am generating XSL-FO using XSL 2.0 (Saxon HE). I want to invert certain font styles (so that italic text within an italic paragraph becomes roman, for instance). (In my actual XSL, I do this as the second part of a two-pass process. The issue occurs regardless, so my example shows a single pass for the purposes of simplicity.)
Sample input:
<?xml version="1.0" encoding="utf-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="skeleton">
<fo:region-body margin="1in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="skeleton">
<fo:flow flow-name="xsl-region-body">
<fo:block><fo:inline font-style="italic">A Title <fo:inline color="black" font-style="italic">With Some Roman Text</fo:inline> in the Middle</fo:inline></fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
Sample XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//fo:inline[parent::*[#font-style=current()/#font-style]]/#font-style">
<xsl:message>Found nested font-style.</xsl:message>
<xsl:attribute name="font-style">normal</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
My desired result is:
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="skeleton">
<fo:region-body margin="1in" />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="skeleton">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<fo:inline font-style="italic">
A Title
<fo:inline color="black" font-style="normal">
With Some Roman Text
</fo:inline>
in the Middle
</fo:inline>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
This is the result I get using this online tester: http://chris.photobooks.com/xml/default.htm
When I run this transform using Saxon the interior fo:inline remains italic and my debug message never appears.
I've found a workaround using for-each (if I do not attempt to select the attribute, but rather select the matching fo:inline, the template is triggered). But I'm curious to know what is wrong with this, in my opinion, much cleaner solution.
Thanks!
(Just to add a little more information, here's the less-ideal template that does get me the result I want, at least so far:
<xsl:template match="//fo:inline[parent::*[#font-style=(current()/#font-style)]]">
<xsl:message>Found nested font-style.</xsl:message>
<xsl:copy>
<xsl:for-each select="#*">
<xsl:message><xsl:value-of select="local-name()"/></xsl:message>
<xsl:choose>
<xsl:when test="local-name()='font-style'">
<xsl:attribute name="font-style">normal</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:copy>

It is possibly something to do with this line...
<xsl:template match="//fo:inline[parent::*[#font-style=current()/#font-style]]/#font-style">
In XSLT 1.0, I get an error...
The current() function may not be used in a match pattern.
I am not sure if the same happens in XSLT 2.0, but try changing the line to this, and see if that makes a difference
<xsl:template match="//fo:inline[#font-style = parent::*/#font-style]/#font-style">

I managed to obtain the result you are looking for changing the XPath expression to
//fo:inline[parent::*[#font-style]]/#font-style
The previous XPath expression matched no nodes in the document, so the template was never called (and so the message was also not printed).

Taking this to its logical extension, you probably want to convert to "normal" any fo:inline that has an odd number of ancestor fo:inline elements with the same font-style as this one (e.g. if you had italic within italic within italic then only the middle one of the three would want to be converted back to normal).
<xsl:template match="fo:inline[
count(ancestor::fo:inline[#font-style = current()/#font-style]) mod 2 = 1]">
This works because XSLT 2.0 defines current() in a pattern as the node that the pattern is being tested against (in this case the fo:inline that is the subject of the outer predicate).

Related

Check if xsl:attribute name is valid for XSL-FO

In my data it's possible that there are one or more processing-instructions which are used to give that specific block of content new attributes or overwrite the values of existing ones.
The way I do this requires it that the name of the PIs are valid xsl attribute names.
The Question: Is it possible to check within the xsl-stylesheet if the name of the PI is an actual valid (=allowed as <xsl:attribute name="*thisname*"> in XSL-FO) attribute name?
<xsl:if test="./processing-instruction()"> <!-- add condition to test for valid name? -->
<xsl:for-each select="./processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:attribute name="{$pi_name}"><xsl:value-of select="." /></xsl:attribute>
</xsl:for-each>
</xsl:if>
EDIT:
Regarding this problem: Check if xsl:attribute name is valid for XSL-FO
That's the code I use derived from Tony's solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="handle_block_attribute">
<xsl:variable name="attributes" select="document('PATH\attributelist.xml')//attributelist/attribute"/>
<xsl:if test=".//processing-instruction()">
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:choose>
<xsl:when test="$pi_name = $attributes">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<fo:inline color="red">Invalid attribute-name in PI: <xsl:value-of select="$pi_name" /></fo:inline><fo:block />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The template is called in this way:
<xsl:template match="para">
<fo:block xsl:use-attribute-sets="para.standard">
<xsl:call-template name="handle_block_attribute" />
<xsl:apply-templates/>
</fo:block>
</xsl:template>
And that's the data:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
<sect class="hierarchic" type="chapter">
<para class="heading" style="Chapter">
<inline style="HeadingText">HD</inline>
</para>
<para style="Standard">Lorem ipsum dolor sit amet consectetuer eleifend consequat pede Aenean est. <?font-size 13pt?><?fotn-family Arial?><?color green?><?fline-height 3pt?></para>
<para style="Standard">Consequat semper tortor id convallis leo Phasellus eget non sagittis neque.</para>
</sect>
</book>
EDIT2:
Well, maybe there is a more elegant way, but it works: I'm going with two for-each-loops, one for the correct PIs and after that another one for the flawed ones including error-output.
<xsl:if test=".//processing-instruction()">
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:if test="$pi_name = $attributes">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:if>
</xsl:for-each>
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:if test="not($pi_name = $attributes)">
<fo:inline color="red">Invalid attribute name in PI: <xsl:value-of select="$pi_name" /></fo:inline><fo:block />
</xsl:if>
</xsl:for-each>
</xsl:if>
You've made it harder than necessary for yourself by using XSLT 1.0 instead of either XSLT 2.0 or XSLT 3.0 simply because it's harder to construct a list of property names to compare against. With the later versions, you could just make a sequence of strings. With XSLT 1.0, the simplest way (that I can remember) is to put each property name as the value of a separate element and then compare the prospective property name against the selected set of elements. The magic of XPath's = is that it is true if any value of one side matches any value on the other side.
You can extract the definitive list of XSL-FO property names by processing the XML source for the XSL specification: http://www.w3.org/TR/2006/REC-xsl11-20061205/xslspec.xml
With this stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:local="uuid:503f6e96-fda1-4464-98b6-a60dbecc5946"
exclude-result-prefixes="local">
<properties xmlns="uuid:503f6e96-fda1-4464-98b6-a60dbecc5946">
<property>font-size</property>
<property>font-weight</property>
</properties>
<xsl:variable name="properties" select="document('')/*/local:properties/local:property"/>
<xsl:template match="fo:*">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:for-each select="processing-instruction()">
<xsl:variable name="pi_name" select="local-name()" />
<xsl:choose>
<xsl:when test="$pi_name = $properties">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:message>Unrecognised property: <xsl:value-of select="$pi_name"
/></xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
this document:
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:block><?font-size 16pt?><?font-weight bold?></fo:block>
<fo:block><?fotn-size 16pt?><?bogus bold?></fo:block>
</fo:root>
generates this:
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:block font-size="16pt" font-weight="bold"/>
<fo:block/>
</fo:root>
plus messages about 'fotn-size' and 'bogus'.
I put the property names in the stylesheet to make it easier to test. You could put the XML in an external document and not have to fuss with the local namespace. The namespace was necessary because the only non-XSLT elements allowed at the top level of an XSLT stylesheet have to be in a non-XSLT namespace.
As Martin Honnen has commented, PI target and attributes names must conform the Name production of XML specification. Thus, the only case when this might conflict is when a PI target could be interpreted as a QName and the prefix does not match an in scope namespace binding.
Because of that, : is not allowed in PI target.
Here is the errors given by different XSLT processor when parsing the input source:
<?a:b 7pt?>
<root/>
Saxon:
org.xml.sax.SAXParseException; systemId: urn:from-string; lineNumber:
1; columnNumber: 6; A colon is not allowed in the name 'a:b' when
namespaces are enabled.
MSXML:
Input parsing error: Entity names, PI targets, notation names and
attribute values declared to be of types ID, IDREF(S), ENTITY(IES) or
NOTATION cannot contain any colons. , 1:7, <?a:b 7pt?>
Edit: about <?fotn-size 7pt?>, this stylesheet following your example
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<root>
<xsl:if test="./processing-instruction()">
<xsl:for-each select="./processing-instruction()">
<xsl:variable name="pi_name">
<xsl:value-of select="local-name()" />
</xsl:variable>
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:for-each>
</xsl:if>
</root>
</xsl:template>
</xsl:stylesheet>
With this input:
<?fotn-size 7pt?>
<root/>
Output:
<root fotn-size="7pt"/>

hyphenation character in xslt attribute (xsl-fo)

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.)

use xslt to add newlines after attributes

I am trying to transform XML that looks like:
<item attr1="value1" attr2="value2"><nestedItem attr1="value1" attr="value2"/></item>
To XML that looks like:
<item
attr1="value1"
attr2="value2">
<nestedItem
attr1="value1"
attr="value2"/>
</item>
I am working with a stylesheet:
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template name="newline">
<xsl:text disable-output-escaping="yes">
</xsl:text>
</xsl:param>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="text()|#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
I've tried calling my newline template from a few different places, but can't get newlines inserted between my attributes.
Thanks!
There is no support for the wanted serialization in XSLT 1.0 and 2.0 (and, to my knowledge, also in the forthcoming XSLT 3.0).
In case your XSLT processor allows serialization via a user-provided XmlWriter class, then you can implement such serialization.
For example, when using one or more specific overloads of the .NET XslCompiledTransform.Transform() method, one may pass as one of the arguments to the method, an instance of XmlWriter. Pass an instance of your own class that derives from XmlWriter.
The following template may not pretty and may not be best practice but it works for me. Give it a try. It will not output xmlns 'attributes' if there are any on the element.
<xsl:template match="*">
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:value-of select="name()"/><xsl:text>
</xsl:text>
<xsl:for-each select="#*">
<xsl:text> </xsl:text><xsl:value-of select="concat(name(),'=' ,'"', . ,'"')" /><xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text disable-output-escaping="yes">>
</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes"></</xsl:text><xsl:value-of select="name()"/><xsl:text disable-output-escaping="yes">>
</xsl:text>
</xsl:template>
When you use the Saxon serializer with indentation you can get output close to what you are looking for, but it might not be exactly what you want. If you're really fussy about the format then you will have to write your own serializer or adapt an existing one by tweaking the code. The usual philosophy in XML circles is that you shouldn't really care about distinctions that will be ignored once the data is parsed, and that includes things like the choice of quote character, the order of attributes, and the whitespace that separates attributes.

XSL FO inline alignment on an existing sort/conditional XSL

I need to get right-align and left-align working in the same line. Looking over similar responses, I found the below recommendation,
<fo:block text-align-last="justify">
LEFT TEXT (want this to be the Contacts element from the below)
<fo:leader leader-pattern="space" />
RIGHT TEXT (want this to be the Address1 element from the below)
</fo:block>
But when I try to apply it to my existing XSL code (see below) I can’t make it work – I don’t know enough about how to edit it to accommodate/merge both the sort/conditionals and the FO. Can someone help me get this right?
Exsiting/working code:
<?xml version="1.0"?><!-- DWXMLSource="XML - Builder Members.xml" -->
<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp " ">]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<memberdata>
<xsl:for-each select="memberdata/memberinfo">
<xsl:sort select="SortKey"/>
<memberdata>
<xsl:if test="Contacts[.!='']">
<Contacts><xsl:value-of select="Contacts" /></Contacts>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:if test="Address1[.!='']">
<Address1><xsl:value-of select="Address1" /></Address1>
<xsl:text>
</xsl:text>
</xsl:if>
</memberdata>
</xsl:for-each>
</memberdata>
</xsl:template>
</xsl:stylesheet>
Independently of the actual answer to your question (which is impossible to give in the current form the question is in), I'd like to suggest a few improvements to your general approach to XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="memberdata">
<xsl:copy>
<xsl:apply-templates select="memberinfo">
<xsl:sort select="SortKey" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="memberinfo">
<memberdata>
<xsl:apply-templates select="Contacts" />
<xsl:apply-templates select="Address1" />
</memberdata>
</xsl:template>
<xsl:template match="Contacts|Address1">
<xsl:if test="normalize-space() != ''">
<xsl:copy-of select="." />
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Avoid <xsl:for-each>, use distinct templates and <xsl:apply-templates> instead. This results in cleaner, less duplicated and less deeply nested code. It also could result in more efficient processing of your stylesheet, as XSLT processors are optimized for template matching and can parallelize template execution.
Note that you can use the same template for multiple elements, see third template above.
Avoid adding line-breaks via such a construct: <xsl:text>
</xsl:text>. Doing this destroys source code readability and is prone to errors as soon as the source code is formatted (I've already done this in your question to be able to indent your code properly in the first place). Use character references
instead to separate source code layout and output layout.
Note that you can use <xsl:copy-of> to make a copy of an element, no need to do <foo><xsl:value-of select="foo" /></foo>.
Taking your request at face value, this seems to be what you're asking for, which merges the sort, the conditionals and the FO.
<?xml version="1.0"?><!-- DWXMLSource="XML - Builder Members.xml" -->
<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp " ">]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<memberdata>
<xsl:for-each select="memberdata/memberinfo">
<xsl:sort select="SortKey"/>
<memberdata>
<fo:block text-align-last="justify">
<xsl:if test="Contacts[.!='']">
<Contacts><xsl:value-of select="Contacts" /></Contacts>
<xsl:text>
</xsl:text>
</xsl:if>
<fo:leader leader-pattern="space" />
<xsl:if test="Address1[.!='']">
<Address1><xsl:value-of select="Address1" /></Address1>
<xsl:text>
</xsl:text>
</xsl:if>
</fo:block>
</memberdata>
</xsl:for-each>
</memberdata>
</xsl:template>
</xsl:stylesheet>
However it seems unlikely that you really want to mix <fo:*> elements and other elements (<memberdata>) in your output... unless you plan to process them later to produce a full FO document. So the above may not be quite the solution you need.
(See also #Tomalak's good points about how to improve the XSLT. I would differ with him only on the question of for-each vs. apply-templates... it really depends on several factors and what your priorities are.)

Where do I put an XSL function in an XSL document?

I have an XSL style sheet for which I need to add some custom string manipulation using an xsl:function. But I am having trouble trying to work out where to put the function in my document.
My XSL simplified looks like this,
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:my="myFunctions" xmlns:d7p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="Master.xslt"/>
<xsl:template match="/">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- starts actual layout -->
<fo:page-sequence master-reference="first">
<fo:flow flow-name="xsl-region-body">
<!-- this defines a title level 1-->
<fo:block xsl:use-attribute-sets="heading">
HelloWorld
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
And I want to put in a simple function, say,
<xsl:function name="my:helloWorld">
<xsl:text>Hello World!</xsl:text>
</xsl:function>
But I cannot work out where to put the function, when I put it under the node I get an error saying 'xsl:function' cannot be a child of the 'xsl:stylesheet' element., and if I put it under the node I get a similar error.
Where should I put the function? Idealy I would like to put my functions in an external file and import them into my xsl files.
There is no xsl:function in XSL version 1.0. You have to create a named template
<xsl:template name="helloWorld">
<xsl:text>Hello World!</xsl:text>
</xsl:template>
(...)
<xsl:template match="something">
<xsl:call-template name="helloWorld"/>
</xsl:template>
You can upgrade the stylesheet version to 2.0
Then in stylesheet declaration specify as
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://www.**.com">
** Your choice you can specify anything as your wish
then below this specify your function
<xsl:function name="func:helloWorld">
<xsl:text>Hello World!</xsl:text>
</xsl:function>
Then in template you can use it as
<xsl:template match="/">
<xsl:value-of select="func:helloWorld"/>
</xsl:template>