File formating with XSLT

O

olivier.scalbert

Hello,
From the source file, I need to create with XSLT a result file with
the following constraints:

source:
<Source>
<Item>AAA</Item>
<Item>BBBBBBBBBBB</Item>
<Item>CCCCCCCCC</Item>
<Item>DDDDDDDDDDDDDDD</Item>
<Item>EEEEE</Item>
<Item>FF</Item>
<Item>G</Item>
</Source>

result: (text file with line length = 16, with spaces)
AAA BBBBBBBBBBB
CCCCCCCCC
DDDDDDDDDDDDDDD
EEEEE FF G

Each line of the output file is composed of Item texts separated by
one space.
If by adding a new item text on the line we have more than 16
characters then pad the current line with spaces and copy the item
text to a new line.

I do not know if it is possible to do that in XSLT.
Thanks for your help.

Olivier
 
D

Dimitre Novatchev

Use the FXSL stylesheet file:

strSplit-to-Lines.xsl

And modify it appropriately for your case.

This transformation (just modified the above stylesheet, calling the
template with lineLength=16):

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:str-split2lines-func="f:str-split2lines-func"
exclude-result-prefixes="f str-split2lines-func"
<xsl:import href="str-foldl.xsl"/>

<!-- to be applied on text.xml -->

<str-split2lines-func:str-split2lines-func/>

<xsl:eek:utput indent="yes" omit-xml-declaration="yes"/>

<xsl:template match="/">
<xsl:call-template name="str-split-to-lines">
<xsl:with-param name="pStr"
select="concat(normalize-space(/), ' ')"/>
<xsl:with-param name="pLineLength" select="16"/>
<xsl:with-param name="pDelimiters" select="'
'"/>
</xsl:call-template>
</xsl:template>

<xsl:template name="str-split-to-lines">
<xsl:param name="pStr"/>
<xsl:param name="pLineLength" select="60"/>
<xsl:param name="pDelimiters" select="'
'"/>

<xsl:variable name="vsplit2linesFun"
select="document('')/*/str-split2lines-func:*[1]"/>

<xsl:variable name="vrtfParams">
<delimiters><xsl:value-of select="$pDelimiters"/></delimiters>
<lineLength><xsl:copy-of select="$pLineLength"/></lineLength>
</xsl:variable>

<xsl:variable name="vResult">
<xsl:call-template name="str-foldl">
<xsl:with-param name="pFunc" select="$vsplit2linesFun"/>
<xsl:with-param name="pStr" select="$pStr"/>
<xsl:with-param name="pA0" select="$vrtfParams"/>
</xsl:call-template>
</xsl:variable>

<xsl:for-each select="$vResult/line">
<xsl:for-each select="word">
<xsl:value-of select="concat(., ' ')"/>
</xsl:for-each>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>

<xsl:template match="str-split2lines-func:*" mode="f:FXSL">
<xsl:param name="arg1" select="/.."/>
<xsl:param name="arg2"/>

<xsl:copy-of select="$arg1/*[position() &lt; 3]"/>
<xsl:copy-of select="$arg1/line[position() != last()]"/>

<xsl:choose>
<xsl:when test="contains($arg1/*[1], $arg2)">
<xsl:if test="string($arg1/word)">
<xsl:call-template name="fillLine">
<xsl:with-param name="pLine" select="$arg1/line[last()]"/>
<xsl:with-param name="pWord" select="$arg1/word"/>
<xsl:with-param name="pLineLength" select="$arg1/*[2]"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:eek:therwise>
<xsl:copy-of select="$arg1/line[last()]"/>
<word><xsl:value-of select="concat($arg1/word, $arg2)"/></word>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

<!-- Test if the new word fits into the last line -->
<xsl:template name="fillLine">
<xsl:param name="pLine" select="/.."/>
<xsl:param name="pWord" select="/.."/>
<xsl:param name="pLineLength" />

<xsl:variable name="vnWordsInLine" select="count($pLine/word)"/>
<xsl:variable name="vLineLength" select="string-length($pLine) +
$vnWordsInLine"/>
<xsl:choose>
<xsl:when test="not($vLineLength + string-length($pWord) >
$pLineLength)">
<line>
<xsl:copy-of select="$pLine/*"/>
<xsl:copy-of select="$pWord"/>
</line>
</xsl:when>
<xsl:eek:therwise>
<xsl:copy-of select="$pLine"/>
<line>
<xsl:copy-of select="$pWord"/>
</line>
<word/>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

Produces the wanted results (the lines are truncated but padding each line
to 16 chars width is left as an exercise to the reader :eek:) )

AAA BBBBBBBBBBB
CCCCCCCCC
DDDDDDDDDDDDDDD
EEEEE FF G

Cheers,
Dimitre Novatchev.
 
P

p.lepin

<Source>
<Item>AAA</Item>
<Item>BBBBBBBBBBB</Item>
<Item>CCCCCCCCC</Item>
<Item>DDDDDDDDDDDDDDD</Item>
<Item>EEEEE</Item>
<Item>FF</Item>
<Item>G</Item>
</Source>

result: (text file with line length = 16, with spaces)
AAA BBBBBBBBBBB
CCCCCCCCC
DDDDDDDDDDDDDDD
EEEEE FF G

Each line of the output file is composed of Item texts
separated by one space.
If by adding a new item text on the line we have more
than 16 characters then pad the current line with spaces
and copy the item text to a new line.

I do not know if it is possible to do that in XSLT.

It certainly is possible, but fairly... perverse. XSLT is
not very good at stuff like that.

Just for the heck of it:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput method="text"/>
<xsl:param name="max-length" select="16"/>
<xsl:param name="pad-with" select="'.'"/>
<xsl:template match="Source">
<xsl:apply-templates select="Item[1]"/>
</xsl:template>
<xsl:template match="Item">
<xsl:param name="cur-position" select="0"/>
<xsl:variable name="cur-length"
select="string-length(.)"/>
<xsl:variable name="new-position">
<xsl:choose>
<xsl:when test="0 = $cur-position">
<xsl:value-of
select="$cur-position + $cur-length"/>
</xsl:when>
<xsl:eek:therwise>
<xsl:value-of
select="$cur-position + $cur-length + 1"/>
</xsl:eek:therwise>
</xsl:choose>
</xsl:variable>
<xsl:if
test=
"
($max-length &lt; $cur-length) or
(0 = $cur-length)
">
<xsl:message
terminate="yes">Invalid entity.</xsl:message>
</xsl:if>
<xsl:choose>
<xsl:when test="$new-position &gt; $max-length">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$cur-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
<xsl:value-of select="."/>
<xsl:apply-templates
select="following-sibling::Item[1]">
<xsl:with-param name="cur-position"
select="$cur-length"/>
</xsl:apply-templates>
<xsl:if test="not(following-sibling::Item[1])">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$cur-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:when>
<xsl:eek:therwise>
<xsl:if test="0 &lt; $cur-position">
<xsl:value-of select="$pad-with"/>
</xsl:if>
<xsl:value-of select="."/>
<xsl:apply-templates
select="following-sibling::Item[1]">
<xsl:with-param name="cur-position"
select="$new-position"/>
</xsl:apply-templates>
<xsl:if test="not(following-sibling::Item[1])">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$new-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>
<xsl:template name="pad-space">
<xsl:param name="position"/>
<xsl:variable name="left"
select="$max-length - $position"/>
<xsl:if test="0 &lt; $left">
<xsl:value-of select="$pad-with"/>
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$position + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Not tested on anything but the sample document provided by
the OP. Note that on large documents it is likely to barf
all over the stack and die horrible death. So I would
strongly advise against deploying it in production
environments without some serious tinkering beforehand
(preferrably the tinkering would involve rewriting the
transformation in a language more suitable for the task).
 
O

olivier.scalbert

Thank you very much for your help.
As the input source file is also generated by an XSLT transformation,
do you think I can hook your function inside this transformation, and
avoid the intermediary file ?

Olivier
 
D

Dimitre Novatchev

Thank you very much for your help.
As the input source file is also generated by an XSLT transformation,
do you think I can hook your function inside this transformation, and
avoid the intermediary file ?

Olivier

Yes, this is a standard practice.

In XSLT 1.0 use the xxx:node-set() extension to convert the RTF results of
the first transformation to a regular tree.

In XSLT 2.0 this is not necessary.


Cheers,
Dimitre Novatchev
 
D

Dimitre Novatchev

Note that on large documents it is likely to barf
all over the stack and die horrible death. So I would
strongly advise against deploying it in production


Not necessarily. One can use a DVC (Divide and Conquer) approach, which
requires as little as Log2(N) maximum stack depth.


Cheers,
Dimitre Novatchev

<Source>
<Item>AAA</Item>
<Item>BBBBBBBBBBB</Item>
<Item>CCCCCCCCC</Item>
<Item>DDDDDDDDDDDDDDD</Item>
<Item>EEEEE</Item>
<Item>FF</Item>
<Item>G</Item>
</Source>

result: (text file with line length = 16, with spaces)
AAA BBBBBBBBBBB
CCCCCCCCC
DDDDDDDDDDDDDDD
EEEEE FF G

Each line of the output file is composed of Item texts
separated by one space.
If by adding a new item text on the line we have more
than 16 characters then pad the current line with spaces
and copy the item text to a new line.

I do not know if it is possible to do that in XSLT.

It certainly is possible, but fairly... perverse. XSLT is
not very good at stuff like that.

Just for the heck of it:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput method="text"/>
<xsl:param name="max-length" select="16"/>
<xsl:param name="pad-with" select="'.'"/>
<xsl:template match="Source">
<xsl:apply-templates select="Item[1]"/>
</xsl:template>
<xsl:template match="Item">
<xsl:param name="cur-position" select="0"/>
<xsl:variable name="cur-length"
select="string-length(.)"/>
<xsl:variable name="new-position">
<xsl:choose>
<xsl:when test="0 = $cur-position">
<xsl:value-of
select="$cur-position + $cur-length"/>
</xsl:when>
<xsl:eek:therwise>
<xsl:value-of
select="$cur-position + $cur-length + 1"/>
</xsl:eek:therwise>
</xsl:choose>
</xsl:variable>
<xsl:if
test=
"
($max-length &lt; $cur-length) or
(0 = $cur-length)
">
<xsl:message
terminate="yes">Invalid entity.</xsl:message>
</xsl:if>
<xsl:choose>
<xsl:when test="$new-position &gt; $max-length">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$cur-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
<xsl:value-of select="."/>
<xsl:apply-templates
select="following-sibling::Item[1]">
<xsl:with-param name="cur-position"
select="$cur-length"/>
</xsl:apply-templates>
<xsl:if test="not(following-sibling::Item[1])">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$cur-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:when>
<xsl:eek:therwise>
<xsl:if test="0 &lt; $cur-position">
<xsl:value-of select="$pad-with"/>
</xsl:if>
<xsl:value-of select="."/>
<xsl:apply-templates
select="following-sibling::Item[1]">
<xsl:with-param name="cur-position"
select="$new-position"/>
</xsl:apply-templates>
<xsl:if test="not(following-sibling::Item[1])">
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$new-position"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>
<xsl:template name="pad-space">
<xsl:param name="position"/>
<xsl:variable name="left"
select="$max-length - $position"/>
<xsl:if test="0 &lt; $left">
<xsl:value-of select="$pad-with"/>
<xsl:call-template name="pad-space">
<xsl:with-param name="position"
select="$position + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Not tested on anything but the sample document provided by
the OP. Note that on large documents it is likely to barf
all over the stack and die horrible death. So I would
strongly advise against deploying it in production
environments without some serious tinkering beforehand
(preferrably the tinkering would involve rewriting the
transformation in a language more suitable for the task).
 
P

p.lepin

Not necessarily.

I was talking specifically about the implementation I
provided.
One can use a DVC (Divide and Conquer) approach, which
requires as little as Log2(N) maximum stack depth.

Wouldn't there be certain problems with implementing DVC
for this task? The position of every Item depends on all
previous Items after all. Tail recursion would seem like a
better approach to me, but I not sure there are any XSLT
processors to date that can optimize it away. (Are there,
by the way?)
 
D

Dimitre Novatchev

Wouldn't there be certain problems with implementing DVC
for this task? The position of every Item depends on all
previous Items after all.

No, there's usually no problem with this. Look how the DVC version of
foldl() is implemented in FXSL:


<xsl:stylesheet version="2.0"
xmlns:f="http://fxsl.sf.net/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="f">

<xsl:import href="func-apply.xsl"/>
<xsl:import href="func-drop.xsl"/>

<xsl:function name="f:foldl">
<xsl:param name="pFunc" as="element()"/>
<xsl:param name="pA0"/>
<xsl:param name="pList" as="item()*"/>

<xsl:sequence select=
"if(empty($pList))
then
$pA0
else
for $vcntList in count($pList) return
if($vcntList = 1)
then
f:apply($pFunc, $pA0, $pList[1])
else
for $vHalfLen in ($vcntList idiv 2) return
f:foldl($pFunc,
f:foldl($pFunc, $pA0, $pList[position() le
$vHalfLen]),
f:drop($vHalfLen, $pList)
)"/>

</xsl:function>

</xsl:stylesheet>


Something similar is done in FXSL 1.x (For XSLT 1.0).
Tail recursion would seem like a
better approach to me, but I not sure there are any XSLT
processors to date that can optimize it away.

Some do (Net XslCompiledTransform, Saxon6.5, JD are all very good at
optimizing TR), some don't, and all 3 XSLT 2.0 processors have some
difficulties -- see more on this here:

http://dnovatchev.spaces.live.com/Blog/cns!44B0A32C2CCF7488!345.entry

Cheers,
Dimitre Novatchev
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,990
Messages
2,570,211
Members
46,796
Latest member
SteveBreed

Latest Threads

Top