Get attributes of a parent node XSLT - xslt

I want to conditionally format URL's in Apache FOP, for this, I want to check if the property is of type HYPERLINK then apply conditional formatting and convert it into an URL.
Below is my XML
<properties>
<property type="CUSTOM" id="150" key="localizedfield">
<name>Localized Text</name>
<value>Test</value>
</property>
<property type="CUSTOM" id="149" key="textareafield">
<name>Textarea</name>
<value>My longer default text.</value>
</property>
<property type="HYPERLINK" key="ASSET_LINK">
<name>Asset Link</name>
<value>Test=https://test.com</value>
</property>
<property type="CUSTOM" key="VALIDITY">
<name>Asset Availability</name>
<value>Available</value>
</property>
</properties>
The XSL which I am using for transformation looks something like below
<xsl:template name="table-row">
<xsl:for-each select="properties/property">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="property">
<fo:table-cell >
<fo:block >
<xsl:choose>
<xsl:when test="<check if type is HYPERLINK>">
<!-- Format as hyperlink -->
</xsl:when>
<xsl:otherwise>
<!-- format as normal text -->
</xsl:otherwise>
</xsl:choose>
</fo:block>
</fo:table-cell>
</xsl:template>
in the xsl:when condition I only get the name and value, how can I get the complete property node here so I can check if the type attribute is HYPERLINK and then format accordingly?

I think you want
<xsl:when test="#type='HYPERLINK'">

Related

How to interrupt a page-sequence to insert another page-sequence

Via xsl:for-each-group I group my data with a property pagemaster. For this group I assign the page-sequence in the attribute pagemaster. Then the template is called for each element in this group.
Now to my question: is it somehow possible that I leave the current page-sequence in the for-each loop to insert another page-sequence and then continue with the previous one?
As an example, let's assume that all elements in the group should be rendered on blue pages, but if an element has a certain attribute, a red page will be inserted at this place.
My code section looks like this:
.
<!-- master-set defined here -->
.
</fo:layout-master-set>
<xsl:for-each-group select=".//reportelements/*[pagemaster != '']" group-adjacent="pagemaster">
<fo:page-sequence master-reference="{current-grouping-key()}">
<!-- Static-Content per pagemaster Group -->
<xsl:call-template name="STATIC-CONTENT">
<xsl:with-param name="pageMaster" select="current-grouping-key()" />
</xsl:call-template>
<fo:flow flow-name="xsl-region-body">
<xsl:for-each select="current-group()">
<!-- Here, for example, i would like to check if the current element (.)
is from type "section" and the attribute "hastoc" is true.
If yes the current page sequence should be interrupted and a new page-sequence
should be inserted on which the table of contents is rendered. -->
<xsl:apply-templates select="."/>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</xsl:for-each-group>
</fo:root>
</xsl:template>
<xsl:template name="STATIC-CONTENT">
<xsl:param name="pageMaster" />
<xsl:if test="$pageMaster = 'TITLEPAGE'">
<fo:static-content flow-name="xsl-region-after">
<fo:block text-align="right">
<fo:external-graphic src="url(file:C\Logo.pdf)" content-width="6cm" />
</fo:block>
</fo:static-content>
</xsl:if>
</xsl:template>
Xml Code looks like
<?xml version="1.0" encoding="utf-8"?>
<root>
<report>
<layout>report.xsl</layout>
<namecontent>Report</namecontent>
<reportelements>
<section>
<layout>section_normal</layout>
<pagemaster></pagemaster>
<titlecontent>Vorspann</titlecontent>
<hastoc>false</hastoc>
<reportelements>
<picture>
<layout>picture_title</layout>
<pagemaster>TITLEPAGE</pagemaster>
<titlecontent>Titelpicture</titlecontent>
<reportelements />
<path>files\Titelpicture213124.Jpeg</path>
</picture>
<section>
<layout>section_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>Profil</titlecontent>
<hastoc>true</hastoc>
<reportelements>
<paragraph>
<layout>paragraph_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>p1</titlecontent>
<reportelements />
<textcontent>paragraph_leader 1 text</textcontent>
</paragraph>
<paragraph>
<layout>paragraph_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>p2</titlecontent>
<reportelements />
<textcontent>paragraph_leader 2 text</textcontent>
</paragraph>
.
.
.
.
I am using the Apache Formatting Object Processor 2.3

Display a value from a preceding-sibling of a following sibling/child node?

Based on this XML I am trying to display a table with a row for each DOCREF that also shows the STEP2/TITLE where the REFERENCE equals the ID. I can make this work where the DOCREF/#REFERENCE = STEP2/#ID but where I am running into troubles is that it has been requested that the STEP2/TITLE also show for each STEP3 element until there is a new STEP2 element, and then it would show for all the STEP3's until it changes again and so on.
<WORKCARD>
<STSBODY>
<DOCREFS>
<DOCREF REFERENCE="123" VALUE="Ref1"/>
<DOCREF REFERENCE="456" VALUE="Ref2"/>
<DOCREF REFERENCE="789" VALUE="Ref3"/>
</DOCREFS>
</STSBODY>
<BODY>
<ITEMS>
<ITEM>
<XML>
<STEP2 ID="123">
<TITLE>Test1</TITLE>
</STEP2>
</XML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP3 ID=456>Step info goes here</STEP3>
</XML>
</ITEMXML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP2 ID=789>Test2</STEP3>
</XML>
</ITEMXML>
</ITEM>
</ITEMS>
</BODY>
</WORKCARD>
I am modifying an existing XSLT. Here is the section I am working with and you can see my various attempts that don't get me what I need.
<xsl:key name="step2Ref" match="STEP2" use="#ID" />
<xsl:when test="DOCREF[#TASK_CARD_ITEM > $item]">
<xsl:for-each select="DOCREF[#TASK_CARD_ITEM > $item and not($doctype = 'NDT' and key('refItem', #TASK_CARD_ITEM)/descendant::L1ITEM[#ID] and not(key('refItem',#TASK_CARD_ITEM)/descendant::L2ITEM))]">
<xsl:sort select="#TASK_CARD_ITEM" data-type="number" />
<!--<xsl:call-template name="subtaskitemrow"/>-->
<fo:table-row>
<fo:table-cell number-columns-spanned="6" border="solid 1pt red">
<fo:block>
<!-- this displays the correct info when there is a matching STEP2/#ID-->
<xsl:value-of select="key('step2Ref', #REFERENCE)/TITLE" />***
<!--This gets the STEP2/TITLE where STEP2/#ID = #REFERENCE-->
<xsl:value-of select="//STEP2/#ID" />+++
<xsl:value-of select="//ITEM/#TASK_CARD_ITEM" />***
<xsl:value-of select="#TASK_CARD_ITEM"/>+++
<xsl:variable name="tcitem"><xsl:value-of select="#TASK_CARD_ITEM" /></xsl:variable>
<xsl:value-of select="//ITEM[$tcitem]/#TASK_CARD_ITEM" />***
<!--preceding-sibling STEP2/#ID-->
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</xsl:when>
Some notes I've written to myself are:
When looping through the DOCREFS I need to display the STEP2/TITLE for each one until the STEP2/TITLE changes and then display the new one.
I need to match STEP3's preceding-sibling STEP2's #ID
Please help?
ETA:
Desired output for this sample (changed text in xml slightly from original post) would be something like this:
Test1
Test1
Test2
So for the first and second DOCREFs it would show the title from the first STEP2 and the third DOCREF would show the title from the second STEP2.
Your XML document is not well-formed, and - more importantly - the second <STEP2> has no <TITLE>. Assuming it can be corrected to:
<WORKCARD>
<STSBODY>
<DOCREFS>
<DOCREF REFERENCE="123" VALUE="Ref1"/>
<DOCREF REFERENCE="456" VALUE="Ref2"/>
<DOCREF REFERENCE="789" VALUE="Ref3"/>
</DOCREFS>
</STSBODY>
<BODY>
<ITEMS>
<ITEM>
<XML>
<STEP2 ID="123">
<TITLE>Test1</TITLE>
</STEP2>
</XML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP3 ID="456">Step info goes here</STEP3>
</XML>
</ITEMXML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP2 ID="789">
<TITLE>Test2</TITLE>
</STEP2>
</XML>
</ITEMXML>
</ITEM>
</ITEMS>
</BODY>
</WORKCARD>
you can use the following logic to retrieve the data you need (for testing purposes, more is shown than required in your question):
<xsl:key name="step2Ref" match="STEP2" use="#ID" />
...
<xsl:template match="DOCREF">
<tr>
<!-- DOCREF VALUE-->
<td><xsl:value-of select="#VALUE" /></td>
<!-- DOCREF REFERENCE -->
<td><xsl:value-of select="#REFERENCE" /></td>
<xsl:variable name="ref">
<xsl:choose>
<xsl:when test="key('step2Ref', #REFERENCE)">
<xsl:value-of select="#REFERENCE" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="preceding-sibling::DOCREF[key('step2Ref', #REFERENCE)][1]/#REFERENCE" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- REFERENCE USED FOR KEY -->
<td><xsl:value-of select="$ref" /></td>
<!-- RETRIEVED TITLE -->
<td><xsl:value-of select="key('step2Ref', $ref)/TITLE" /></td>
</tr>
</xsl:template>
In the above example, the following result is returned:
Ref1 123 123 Test1
Ref2 456 123 Test1
Ref3 789 789 Test2
Note that it is assumed that the first <DOCREF> does have a related <STEP2>.

XML to csv conversion using xslt-ouput multiple values in particular format

I'm trying to convert the xml data in following format to csv format using xslt.I am right now struck trying to output multivalues for particular custom field.
This is my xml data.
<input-xml>
<add add-value="First Name">
<value type="string">Newbee</value>
</add>
<add add-value="Surname">
<value type="string">user1</value>
</add>
<add add-value="Title">
<value type="string">Software,engineer</value>
</add>
<add add-value="Title">
<value type="string">Associate level</value>
</add>
<add add-value="Description">
<value type="string">This user is new to xslt.</value>
</add>
</input-xml>
Below is the snippet of the xslt code I am using to transform the data.
<xsl:template match="input-xml">
<!-- output the fields in order -->
<xsl:call-template name="output-field">
<xsl:with-param name="field-value" select="*[(#attr-name = 'First')]/value"/>
</xsl:call-template>
.............
.............
<xsl:call-template name="output-field">
<xsl:with-param name="field-value" select="*[(#attr-name = 'Title')]/value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="output-field">
<xsl:param name="field-value"/>
<xsl:choose>
<xsl:when test="contains($field-value,$delimiter)">
<!-- if the field value contains a delimiter, then enclose in quotes -->
<!--delimiter here is comma (,)-->
<xsl:text>"</xsl:text>
<xsl:value-of select="$field-value"/>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- otherwise output it raw -->
<xsl:value-of select="$field-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template
With the above code I'm able to output data as:
Newbee,User1,Software engineer,This user is new to xslt.
This is absolutely fine as long as there are no multiple values.
The problem now is there are two value for field 'Title' which will have to enclosed within quotes ("") as below.
Expected output csv data:
Newbee,User1,"|Software engineer|,|Associate level|",This user is new to xslt.
I'm not able to figure out what changes i'll have to make above code and how to proceed further..
Can anyone help me out..??
#LarsH your previous reply to question i posted is working fine..I found that for the below xml format,the xslt doesn't quite work the way it is desired.
<input-xml>
<add add-value="First Name">
<value type="string">Newbee</value>
</add>
<add add-value="Surname">
<value type="string">user1</value>
<add add-value="Title">
<value type="string">Associate level</value>
<value type="string">Software,engineer</value>
</add>
<add add-value="Description">
<value type="string">This user is new to xslt.</value>
</add>
</input-xml>
The output i'm getting is
Newbee,User1,Software engineerAssociate level,This user is new to xslt.
Do i need to write another xslt to be able to handle this xml..? can i modify the existing xslt itself to take care of both xml variants..?
I would change your output-field template so that instead of taking the value to output, it takes the name of the field (attribute). E.g. pass a field-name parameter instead of field-value. Then you can put your logic about multiple values into that template.
[On second thought, you could do this just as well by passing field value*s* in the way that you already were ... just recognizing that what you are passing is a nodeset, not necessarily just a single value.]
In the output-field template, you can say something like:
<xsl:template name="output-field">
<xsl:param name="field-name"/>
<xsl:variable name="field-values" select="*[#attr-name = $field-name]" />
<xsl:choose>
<xsl:when test="count($field-values) > 1">
<xsl:text>"</xsl:text>
<xsl:for-each select="$field-values">
<xsl:value-of select="concat('|', ., '|')" />
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:when test="contains($field-values[1], $delimiter)">
... the rest of your existing template
</xsl:choose>
</xsl:template>
If you need more help with some of those details, let us know which ones.

assigning a variable in xslt

I have the following xsl with me
<Root>
<child>
<Book name="Title" value="hailey" />
<Book name="Title" value="After death" />
<Book name="Price" value="100" />
</child>
<child>
<Book name="Title" value="After death" />
<Book name="genre" value="fiction" />
</child>
</Root>
I want to iterate through the "child" node and if "Title" is appearing (atleast once), i want a variable to be set. I am using the following code in xslt
<xsl:variable name="flag">
<xsl:for-each select="/Root/Child" >
<xsl:for-each select="./Book" >
<xsl:if test="./#name = 'Title'">
<xsl:value-of select="'true'"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
The problem is i the value if variable "flag" is set as "truetruetrue", whereas i want it to be just "true". Any help is appreciated
No need for iteration or conditional instructions at all. Just use this one-liner:
<xsl:variable name="vYourName" select="boolean(/Root/Child/Book[#name='Title'])"/>
For this particular XML document this can be expressed even shorter:
<xsl:variable name="vYourName" select="boolean(/*/*/*[#name='Title'])"/>
Explanation:
Both definitions define the variable named "vYourName" to be true() exactly when at least one of the Root/Child/Book elements has a Title attribute.
Do note:
By definition the function boolean ($ns) returns true if and only if the nodeset $ns is non-empty.
The string representation of the boolean value true() is the string "true".
Update:
In a comment, the OP asked:
if there is atleast one occurence, is there a way to assign the
"value" of that to the variable?
The answer: Yes, if by "the value" you mean the first value attribute, use:
<xsl:variable name="vYourName" select="(/*/*/*[#name='Title'])[1]/#value"/>
<xsl:variable name="flag">
<xsl:if test="/Root/Child/Book/#name = 'Title'">
<xsl:value-of select="'true'"/>
</xsl:if>
</xsl:variable>
If you like to stick with your code may use
<xsl:variable name="flag">
<xsl:for-each select="//Book" >
<xsl:if test="#name = 'Title'">
<xsl:value-of select="'true'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Untested (and UPDATED thanks to the comment from Lucero)...
<xsl:variable name="flag">
<xsl:if test="count(/Root/Child/Book[#name='Title'])>0">true</xsl:if>
</xsl:variable>

XSLT help - XSL for various similar XML, Template usage

I am very new to XSL and XPath. Apologies if this question shows some stupidity.
I have an XML something like
<root>
<widget name="status">
...
<component name="date">
<component name="day" label="Fri"/>
<component name="date" label="4"/>
</component>
<component name="time" label="11:23 AM"/>
....
</widget>
<widget name="foo">
</widget>
</root>
I need to create a DateTime tag which is compose of all the three values something like
Fri 4 11:23 AM
I am writing an XSL for it.
<DateTime>
<xsl:value-of select="(//widget[#name="status"]/component[#name='date'])[1]/#label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="(//widget[#name="status"]/component[#name='date'])[2]/#label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//widget[#name="status"]/component[#name='time']/#label"/>
</DateTime>
Question:
I am passing the "widget[#name="date"]" to each of the select statement. Is there any better way to shorten the xpath.
I need to move this into a template and call the template. which one I should use call-template/apply-templates?
We have a set of similar applications which generate these XML. The above XML is from applicationA. ApplicationB might show the detail in little bit different way, something like <component name="datetime">Fri 4 11:23 AM</component>. We have almost 3-4 such application where they display the details in little bit different way.
DateTime is just an example, there are some other details which I also need to capture from these various applications.
I am thinking to write a single XSL to deal with all the applications.
One way to do it with your XML would be this:
<xsl:template match="widget">
<!-- ... -->
<xsl:apply-templates select="." mode="create-date-time" />
<!-- ... -->
</xsl:template>
<xsl:template match="widget" mode="create-date-time">
<xsl:variable name="date" select="component[#name='date']" />
<xsl:variable name="time" select="component[#name='time']" />
<DateTime>
<xsl:value-of select="normalize-space(
concat(
$date/component[#name='day']/#label, ' ',
$date/component[#name='date']/#label, ' ',
$time/#label
)
)" />
</DateTime>
</xsl:template>
I am passing the widget[#name="date"] to each of the select statement. Is there any better way to shorten the xpath.
Use <xsl:template>/<xsl:apply-templates>, and relative paths. Store things you need more than once in an <xsl:variable>. See above.
I need to move this into a template and call the template. which one I should use call-template/apply-templates?
The latter. Always go for <xsl:apply-templates> unless there is good reason not to. As a rule of thumb: If you are unsure, then there is no good reason.
We have a set of similar applications which generate these XML. The above XML is from applicationA. ApplicationB might show the detail in little bit different way, something like <component name="datetime">Fri 4 11:23 AM</component> We have almost 3-4 such application where they display the details in little bit different way.
You could expand the create-date-time template to accommodate for this:
<xsl:template match="widget" mode="create-date-time">
<xsl:variable name="date" select="component[#name='date']" />
<xsl:variable name="time" select="component[#name='time']" />
<xsl:variable name="dt" select="component[#name='datetime']" />
<DateTime>
<xsl:value-of select="normalize-space(
concat(
$dt/label, ' ',
$date/component[#name='day']/#label, ' ',
$date/component[#name='date']/#label, ' ',
$time/#label
)
)" />
</DateTime>
</xsl:template>
There will be no error if certain components are missing. normalize-space() makes sure that there are no excess spaces for any combination of components.
The above may fail if the date+time and datetime components are not mutually exclusive (I've assumed they are). If they are not, or if more complicated cases occur, create additional specific templates, like this one:
<xsl:template match="widget[component[name='datetime']]" mode="create-date-time">
<xsl:variable name="dt" select="component[#name='datetime']" />
<DateTime>
<xsl:value-of select="component[#name='datetime']/#label" />
</DateTime>
</xsl:template>
The <xsl:apply-templates> will make sure the correct one is called. Just create specific match= expressions for each case that can occur.
This simple transformation (16 lines, single template, completely "push" style, no variables, no modes):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match=
"component
[contains('|day|date|time|',
concat('|', #name, '|'))
]
">
<xsl:value-of select="concat(#label, ' ')"/>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<widget name="status">
...
<component name="date">
<component name="day" label="Fri"/>
<component name="date" label="4"/>
</component>
<component name="time" label="11:23 AM"/>
....
</widget>
<widget name="foo">
</widget>
</root>
produces exactly the wanted result:
Fri 4 11:23 AM