Numbering tables where multiple instances count as one table XSLT 3.0 - count

Given this XML:
<preliminaryRqmts>
<!-- Table 1 -->
<reqCondGroup>
<reqCondNoRef>
<reqCond>Lorem ipsum</reqCond>
</reqCondNoRef>
</reqCondGroup>
<!-- Table 2 -->
<reqPersons>
<person man="A">
<personCategory personCategoryCode="Chemical technician"/>
<personSkill skillLevelCode="sk02"/>
<trade>Cleaner</trade>
<estimatedTime unitOfMeasure="h">1,0</estimatedTime>
</person>
</reqPersons>
<reqPersons>
<person man="B">
<personCategory personCategoryCode="Operator"/>
<personSkill skillLevelCode="sk02"/>
<trade>Painter</trade>
<estimatedTime unitOfMeasure="h">1,0</estimatedTime>
</person>
</reqPersons>
<reqPersons>
<person man="B">
<personCategory personCategoryCode="Operator"/>
<personSkill skillLevelCode="sk03"/>
<trade>Rider</trade>
<estimatedTime unitOfMeasure="h">0,8</estimatedTime>
</person>
</reqPersons>
<!-- Table 3 -->
<reqSafety>
<noSafety/>
</reqSafety>
</preliminaryRqmts>
<taskDefinition>
<task>
<taskDescr>
<simplePara>Lorem ipsum</simplePara>
</taskDescr>
</task>
<preliminaryRqmts>
<!-- Table 4 -->
<reqCondGroup>
<noConds/>
</reqCondGroup>
<!-- Table 5 -->
<reqPersons>
<person man="A">
<personCategory personCategoryCode="Basic user"/>
<trade>Operator</trade>
<estimatedTime unitOfMeasure="h">0,3</estimatedTime>
</person>
</reqPersons>
<!-- Table 6 -->
<reqSpares>
<noSpares/>
</reqSpares>
</preliminaryRqmts>
</taskDefinition>
I have to include the Table number when outputting the table title. <reqPersons> may have multiple siblings but they are counted as one table. So in the XML provided, the first three reqPersons are counted as one table, Table 2. reqPersons is not a required element so there could be <preliminaryRqmts> without any <reqPersons>.
I am having trouble getting the correct table numbering for reqPersons when there is more than one preliminaryRqmts with a reqPersons. Originally I had <xsl:value-of select="if(preceding::reqPersons) then 1 else 0"/>. This fails when there are multiple preliminaryRqmts/reqPersons.
Here are the templates for the table numbering and reqPersons. Only the first reqPersons gets a title and table number. Any following-siblings::reqPersons are ignored in the table count. I need help with fixing <xsl:variable name="countPer" select="count(ancestor-or-self::preliminaryRqmts/reqPersons[1])"/>, the rest of the numbering is working properly.
<xsl:template match="reqPersons[1]">
<fo:block>
<xsl:text>Table </xsl:text>
<xsl:call-template name="number-tables"/>
<xsl:text>  Required persons</xsl:text>
</fo:block>
<fo:table>
<xsl:call-template name="reqPersonTableBody"/>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template match="reqPersons">
<fo:block>
<fo:table>
<xsl:call-template name="reqPersonTableBody"/>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template name="reqPersonTableBody">
<fo:table-column column-number="1" column-width="33%"/>
<fo:table-column column-number="2" column-width="33%"/>
<fo:table-column column-number="3" column-width="33%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<fo:block>Person</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Category</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Skill level</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates select="personnel | person"/>
</fo:table-body>
</xsl:template>
<xsl:template name="number-tables">
<xsl:variable name="countreqCondTables" select="count(preceding::reqCondGroup|preceding::reqSupportEquips|preceding::reqSupplies|preceding::reqSpares|preceding::reqTechInfoGroup)"/>
<xsl:variable name="countPer" select="count(ancestor-or-self::preliminaryRqmts/reqPersons[1])"/>
<xsl:variable name="countSelfPer" select="count(ancestor-or-self::reqPersons[1])"/>
<xsl:variable name="countSelf" select="count(ancestor-or-self::table|ancestor-or-self::reqCondGroup|ancestor-or-self::reqSupportEquips|ancestor-or-self::reqSupplies|
ancestor-or-self::reqSpares|ancestor-or-self::reqTechInfoGroup)"/>
<xsl:value-of select="$countPer+$countSelf+$countSelfPer+$countreqCondTables"/>
</xsl:template>

I would check whether you can use xsl:number count="some pattern matching the elements you want to count" level="any" e.g. in XSLT 3
<xsl:param name="table-count-pattern" static="yes" as="xs:string" select="'preliminaryRqmts/reqCondGroup | preliminaryRqmts/reqPersons[1] | preliminaryRqmts/reqSafety | preliminaryRqmts/reqSpares'"/>
<xsl:template _match="{$table-count-pattern}">
<xsl:comment>computed table <xsl:number _count="{$table-count-pattern}" level="any"/></xsl:comment>
<xsl:next-match/>
</xsl:template>
seems to give the numbers you have in your comments output in another comment. Of course, you don't want to output a comment, you want to output that number in your fo:block but you can obviously adapt the above suggestion easily. If needed, if you have lots of different templates for all the elements that need to output that count, put the above template in a mode with e.g. mode="count", and in your other templates, where you need to number, use e.g. <xsl:apply-templates select="." mode="count"/>. You will probably want to remove the xsl:next-match in that case.

Related

Need to reduce the 20 days prior from the input xml

Need to get the 20 days prior from the input xml element.
Input xml I'm having(year-month-day):
<section>
<Plans>
<Date>2022-01-01</Date>
</Plans>
</section>
<p>20 days back date: <keyword keyref="Cost:Date:date4"/></p>
XSL I'm using
<xsl:template match="keyword[contains(#keyref, 'Cost:Date:date4')]">
<xsl:param name="section" as="element()" tunnel="yes">
<empty/>
</xsl:param>
<keyword keyref="Cost:Date:date4">
<xsl:call-template name="format_variable">
<xsl:with-param name="cur_keyref" select="#keyref"/>
<xsl:with-param name="cur_value"
select="$section//Plans/Date"/>
<xsl:with-param name="cur_format" select="'date4'"/>
</xsl:call-template>
</keyword>
</xsl:template>
<xsl:template name="format_variable">
<xsl:param name="cur_keyref" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_value" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_format" as="xs:string">MISSING</xsl:param>
<xsl:choose>
<xsl:when test="$cur_format = 'date4'">
<xsl:variable name="format" select="replace($cur_value, '(.{4})-(.{2})-(.{2})', '$2/$3/$1')"/>
<xsl:value-of select="$format - xs:dateTime('P20D')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="concat('MISSING_FORMAT_', $cur_keyref, '_', $cur_value, '_[', $cur_format, ']')"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
When I'm trying the above code its showing the below error:
Invalid dateTime value "P20D" (Non-numeric year component)
Expected output is:
20 days back date: 12/12/2021
FWIW, I believe you are grossly overcomplicating this. The date in the input XML is in YYYY-MM-DD format; all you need to do is subtract a duration of 20 days from the given date - see demo here: https://xsltfiddle.liberty-development.net/6qaCymJ
I think you want to subtract a dayTimeDuration with e.g. <xsl:value-of select="$format - xs:dayTimeDuration('P20D')"/>.

Mapping two records to same node, where one is an iteration of a child record of the other

I wish to create a iteration of a node to a node on the source schema. This is easy enough, but the trouble comes when I want to create different iterations on the same node based on a child of the first node.
<cases>
<customer>
<account>
<name>John Smith</name>
<address>hello road 321</address>
<current_balance>100</current_balance>
<current_balance_date>20180712</current_balance_date>
</account>
<invoices>
<invoice>
<amount>231</amount>
<paydate>20183104</paydate>
</invoice>
<invoice>
<amount>2332</amount>
<paydate>20181204</paydate>
</invoice>
</invoices>
</customer>
</cases>
There can be one current_balance per customer, but several invoices, and I need to map them to the same node on the target schema, and have it look like this:
<basis>
<toPay>100</toPay>
<dateToPay>20180712</dateToPay>
</basis>
<basis>
<toPay>231</toPay>
<dateToPay>20183104</dateToPay>
</basis>
<basis>
<toPay>2332</toPay>
<dateToPay>20181204</dateToPay>
</basis>
I have tried table looping, regular looping, conditional looping, and created xslt (which I also am very inexperienced with), and can't seem to make it work. I am only able to make either one of each or two of each.
Edit: I am currently trying an xslt-inline-call:
<xsl:template name="basis">
<!-- balance-parameters -->
<xsl:param name="current_balance" />
<xsl:param name="current_balance_date" />
<!-- invoice-parameters -->
<xsl:param name="amount" />
<xsl:param name="paydate" />
<xsl:element name="basis">
<xsl:element name="toPay"><xsl:value-of select="$current_balance" /></xsl:element>
<xsl:element name="dateToPay"><xsl:value-of select="$current_balance_date" /></xsl:element>
</xsl:element>
<xsl:for-each select="cases/customer/account/invoices/invoice">
<xsl:element name="basis">
<xsl:element name="toPay"><xsl:value-of select="$amount" /></xsl:element>
<xsl:element name="dateToPay"><xsl:value-of select="$paydate" /></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:template>
The for-each does not output anything at all, I have tried both case/customer/invoices/invoice and case/customer/invoices. I just cannot make this work at all
This one should work. You should loop in /cases/customer/account/invoices/invoice not case/customer/invoices/invoice
<xsl:template name="basis">
<basis>
<toPay>
<xsl:value-of select="/cases/customer/account/current_balance" />
</toPay>
<dateToPay>
<xsl:value-of select="/cases/customer/account/current_balance_date" />
</dateToPay>
</basis>
<xsl:for-each select="/cases/customer/account/invoices/invoice">
<basis>
<toPay>
<xsl:value-of select="amount" />
</toPay>
<dateToPay>
<xsl:value-of select="paydate" />
</dateToPay>
</basis>
</xsl:for-each></xsl:template>
First, make sure the source Schema has basis and set as maxOccurs = unbounded.
This is actually really simple. current_balance should work with just links.
You will need Looping Functoid linking invoice and basis and another Looping Functoid linking current_balance and basis.
That should create two for-each in the xsl which you can view using Validate Map.

How to output an IMG tag from XML using XSLT

Here is my XML:
<?xml version="1.0" encoding="UTF-8"?>
<Collection>
<Content>
<ID>2779</ID>
<Type>Content</Type>
<Title>Article One</Title>
<QuickLink>/template.aspx?id=2779</QuickLink>
<Teaser />
<Html>
<root>
<NewsArticle>
<artTitle>The Comprehensive Breast Center: Quality Care on the Fast Track</artTitle>
<artThumb>
<img alt="Thumb Article One" src="/uploadedImages/Test/News/artOne.png?n=5954" />
</artThumb>
<artFull />
<releaseDate />
<contactName />
<contactPhone />
<contactEmail />
<artTeaser>The National Cancer Institute estimates that a woman in the United States has a 1 in 8 chance of developing invasive breast cancer</artTeaser>
<artText>
<p>The Comprehensive Breast Center: Quality Care on
the Fast Track</p>
<p>
How do I display the IMG tag from my XML above to an HTML document using XSLT
Something like this should do the trick:
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:copy-of select="Html/root/NewsArticle/artThumb/node()"/>
</xsl:for-each>
</xsl:template>
I should note that this assumes you're getting this from an Ektron collection -- assumption made based on your tagging of this question. This will display the image from each content block in the collection. If you want just the image from the first content block of the collection, you could remove the for-each and instead use something like this:
<xsl:template match="/">
<xsl:copy-of select="Collection/Content/Html/root/NewsArticle/artThumb/node()"/>
</xsl:template>
Also, it works either way, but i removed the slash from the front of the select on the for-each. Seemed redundant since the code is in a template that already matches on "/".
UPDATE
Some of that can be done in the workarea -- it allows you to set the css class, though I'm not sure if you can set the title attribute of an image. Here's how you could do that via XSLT. In this case, you can't copy the node whole-sale:
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:variable name="imageSrc" select="Html/root/NewsArticle/artThumb/img/#src" />
<xsl:variable name="imageId">
<xsl:text>NewsArticle_</xsl:text>
<xsl:value-of select="ID" />
<xsl:text>_image</xsl:text>
</xsl:variable>
<xsl:variable name="contentTitle" select="Html/root/NewsArticle/artTitle" />
<img id="{ $imageId }" class="myCssClass" title="{ $contentTitle }" alt="{ $contentTitle }" src="{ $imageSrc }" />
</xsl:for-each>
</xsl:template>
(Updated again - appears i misread your comment. Thanks, #MathiasMüller!)
When assigning ids to elements like this, I prefer to use a little more than just the content id. In this case, by using "NewsArticle_{Content ID}image", I allow for a container div to use an id "NewsArticle{Content Id}" if it is needed in the future without colliding with the image ids.
How do i assign a title and an alt from artTitle and also a class and id?
Building upon the answer given by #BrianOliver, this is how you output an img element whose "title" attribute reflects the content of artTitle from your input XML - the same for ID.
I assume that by "an alt from artTitle" you mean that the text content of img/#alt should also come from the artTitle element.
<xsl:template match="/">
<xsl:for-each select="Collection/Content">
<xsl:variable name="imageSrc" select="Html/root/NewsArticle/artThumb/img/#src" />
<!--xsl:variable name="imageAlt" select="Html/root/NewsArticle/artThumb/img/#alt" /-->
<xsl:variable name="imageId" select="ID"/>
<xsl:variable name="imageTitle" select="Html/root/NewsArticle/artTitle"/>
<img id="{$imageId}" class="myCssClass" title="{$imageTitle}" alt="{ $imageTitle}" src="{$imageSrc}"/>
</xsl:for-each>
</xsl:template>
However, I am not sure where the class attribute should come from.

how to get child nodes in xsl

here my code-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="ArrayOfLinkEntity" name="bindLink">
<ul>
<xsl:for-each select="LinkEntity[ParentLinkId=0]">
<li>
<xsl:variable name="linkId" select="LinkId"/>
<xsl:variable name="child" select="count(/ArrayOfLinkEntity/LinkEntity[ParentLinkId=$linkId])"/>
<xsl:value-of select="$child"/>
<xsl:choose>
<xsl:when test="($child > 0)">
<a href="#" data-flexmenu="flexmenu1" onclick="javascript:setPageLinkId({$linkId});">
<xsl:value-of select="LinkTitle"/>
<img src="../images/down.gif" border="0"/>
</a>
</xsl:when>
<xsl:otherwise >
<a href="#" onclick="javascript:setPageLinkId({$linkId});">
<xsl:value-of select="LinkTitle"/>
</a>
</xsl:otherwise>
</xsl:choose>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
but I am getting $child=0 always.but there exists children.
my xml structure-
<ArrayOfLinkEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LinkEntity>
<EntityId>00000000-0000-0000-0000-000000000000</EntityId>
<LinkId>1</LinkId>
<SequenceNo>1</SequenceNo>
<ParentLinkId>0</ParentLinkId>
<LinkTitle>Home</LinkTitle>
<SubLink />
</LinkEntity> ...
</ArrayOfLinkEntity>
What should I do? Please suggest.
but I am getting $child=0 always.but
there exists children.
If by "children" you mean a LinkEntity with ParentLinkId child that is equal to the LinkId of the current node, the result you get is correct.
The only LinkEntity has an LinkId 1, but there are no LinkEntity elements in the provided XML document whose ParentLinkId is 1.
You need to show a complete (but the shortest possible) XML document on which your code exhibits this issue. Without being able to repro the problem, no one can give you a logical advice.
From your following code:
<xsl:variable name="linkId" select="LinkId"/>
<xsl:variable name="child" select="count(/ArrayOfLinkEntity/LinkEntity[ParentLinkId=$linkId])"/>
This occurs within a for-each loop where the only nodes being looped over are LinkEntity's with ParentLinkId = 0. But from your source XML, the value of LinkId = 1, and in your variable assignment for $child, you are selecting on LinkEntity's with ParentLinkId = 1, which don't exist in your source XML data.
If I misunderstood something please let me know, but from what I can see that may be the problem.

Select/Output unique values

I need to loop through an XML document (no problem over there) and check if a value that i find is already in a (a) tag in a div in my XSL document that i am generating, only if the value is not in that (a) tag i should create a new (a) tag for it and put in in the div that i am checking...
Any one knows how to do it dynamically in XSLT?
<div id="tags"><span class="l_cap"> </span>
all
<xsl:for-each select="root/nodes/node/data/genres">
<xsl:for-each select="value">
**<xsl:if test="not(contains())">**
<xsl:value-of select="current()"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
 
sorry for before, what i am trying to do is: in the if statement, check if the current value is already exist in the div if not, add it, if is, don't do anything...
10x again
It sounds like you're trying to create a distinct list of all of the "genres" in your list.
Assuming a data structure which looks a bit like this:
<root>
<nodes>
<node>
<data>
<genres>
<value>One</value>
<value>Two</value>
<value>Two</value>
<value>Three</value>
<value>Two</value>
</genres>
</data>
</node>
</nodes>
</root>
And a stylesheet which looks a bit like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="genres" match="value" use="."/>
<xsl:template match="/">
<div>
<xsl:for-each select="/root/nodes/node/data/genres/value">
<xsl:if test="generate-id(.) = generate-id(key('genres', .)[1])">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
Then you will end up with something like this:
<div>
One
Two
Three
</div>
This is a fairly standard XSLT 1.0 technique. It uses keys (described here: http://www.xml.com/pub/a/2002/02/06/key-lookups.html ) to create a sort of index of all the /root/nodes/node/data/genres/value entries. Then it loops through all of the entries, but only prints the first one of each type. The end result is that each value will only be output once.

Resources