grouping into element with condition on attribute of following sibling - xslt-2.0

I have this xml-File:
<w>
<!-- many text nodes like below-->
<text attr1="3" width="100">This is a sentence.</text>
<text attr1="5" width="110">Another sentence, this time with a % in it.</text>
<text attr1="9" width="40">Some text.</text>
<text attr1="3" width="49">Other text.</text>
<text attr1="1" width="90">Again some text</text>
<!-- many text nodes like above-->
</w>
I want to collapse all following nodes of the element with the '%' in it which have a width-attribute lower than 50 into its text node and surround the whole group with <tag1>, so that it looks like this:
<w>
<!-- many text nodes like below-->
<text attr1="3" width="100">This is a sentence.</text>
<text attr1="5" width="110">Another sentence, this time with a
<tag1>% in it. Some text. Other text.</tag1>
</text>
<text attr1="1" width="90">Again some text</text>
<!-- many text nodes like above-->
</w>
I have tried it with this template:
<xsl:choose>
<xsl:when test="contains(.,'%') and following-group|#width <50">
<!-- I cannot choose current-group(), it generates a output with no tag1's
at all -->
<tag1><xsl:value-of-select="."/></tag1>
</xsl:when>
</xsl:choose>
But this only puts the line which contains the % in the <tag1>s. I dont understand why I cant select the whole group I match in the test. I am also aware that even if it matched the whole thing, the <tag1>s would include the whole line with the %, but this is secondary.

Here is a sample that tries to solve it, although I would need more input samples or clearer and more detailed description of the possible inputs:
<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:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="text[contains(., '%')]">
<xsl:choose>
<xsl:when test="not(self::text[contains(., '%')])">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="head" select="."/>
<xsl:for-each-group select="current-group() except $head" group-adjacent="boolean(#width < 50)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:element name="{name($head)}" namespace="{namespace-uri($head)}">
<xsl:copy-of select="#*"/>
<xsl:analyze-string select="$head" regex="%.*$">
<xsl:matching-substring>
<tag1>
<xsl:value-of select="., current-group()/node()/string()"/>
</tag1>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With that stylesheet Saxon 9 transforms the input sample
<w>
<text>foo</text>
<text>bar</text>
<text attr1="3" width="100">This is a sentence.</text>
<text attr1="5" width="110">Another sentence, this time with a % in it.</text>
<text attr1="9" width="40">Some text.</text>
<text attr1="3" width="49">Other text.</text>
<text attr1="1" width="90">Again some text</text>
<text>foo bar</text>
<text>foo baz</text>
</w>
into the following result:
<w>
<text>foo</text>
<text>bar</text>
<text attr1="3" width="100">This is a sentence.</text>
<text attr1="9" width="40">Another sentence, this time with a <tag1>% in it. Some text. Other text.</tag1>
</text>
<text attr1="1" width="90">Again some text</text>
<text>foo bar</text>
<text>foo baz</text>
</w>

Related

Need help in grouping and transposing data using XSLT

I need help in conditional grouping and transposing XML data.
I need to group and transpose data using week of the month, Worker ID and Rate_Category_Code. Need to report the Hours_Worked in the appropriate weeks. Hours would be blank for all other week. Data in XML will be never for more then a month.
If there are overtime hours reported (Identified by Rate_Category_Code as OT) then i need to create separate row for the week in which overtime hours was reported.
Below is the XML data -
``
<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1120_CR_HR_Fieldglass_Daily_Timesheet">
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>8</wd:Hours_Worked>
<wd:Reported_Date>2019-06-10</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>8</wd:Hours_Worked>
<wd:Reported_Date>2019-06-18</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001743</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>OT</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>1</wd:Hours_Worked>
<wd:Reported_Date>2019-06-10</wd:Reported_Date>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_ID>FNRAWK00001742</wd:Worker_ID>
<wd:Task_Code>01</wd:Task_Code>
<wd:GL_Account_Code>Default</wd:GL_Account_Code>
<wd:Rate_Category_Code>ST</wd:Rate_Category_Code>
<wd:UOM>HR</wd:UOM>
<wd:Hours_Worked>9</wd:Hours_Worked>
<wd:Reported_Date>2019-06-20</wd:Reported_Date>
</wd:Report_Entry>
</wd:Report_Data>
``
Desired output -
``
Type=Upload Full Time Sheet with Revision
Transaction=True
Approval Required=True
Messaging Required=False
Language=English (United States)
Number Format=#,##9.99 (Example: 1,234,567.99)
Date Format=MM/DD/YYYY
Submit=FALSE
Buyer=FNRA
Supplier Review=False
Comments=
Worker_ID|Month_Start_Date|First_Monday|Cost_Center_Code|Task_Code|GL_Account_Code|Rate_Category_Code|UOM|Mon_Hrs|Tue_Hrs|Wed_Hrs|Thu_Hrs|Fri_Hrs|Sat_Hrs|Sun_Hrs
FNRAWK00001743|06/01/2019|05/27/2019||01|Default|ST|HR||||||||
FNRAWK00001743|06/01/2019|06/03/2019||01|Default|ST|HR||||||||
FNRAWK00001743|06/01/2019|06/10/2019||01|Default|ST|HR|8|||||||
FNRAWK00001743|06/01/2019|06/10/2019||01|Default|OT|HR|1|||||||
FNRAWK00001743|06/01/2019|06/17/2019||01|Default|ST|HR||8||||||
FNRAWK00001743|06/01/2019|06/24/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|05/27/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/03/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/10/2019||01|Default|ST|HR||||||||
FNRAWK00001742|06/01/2019|06/10/2019||01|Default|OT|HR||||||||
FNRAWK00001742|06/01/2019|06/17/2019||01|Default|ST|HR||||9||||
FNRAWK00001742|06/01/2019|06/24/2019||01|Default|ST|HR||||||||
``
XSLT -
``
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday.report/INT1120_CR_HR_Fieldglass_Daily_Timesheet" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:my="http://www.example.com/my" xmlns:functx="http://www.functx.com" exclude-result-prefixes="xs my functx">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:variable name="Linefeed" select="'
'"/>
<xsl:variable name="Delimiter" select=" '|' "/>
<xsl:function name="functx:repeat-string" as="xs:string">
<xsl:param name="stringToRepeat" as="xs:string?"/>
<xsl:param name="count" as="xs:integer"/>
<xsl:sequence select="string-join((for $i in 1 to $count return $stringToRepeat),'')"/>
</xsl:function>
<xsl:function name="functx:pad-integer-to-length" as="xs:string">
<xsl:param name="integerToPad" as="xs:anyAtomicType?"/>
<xsl:param name="length" as="xs:integer"/>
<xsl:sequence select="if ($length < string-length(string($integerToPad))) then error(xs:QName('functx:Integer_Longer_Than_Length')) else concat (functx:repeat-string('0',$length - string-length(string($integerToPad))),string($integerToPad))"/>
</xsl:function>
<xsl:function name="functx:date" as="xs:date">
<xsl:param name="year" as="xs:anyAtomicType"/>
<xsl:param name="month" as="xs:anyAtomicType"/>
<xsl:param name="day" as="xs:anyAtomicType"/>
<xsl:sequence select="xs:date(concat(functx:pad-integer-to-length(xs:integer($year),4),'-',functx:pad-integer-to-length(xs:integer($month),2),'-',functx:pad-integer-to-length(xs:integer($day),2)))"/>
</xsl:function>
<!--Function which accepts date as input and returns the date for first day of the month (Input Date)-->
<xsl:function name="functx:first-day-of-month" as="xs:date?">
<xsl:param name="date" as="xs:anyAtomicType?"/>
<xsl:sequence select="functx:date(year-from-date(xs:date($date)),month-from-date(xs:date($date)),1)"/>
</xsl:function>
<!--Function which accepts date as input and returns the date for first day of the week-->
<xsl:function name="my:thisMonday">
<xsl:param name="date"/>
<xsl:variable name="epoch" select="xs:date('0001-01-01')"/>
<xsl:variable name="dayNumber" select="fn:days-from-duration($date - $epoch)"/>
<xsl:variable name="dayOfWeek" select="$dayNumber mod 7"/>
<xsl:value-of select="$date - xs:dayTimeDuration(concat('P', $dayOfWeek, 'D' ))"/>
</xsl:function>
<xsl:template match="wd:Report_Data">
<File>
<Header>
<xsl:text>Type=Upload Full Time Sheet with Revision</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Transaction=True</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Approval Required=True</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Messaging Required=False</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Language=English (United States)</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Number Format=#,##9.99 (Example: 1,234,567.99)</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Date Format=MM/DD/YYYY</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Submit=FALSE</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Buyer=FNRA</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Supplier Review=False</xsl:text>
<xsl:value-of select="$Linefeed"/>
<xsl:text>Comments=</xsl:text>
<xsl:value-of select="$Linefeed"/>
</Header>
<xsl:value-of select="$Linefeed"/>
<Header>
<xsl:text>Worker_ID|Date|Week_Start_Date|Cost_Center_Code|Task_Code|GL_Account_Code|Rate_Category_Code|UOM|Mon_Hrs|Tue_Hrs|Wed_Hrs|Thu_Hrs|Fri_Hrs|Sat_Hrs|Sun_Hrs</xsl:text>
<xsl:value-of select="$Linefeed"/>
</Header>
<xsl:for-each select="wd:Report_Entry">
<xsl:call-template name="Write_Rows"/>
</xsl:for-each>
</File>
</xsl:template>
<xsl:template name="Write_Rows">
<Record>
<Worker_ID>
<xsl:value-of select="wd:Worker_ID"/>
</Worker_ID>
<xsl:value-of select="$Delimiter"/>
<Date>
<!--<xsl:value-of select="concat(substring(wd:Date,6, 2),'/',substring(wd:Date, 9, 2),'/',substring(wd:Date, 1, 4))"/>-->
<!--Pass date in YYYY-MM-DD format to custom function and format the return value to 'MM/DD/YYYY'-->
<xsl:variable name="StartDateOfMonth" select="string(functx:first-day-of-month(xs:date(concat(substring(wd:Reported_Date, 1, 4),'-', substring(wd:Reported_Date,6, 2),'-',substring(wd:Reported_Date, 9, 2)))))"/>
<xsl:value-of select="concat(substring($StartDateOfMonth,6,2),'/',substring($StartDateOfMonth,9,2),'/',substring($StartDateOfMonth,1,4))"/>
</Date>
<xsl:value-of select="$Delimiter"/>
<Week_Start_Date>
<!--<xsl:variable name="WeekStartDate" select="my:thisMonday(xs:date('2019-06-01'))"/>-->
<!--Format the return value to 'MM/DD/YYYY'-->
<xsl:variable name="WeekStartDate" select="my:thisMonday(xs:date(wd:Reported_Date))"/>
<xsl:value-of select="concat(substring($WeekStartDate,6,2),'/',substring($WeekStartDate,9,2),'/',substring($WeekStartDate,1,4))"/>
</Week_Start_Date>
<xsl:value-of select="$Delimiter"/>
<Cost_Center_Code>
<xsl:value-of select="wd:Cost_Center_Code"/>
</Cost_Center_Code>
<xsl:value-of select="$Delimiter"/>
<Task_Code>
<xsl:value-of select="wd:Task_Code"/>
</Task_Code>
<xsl:value-of select="$Delimiter"/>
<GL_Account_Code>
<xsl:value-of select="wd:GL_Account_Code"/>
</GL_Account_Code>
<xsl:value-of select="$Delimiter"/>
<Rate_Category_Code>
<xsl:value-of select="wd:Rate_Category_Code"/>
</Rate_Category_Code>
<xsl:value-of select="$Delimiter"/>
<UOM>
<xsl:value-of select="wd:UOM"/>
</UOM>
<xsl:value-of select="$Delimiter"/>
<Hours_Worked>
<xsl:value-of select="wd:Hours_Worked"/>
</Hours_Worked>
<xsl:value-of select="$Delimiter"/>
<Overtime>
<xsl:value-of select="wd:Overtime"/>
</Overtime>
<xsl:value-of select="$Linefeed"/>
</Record>
</xsl:template>
</xsl:stylesheet>
``
I think part of the task is grouping with a composite key e.g. in XSLT 3:
<xsl:for-each-group select="wd:Report_Entry" composite="yes" group-by="wd:Worker_ID, wd:Rate_Category_Code, my:thisMonday(xs:date(wd:Reported_Date))">
<xsl:value-of select="current-grouping-key(), sum(current-group()/wd:Hours_Worked)" separator="|"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
That gives the existing data:
FNRAWK00001743|ST|2019-06-10|8
FNRAWK00001743|ST|2019-06-17|8
FNRAWK00001743|OT|2019-06-10|1
FNRAWK00001742|ST|2019-06-17|9
You will need to add logic for outputting empty lines for weeks for which there is no data.

XML to CSV (Generation of New Date with input Date)

I have a requirement in which I have to generate new date(YYYYMMDD) from the date which is coming in the record(YYYYMMDD) and below is logic for New date creation:
1.If 'MM - 1' = 0, Then make MM -'12', and Year value as YearFromActualDate - 1
2.DD is always '01'
3.If 'MM - 1' != 0, Then MM in output will be 'MM - 1' and Year will remain the same
I have mentioned the Expected Output which will give idea of above mentioned logic.
Please advise.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="text" encoding="utf-8" />
<xsl:param name="delim" select="','" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:value-of select="$quote" />
<xsl:text>Name</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:text>Date</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:text>NewDate</xsl:text>
<xsl:value-of select="$quote" />
<xsl:value-of select="$break" />
<xsl:apply-templates select="ID/ED/E1" />
</xsl:template>
<xsl:template match="E1">
<xsl:value-of select="$quote" />
<xsl:value-of select="name"/>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:value-of select="date"/>
<xsl:value-of select="$quote" />
<xsl:value-of select="$delim" />
<xsl:value-of select="$quote" />
<xsl:variable name="newdate" select="'01'" />
<xsl:variable name="inMonth" select="substring(date,5,2)" />
<xsl:variable name="inputYear" select="substring(date,1,4)" />
<xsl:choose>
<xsl:when test = "$inMonth='01'">
<xsl:variable name="calculatedMonth" select="12"/>
<xsl:value-of select="concat(xs:integer($inputYear) - 1,$calculatedMonth,$newdate)" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="inMonthlength" select="string-length(xs:string(xs:integer($inMonth) - 1))" />
<xsl:if test="xs:integer($inMonthlength) !=2">
<xsl:value-of select="concat($inputYear,concat(0,xs:integer($inMonth) - 1),$newdate)" />
</xsl:if>
<xsl:if test="xs:integer($inMonthlength) =2">
<xsl:value-of select="concat($inputYear,(xs:integer($inMonth) - 1),$newdate)" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$quote"/>
<xsl:if test="following-sibling::*">
<xsl:value-of select="$break" />
</xsl:if>
</xsl:template>
Input:
<ID>
<ED>
<E1>
<name>Eva</name>
<date>20190504</date> (Consider date as YYYYMMDD)
</E1>
<E1>
<name>Alan</name>
<date>20190101</date>
</E1>
</ED>
<ID>
Expected Output:
"Name","Date","NewDate"
"Eva","20190504","20190401"
"Alan","20190101","20181201"
If I've reverse-engineered your algorithm correctly, what you want is the first day of the previous month.
Logically the steps are:
Convert your date to an xs:date value
Take the first day of the current month
Subtract one month.
In practice it's simplest to combine (1) and (2) so you end up with
xs:date(replace($date, '(....)(..)(..)', '$1-$2-01')) - xs:yearMonthDuration('P1M')
and then you can format this date as YYYYMMDD using
format-date($date, '[Y0001][M01][D01]')

XSLT How to copy following elements inside the new group

I have this flat xml. i need to group the contents on h1/title and copy all following para's until next h1/title pattern if exists else add empty para.
Source XML:
<Element>
<h1>
<title>Name1</title>
</h1>
<h1>
<title>Name2</title>
</h1>
<para>Test1</para>
<para>Test2</para>
<h1>
<title>Name3</title>
</h1>
<para>Test3</para>
<para>Test4</para>
</Element>
I want the output like below.
<Element>
<group>
<h1>
<title>Name1</title>
</h1>
<para> </para>
</group>
<group>
<h1>
<title>Name2</title>
</h1>
<para>Test1</para>
<para>Test2</para>
</group>
<group>
<h1>
<title>Name3</title>
</h1>
<para>Test3</para>
<para>Test4</para>
</group>
</Element>
so far I have tried following template, it does not copy following para's.
<xsl:template match="h1">
<group>
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="h1">
<xsl:choose>
<xsl:when test="self::h1">
<group>
<xsl:apply-templates select="current-group()"/>
<xsl:apply-templates select="following-sibling::para[not(following::h1)]"/>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</group>
</xsl:template>
I would suggest to group the children of the Element element and then of course inside of the for-each-group you can simply check whether there is no second group item to add the empty para:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Element">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="h1">
<xsl:choose>
<xsl:when test="self::h1">
<group>
<xsl:apply-templates select="current-group()"/>
<xsl:if test="not(current-group()[2])">
<para/>
</xsl:if>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/naZXpX7.

How Can remove 'italic' element inside the 'pub-id', 'italic' element not allowed inside the 'pub-id'

Input:
<DOINUM>10.1080/14772019.2016.1274343</DOINUM>
Output:
No needed 'italic' element inside the 'pub-id' element
<pub-id pub-id-type="doi"><italic>10.1080/14772019.2016.1274343</italic></pub-id>
Xslt Code:
we creat 'pub-id' element below code
<pub-id pub-id-type="doi">
<xsl:apply-templates/>
</pub-id>
we create 'italic' element here, but 'italic' element not allowed inside the 'pub-id element, please give suggestion how can remove 'italic' element inside the 'pub-id' element.
<xsl:template match="IT" mode="demote">
<xsl:param name="content"/>
<xsl:apply-templates select=".." mode="demote">
<xsl:with-param name="content">
<xsl:choose>
<xsl:when test="normalize-space($content)">
<!-- providing 'italic' only if there's something
to put into italics -->
<italic>
<xsl:copy-of select="$content"/>
</italic>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$content"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:apply-templates>
</xsl:template>

XSLT Wrapping text with multiple tags

I have this span element with a class of autbib-pc-bold-italic
<span class="autbib-pc-bold-italic">autbib</span>
I want to create element tags base on the #class attribute value:
My output should be:
<autbib><pc><bold><italic>autbib</italic></bold></pc></autbib>
Here is my xsl templates:
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="count" select="count(tokenize(#class, '-'))"/>
<xsl:with-param name="class" select="tokenize(#class, '-')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="pbib.loop">
<xsl:param name="index" select="1" />
<xsl:param name="count" select="count(tokenize(#class, '-')) + 1"/>
<xsl:param name="class" select="tokenize(#class, '-')"/>
<xsl:element name="{$class[1]}">
<xsl:if test="not($index = $count)">
<xsl:element name="{$class[$index]}">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:element>
<xsl:if test="not($index = $count)">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
And have this output which is wrong:
<autbib>
<pc>autbib</pc>
<bold>autbib</bold>
<italic>autbib</italic>
</autbib>
I need to have this output:
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>
My problem is that I'm not sure where I should place xsl:apply-template so that tags wrap with each other.
Here is my suggestion:
<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:output indent="yes"/>
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:param name="classes" select="tokenize(#class, '-')"/>
<xsl:choose>
<xsl:when test="not($classes[1])">
<xsl:apply-templates/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$classes[1]}">
<xsl:apply-templates select=".">
<xsl:with-param name="classes" select="$classes[position() gt 1]"/>
</xsl:apply-templates>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With Saxon 9.4 that transforms the input
<span class="autbib-pc-bold-italic">autbib</span>
into the result
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>

Resources