I'm working with some XML where the nodes have lots of attributes, and I'm creating an FO PDF file (report) using XSL. I'm trying to create a template that takes a specific attribute on the current node, and create an fo:block that will have some basic formatting.
Here is a template that creates a big list of all the attributes and values on one node.
XSL:
<xsl:template name="createAttributeAndValueList">
<xsl:param name="node" select="." />
<xsl:for-each select="$node/#*">
<fo:block>
<xsl:value-of select="concat(name(),': ')"/>
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:template>
However, there are times when I only want to get one or two of these attributes out, instead of all of them off the node. I'm guessing this is something that would be really obvious, but that I haven't figured out yet due to my inexperience.
I am trying to do the same formatting, but I just can't seem to get the syntax right to be able to pass in my value to the parameter, and get what I want. Here's what I have:
XSL:
<xsl:template name="createAttributeValuePair">
<xsl:param name="attribute" select="." />
<xsl:for-each select="#*">
<fo:block>
<xsl:value-of select="concat(name(),': ')"/>
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:template>
And here's how I'm trying to call it:
XSL:
<fo:block font-weight="normal" margin-left="6pt">
<xsl:call-template name="createAttributeValuePair">
<xsl:with-param name="attribute"
select="/device:DevicePatientEncounter/device:Encounter/
device:Followup/#UnderlyingRhythm"/>
</xsl:call-template>
</fo:block>
where my XML looks like this:
<DevicePatientEncounter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
GeneratedTime="2011-12-14T13:36:05"
EncounterDate="2011-11-15T11:04:54"
xmlns="device">
<Encounter>
<Followup UnderlyingRhythm="Sinus bradycardia"
UnderlyingRhythmRateBpm="44"
IsPmDependent="false"
PresentingRhythm="Atrial fibrillation"
BatteryChargeSeconds="5"
AutoCapFrequency="3 Years"
LastCapacitorFormDate="2011-10-25T00:00:00"
BatteryLongevity="0"
BatteryVoltage="11"
BatteryStatus="MOL"/>
</Encounter>
</DevicePatientEncounter>
A few notes:
You're passing an attribute to createAttributeValuePair but you never do anything with the parameter
You're looping over #* in this template, but the template seems designed to output the name and value of a single attribute
In addition, call-template does not change the current node, so it's not really clear what element's attributes are being iterated in the loop
I'm guessing you're looking for something like this:
<xsl:template name="createAttributeValuePair">
<xsl:param name="attribute" select="."/>
<fo:block>
<xsl:value-of select="concat(name($attribute),': ')"/>
<xsl:value-of select="$attribute"/>
</fo:block>
</xsl:template>
Here's a complete demo:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:device="device">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<fo:block font-weight="normal" margin-left="6pt">
<xsl:call-template name="createAttributeValuePair">
<xsl:with-param name="attribute"
select="/device:DevicePatientEncounter/device:Encounter/
device:Followup/#UnderlyingRhythm"/>
</xsl:call-template>
</fo:block>
</xsl:template>
<xsl:template name="createAttributeValuePair">
<xsl:param name="attribute" select="."/>
<fo:block>
<xsl:value-of select="concat(name($attribute),': ')"/>
<xsl:value-of select="$attribute"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>
The following output is produced when given your example XML:
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:device="device"
font-weight="normal"
margin-left="6pt">
<fo:block>UnderlyingRhythm: Sinus bradycardia</fo:block>
</fo:block>
Related
I would like to use node-set() in Antenna House so I can access preceding-siblings in a sorted list. (I'm trying to follow this example: [using preceding-sibling with with xsl:sort)
I'm not sure what the syntax is for declaring the namespace to access node-set(). AH is not giving any errors, but my call to node-set() fails. I've tried:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxsl" version="1.0">
Here is the XML:
<illustratedPartsCatalog>
<figure id="fig1">...</figure>
<catalogSeqNumber assyCode="00" figureNumber="01" indenture="0" item="000" itemVariant="A" subSubSystemCode="0" subSystemCode="0" systemCode="00">
<itemSeqNumber itemSeqNumberValue="000">
<quantityPerNextHigherAssy>XX</quantityPerNextHigherAssy>
<partRef manufacturerCodeValue="00000" partNumberValue="11111-111">
</partRef>
<partSegment>
<itemIdentData>
<descrForPart>VALVE ASSEMBLY</descrForPart></itemIdentData>
</partSegment><applicabilitySegment><usableOnCodeAssy>X</usableOnCodeAssy>
</applicabilitySegment></itemSeqNumber></catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<figure id="fig2">...</figure>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
</illustratedPartsCatalog>
XSLT:
<xsl:variable name="sortedCSN">
<xsl:for-each select="illustratedPartsCatalog/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:template name="SortParts2" >
<xsl:for-each select="msxsl:node-set($sortedCSN)/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:call-template name="catalogSeqNumber-NI">
<xsl:with-param name="figNo" select="concat(#figureNumber,#figureNumberVariant)"/>
<xsl:with-param name="prfigNo" select="concat(preceding-sibling::catalogSeqNumber/#figureNumber,preceding-sibling::catalogSeqNumber/#figureNumberVariant)" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="catalogSeqNumber-NI">
<xsl:param name="figNo"/>
<xsl:param name="prfigNo" />
<fo:table-row keep-together.within-page="always" wrap-option="wrap">
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-transform="uppercase" wrap-option="wrap">
<fo:block wrap-option="wrap">
<xsl:value-of select=" ./itemSeqNumber/partRef/#partNumberValue"/>
</fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-align="start">
<xsl:choose>
<xsl:when test="$figNo">
<fo:block>
<xsl:text> </xsl:text><xsl:value-of select="$figNo"/><xsl:text> </xsl:text> <xsl:value-of select="$prfigNo"/>
</fo:block>
</xsl:when>
<xsl:otherwise>
<fo:block />
</xsl:otherwise>
</xsl:choose>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-align="start">
<fo:block>
<xsl:if test="./itemSeqNumber/partLocationSegment/notIllustrated">
<xsl:text>-</xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="#item">
<xsl:variable name="itemNo">
<xsl:call-template name="suppressZero" >
<xsl:with-param name="pText" select="#item"/>
</xsl:call-template>
</xsl:variable>
<xsl:text> </xsl:text>
<xsl:value-of select="concat($itemNo,#itemVariant)"/>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding">
<fo:block>
<xsl:value-of select="./itemSeqNumber/quantityPerNextHigherAssy"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
I think the default for Antenna House on Windows is to use MSXML so the attempt with xmlns:msxsl="urn:schemas-microsoft-com:xslt" is fine as far as using the node-set extension function.
I think you simply need to change the XSLT to
<xsl:variable name="sortedCSN">
<xsl:for-each select="illustratedPartsCatalog/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:template name="SortParts2" >
<xsl:for-each select="msxsl:node-set($sortedCSN)/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:call-template name="catalogSeqNumber-NI">
<xsl:with-param name="figNo" select="concat(#figureNumber,#figureNumberVariant)"/>
<xsl:with-param name="prfigNo" select="concat(preceding-sibling::catalogSeqNumber/#figureNumber,preceding-sibling::catalogSeqNumber/#figureNumberVariant)" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
to make sense with the input you have shown (as far as you have shown it, can't judge all those xsl:sort attempts without seeing the data to be sorted).
On the other hand, if you get errors about the stylesheet not being correct XSLT or XML, you would better show us a minimal but complete stylesheet allowing us to reproduce the problem.
Please suggest, to generate range of numbers from two values.
I used call-template methods. Please advice. I am using XSLT 2.
XML:
<article>
<range>3-7</range>
</article>
XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//range">
<xsl:variable name="var1" select="substring-before(., '-')"/>
<xsl:variable name="var2" select="substring-after(., '-')"/>
<range>
<xsl:attribute name="ID">
<xsl:call-template name="tmpPageRange">
<xsl:with-param name="stPage" select="$var1"/>
<xsl:with-param name="lstPage" select="$var2"/>
<xsl:with-param name="presentvalue" select="$var1"/>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="."/>
</range>
</xsl:template>
<xsl:template name="tmpPageRange">
<xsl:param name="stPage"/>
<xsl:param name="lstPage"/>
<xsl:param name="presentvalue"/>
<xsl:if test="number($stPage) < number($lstPage)">
<xsl:value-of select="concat($presentvalue, ' ')"/>
<xsl:call-template name="tmpPageRange">
<xsl:with-param name="stPage" select="number($stPage) + 1"/>
<xsl:with-param name="lstPage"/>
<xsl:with-param name="presentvalue" select="$stPage"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Required OutPut:
<range ID="3 4 5 6 7">3-7</range>
You can use the following:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:template match="article/range">
<range>
<xsl:attribute name="ID">
<xsl:for-each select="xs:integer(tokenize(.,'-')[1]) to xs:integer(tokenize(.,'-')[2])">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:value-of select="."/>
</range>
</xsl:template>
</xsl:stylesheet>
Following on from Lingamurthy CS's answer, there are various shortcuts that XSLT 2.0 offers that allow you to shorten this substantially. In fact for your specific requirement you can roll it right the way up into an attribute value template:
<range ID="{xs:integer(tokenize(.,'-')[1]) to xs:integer(tokenize(.,'-')[2])}">
<xsl:value-of select="."/>
</range>
The XSLT 2.0 rule for converting a sequence of atomic values to a string in an AVT is to convert each item to a string individually and then output the resulting sequence separated by spaces. If you wanted a different separator (or no separator at all) then you could use xsl:attribute, which can take a separator attribute to override the default (single space) separator, e.g.
<range>
<xsl:attribute name="ID"
select="xs:integer(tokenize(.,'-')[1]) to xs:integer(tokenize(.,'-')[2])"
separator="," />
<xsl:value-of select="."/>
</range>
would produce <range ID="3,4,5,6,7">3-7</range>
I'm trying to write a recursive named template that will show the path of a given node:
<?xml version="1.0"?>
<testfile>
<section>
<title>My Section</title>
<para>Trying to write a recursive function that will return a basic xpath of a given node; in the case of this node, I would want to return testfile/section/para, I don't need /testfile/section[1]/para[1] or anything like that. The issue I'm having is that in the case of a named template, I don't know how to select a different node and apply it to the named template.</para>
</section>
</testfile>
I'm trying this template :
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- stylesheet to test a named template trying to build an xpath for a given node -->
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:call-template>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="ancestor::*">
<xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name()"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
<!-- how to recursively call template with parent node? -->
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As per the comment, I'm not sure how to apply a node other than the context node to the named template. The other strategy I tried was to send the node to the template as a param, but I don't know how(or if you can) apply an axis to a param, as in
$thisNode../*
etc.
I'm sure it's something simple that I'm missing...thanks.
You can indeed pass in the node as a param to the template....
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />
To apply an axis to it, for example to test for ancestors, you would do this....
<xsl:when test="$node/ancestor::*">
And to pass its parent element to the template when you recursively call it, do this:
<xsl:with-param name="node" select="$node/parent::*" />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:call-template>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="$node/ancestor::*">
<xsl:message><xsl:value-of select="name($node)"/> has a parent</xsl:message>
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name($node)"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
</xsl:with-param>
<xsl:with-param name="node" select="$node/parent::*" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name($node)"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
An alternate approach is to use xsl:apply-templates, but with the mode parameter to keep it separate from other template matches. Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:apply-templates select="." mode="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:apply-templates>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*" mode="getXpath">
<xsl:param name="pathText"/>
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="ancestor::*">
<xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
<xsl:apply-templates select=".." mode="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name()"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You shouldn't have to pass a node as a param if you just do an xsl:for-each.
Here's a modified example of your XSLT. (Notice that the positional predicates are only output in the path if they are needed.)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- stylesheet to test a named template trying to build an xpath for a given node -->
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath"/>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())"/>
<!--Predicate is only output when needed.-->
<xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Output (using the input from the question)
<result>
<element>element name : testfile path : /testfile</element>
<element>element name : section path : /testfile/section</element>
<element>element name : title path : /testfile/section/title</element>
<element>element name : para path : /testfile/section/para</element>
</result>
For what it's worth, I wrote a simple XPath generation template about a decade ago, in part 2 of my "styling stylesheets" article on DeveloperWorks:
Listing 4. Template that generates a Pseudo XPath in XSLT
<xsl:template name="pseudo-xpath-to-current-node">
<!-- Special-case for the root node, which otherwise
wouldn't generate any path at all. A bit of a kluge,
but it's simple and efficient. -->
<xsl:if test="not(parent::node())">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:for-each select="ancestor-or-self::node()">
<xsl:choose>
<xsl:when test="not(parent::node())">
<!-- This clause recognizes the root node, which doesn't need
to be explicitly represented in the XPath. -->
</xsl:when>
<xsl:when test="self::text()">
<xsl:text>/text()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::comment()">
<xsl:text>/comment()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<xsl:text>/processing-instruction()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::*">
<!-- This test for Elements works because the Principal
Node Type of the self:: axis happens to be Element.
-->
<xsl:text>/</xsl:text>
<xsl:value-of select="name(.)"/>
<xsl:text>[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::node()[name()='xmlns' | starts-with(name(),'xmlns:')]">
<!-- This recognizes namespace nodes, though it's a bit
ugly. XSLT 1.0 doesn't seem to have a more elegant
test. XSLT 2.0 is expected to deprecate the whole
concept of namespace nodes, so it may become a moot
point.
NS nodes are unique; a count isn't required. -->
<xsl:text>/namespace::</xsl:text>
<xsl:value-of select="local-name(.)"/>
</xsl:when>
<xsl:otherwise>
<!-- If I've reached this clause, the node must be an
attribute. Attributes are unique; a count is not
required. -->
<xsl:text>/#</xsl:text>
<xsl:value-of select="name(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
That was an XSLT 1.0 solution, structured for clarity. It's probably possible to simplify it, especially if you're using XSLT and XPath 2.0.
As I explained there, this "pseudo-XPath" version ignores the namespace issue, since I didn't need it for that proof-of-concept tool and since it was intended for human-readable messages rather than for execution. It could be corrected to manage namespaces properly by changing it to write out paths that specify node type with a predicate explicitly testing localname and namespace URI. The resulting paths would be bulkier and harder for humans to process. Exercise for the reader, if you're so inclined.
You might also be able to replace the positional index with something more expressive... but knowing what's going to be meaningful is not easy.
Hope that helps. Have fun.
(Oh, almost forgot: I wouldn't be surprised if there are other solutions on the XSLT FAQ site.)
I think you want something like this:
<xsl:variable name="get.path">
<xsl:text> /</xsl:text>
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="get.current.node" select="name(.)"/>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name(.) = $get.current.node]) + 1"/>
<xsl:text>]</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
I need to write an XSLT function that transforms a sequence of nodes into a sequence of strings. What I need to do is to apply a function to all the nodes in the sequence and return a sequence as long as the original one.
This is the input document
<article id="4">
<author ref="#Guy1"/>
<author ref="#Guy2"/>
</article>
This is how the calling site:
<xsl:template match="article">
<xsl:text>Author for </xsl:text>
<xsl:value-of select="#id"/>
<xsl:variable name="names" select="func:author-names(.)"/>
<xsl:value-of select="string-join($names, ' and ')"/>
<xsl:value-of select="count($names)"/>
</xsl:function>
And this is the code of the function:
<xsl:function name="func:authors-names">
<xsl:param name="article"/>
<!-- HELP: this is where I call `func:format-name` on
each `$article/author` element -->
</xsl:function>
What should I use inside func:author-names? I tried using xsl:for-each but the result is a single node, not a sequence.
<xsl:sequence select="$article/author/func:format-name(.)"/> is one way, the other is <xsl:sequence select="for $a in $article/author return func:format-name($a)"/>.
I am not sure you would need the function of course, doing
<xsl:value-of select="author/func:format-name(.)" separator=" and "/>
in the template of article should do.
If only a sequence of #ref values should be generated there is no need for a function or xsl version 2.0.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="article">
<xsl:apply-templates select="author" />
</xsl:template>
<xsl:template match="author">
<xsl:value-of select="#ref"/>
<xsl:if test="position() !=last()" >
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
</xsl:styleshee
This will generate:
#Guy1,#Guy2
Update:
Do have the string join by and and have a count of items. Try this:
<xsl:template match="article">
<xsl:text>Author for </xsl:text>
<xsl:value-of select="#id"/>
<xsl:apply-templates select="author" />
<xsl:value-of select="count(authr[#ref])"/>
</xsl:template>
<xsl:template match="author">
<xsl:value-of select="#ref"/>
<xsl:if test="position() !=last()" >
<xsl:text> and </xsl:text>
</xsl:if>
</xsl:template>
With this output:
Author for 4#Guy1 and #Guy20
i'm new to xsl-fo.
In my problem there are 3 sibling tags, one of them has an attribute. I have to print the one with the attribute first and then the other two.
My problem is that my results aren't showing and i've tried when and if.
Heres my code:
<fo:block>
<xsl:for-each select="platforms/platform">
<xsl:choose>
<xsl:when test="#highestRated">
<xsl:value-of select="platform"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
! Also available on
<xsl:for-each select="platforms/platform">
<xsl:choose>
<xsl:when test="not(#*)">
<xsl:value-of select="platform"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</fo:block>
and heres an example of the siblings:
<platforms>
<platform>PC</platform>
<platform highestRated="true">PS3</platform>
<platform>X360</platform>
</platforms>
i can't just use them in the order they appear here because each set of siblings are in a different order.
I also get no errors and the rest of the document displays perfectly, they just won't show the results.
Thank you
As others have said, your main problem is that you have select="platform" which is looking for an element /platforms/platform/platform which doesn't exist. Also an xsl:choose with only one xsl:when is the same as an xsl:if. (It is useful to use xsl:when if you want a else condition, which xsl:if doesn't provide. Use xsl:choose / xsl:when / xsl:otherwise instead.) But in this case you may as well select only the elements you want using a predicate; then there is no need for a conditional.
Here is some code that does what you need.
<?xml version="1.0" encoding="UTF-8" ?>
<!-- New document created with EditiX at Wed Mar 21 21:51:52 GMT 2012 -->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:variable name="nl">
<xsl:text>
</xsl:text>
</xsl:variable>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<fo:block>
<xsl:value-of select="$nl"/>
<xsl:for-each select="platforms/platform[#highestRated]">
<xsl:value-of select="."/>
<xsl:value-of select="$nl"/>
</xsl:for-each>
<xsl:text>! Also available on</xsl:text>
<xsl:value-of select="$nl"/>
<xsl:for-each select="platforms/platform[not(#highestRated)]">
<xsl:value-of select="."/>
<xsl:value-of select="$nl"/>
</xsl:for-each>
</fo:block>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format">
PS3
! Also available on
PC
X360
</fo:block>
Try changing
<xsl:value-of select="platform"/>
to
<xsl:value-of select="."/>
Hope that helps