I am extracting data from XML using XSLT. I want to see data in xml-editor.How to wrap text to fit window in XSLT?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match='/'>
<wawes>
<xsl:for-each select="//VARIABLE">
<xsl:sort select="@ID" order="descending"/>
<wave>
<id>
<xsl:value-of select="@ID" ></xsl:value-of>
</id>
<NAME>
<xsl:value-of select="NAME"/>
</NAME>
</wave>
</xsl:for-each>
</wawes>
</xsl:template>
</xsl:stylesheet>
My other answer provides a solution using XSLT-2
, whereas the initial scope of this question was XSLT-1
.
This premise is based on the idea of repeatedly reiterating over the target string, slicing off variably sized, sub 50 segments and placing each one on its own line, and continuing this function until there is no more text available.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:user="http://www.altova.com/MapForce/UDF/user" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="user xs">
<xsl:template name="user:FindLastSpace">
<xsl:param name="text" select="/.."/>
<xsl:variable name="var1_resultof_string_length" select="string-length($text)"/>
<xsl:variable name="var4_result">
<xsl:choose>
<xsl:when test="string(('0' = $var1_resultof_string_length)) != 'false'">
<xsl:value-of select="'0'"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="var2_resultof_substring" select="substring($text, $var1_resultof_string_length)"/>
<xsl:variable name="var3_result">
<xsl:value-of select="($var2_resultof_substring = ' ')"/>
<xsl:value-of select="($var2_resultof_substring = ',')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="string(boolean(translate(normalize-space($var3_result), 'false0 ', ''))) != 'false'">
<xsl:value-of select="$var1_resultof_string_length"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="substring($text, '1', ($var1_resultof_string_length - '1'))"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="floor($var4_result)"/>
</xsl:template>
<xsl:template name="user:CrLf">
<xsl:value-of select="'
'"/>
</xsl:template>
<xsl:template name="user:FormLines">
<xsl:param name="remainingText" select="/.."/>
<xsl:param name="length" select="/.."/>
<xsl:variable name="var1_resultof_substring" select="substring($remainingText, '1', $length)"/>
<xsl:variable name="var2_resultof_FindLastSpace">
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="var4_result">
<xsl:choose>
<xsl:when test="string(($var2_resultof_FindLastSpace = '0')) != 'false'">
<xsl:variable name="var3_resultof_string_length" select="string-length($remainingText)"/>
<xsl:value-of select="$var3_resultof_string_length"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="var5_resultof_CrLf">
<xsl:call-template name="user:CrLf"/>
</xsl:variable>
<xsl:variable name="var6_resultof_FindLastSpace">
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="var8_result">
<xsl:choose>
<xsl:when test="string(($var6_resultof_FindLastSpace = '0')) != 'false'">
<xsl:variable name="var7_resultof_string_length" select="string-length($remainingText)"/>
<xsl:value-of select="$var7_resultof_string_length"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="var11_result">
<xsl:choose>
<xsl:when test="string((substring($remainingText, ($var8_result + '1')) = '')) != 'false'">
<xsl:value-of select="''"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="var9_resultof_FindLastSpace">
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="var10_result">
<xsl:choose>
<xsl:when test="string(($var9_resultof_FindLastSpace = '0')) != 'false'">
<xsl:value-of select="string-length($remainingText)"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="user:FindLastSpace">
<xsl:with-param name="text" select="$var1_resultof_substring"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="user:FormLines">
<xsl:with-param name="remainingText" select="substring($remainingText, ($var10_result + '1'))"/>
<xsl:with-param name="length" select="$length"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat(concat(substring($remainingText, '1', $var4_result), $var5_resultof_CrLf), $var11_result)"/>
</xsl:template>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xformResult>
<xsl:call-template name="user:FormLines">
<xsl:with-param name="remainingText" select="'Google HQ, 345 Spear St, San Francisco, Foo, Bar, Baz, 012310123'"/>
<xsl:with-param name="length" select="floor('50')"/>
</xsl:call-template>
</xformResult>
</xsl:template>
</xsl:stylesheet>
Works perfectly in many tests. Please forgive its craziness, I threw it together in MapForce
Wrapping text in order to fit the screen size or resolution is usually not related to XSLT. XSLT is only concerned with transforming XML, not with the presentation and layout of XML data.
And if you think about it that's not too bad: the same XML must be equally accessible to all types of screen sizes, resolutions - and all types of applications, including XML editors. It would be presumptuous to fit XML to your unique combination of these three factors (what about window size?).
Instead, as pointed out by @rene, wrapping text comes as a feature with virtually every text editor. For example, there is CTRL + W for this in UltraEdit.
N.b. it is not technically impossible to enforce wrapping text in XSLT. You can introduce newline characters in the text content of your elements. But
- Their rendering depends on your application. An XML editor is likely not to render them at all.
- It's still unclear where exactly they should be introduced, given that at "transform time" you have no way of knowing what screens, windows and applications are going to access the XML data
I have had reasonable success using Regular Expressions to break up a string using XSLT-2
's replace
function (replacing with $1\n
). I know you are concerned with XSLT-1
, but from what I have found, regular expressions are the only way to do this without iterative parsing.
Initially, I found this question and answer, but I couldn't get it to work within the Regular Expressions subset allowed with XSLTs, mainly hobbled by the lack of positive lookahead.
Although XSLT is about XML manipulation, my line of work has proven that that is not the only use case as we use XSLT to perform mass data transformations for system and client interoperability, not only on XML data, but plaintext and formatted text (i.e. CSV) data as well.
One such transformation required the implosion of a set of address elements:
<Address>
<Name>Google HQ</Name>
<Line1>345 Spear St</Line1>
<Line2></Line2>
<Line3></Line3>
<Line4></Line4>
<City>San Francisco</City>
<PostCode>94105</PostCode>
<County>CA</County>
<Country>United States</Country>
<CountryCode>USA</CountryCode>
</Address>
into a text block that would resemble:
Google HQ,
345 Spear St,
San Francisco,
94105,
CA,
USA
however, the field that this information would be put into was limited to only 4 lines, and each line being 50 characters. To conserve space, I put the PostCode
, County
, and CountryCode
on a single line and appended it separately, so I had 3 lines of 50 characters instead. I then took the remaining fields, and CSV'd them together (unless one was blank), such that it looked like:
Google HQ, 345 Spear St, San Francisco
Looking at the question and answer I linked to above, I got the regular expression (swapped 16 for 50 in the lengths):
(?:((?>.{1,50}(?:(?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|.{1,50})(?:\r?\n)?|(?:\r?\n|$))
however, it would not work in XSLT.
For starters, it didn't recognise (?>
(atomic groups), or (?=
(positive lookaheads, but after swapping them with anonymous groups, it worked.
But after that, I got errors about it matching a zero-length string. That was being caused by the last occurrences of |$
, which was causing it to match to the end of line, but without that, it finally worked. It was not as neat as I had hoped, but it did compress the single line of text into a set of lines that were narrower than 50 characters at each point. The final regular expression was:
(?:((?:.{1,50}(?:(?:[^\S\r\n])[^\S\r\n]?|(?:\r?\n)|$|[^\S\r\n]))|.{1,50})(?:\r?\n)?|(?:\r?\n))
Now, in my case which doesn't apply to you, I had a line restriction too, but I just discarded the remaining lines.
Hope that's of some help to you