Subject: [xsl] Functional trim() (Was: Re: text::trim()) From: Dimitre Novatchev <dnovatchev@xxxxxxxxx> Date: Thu, 27 Dec 2001 12:58:46 -0800 (PST) |
> Does anyone know if there is a function such as trim() that can be > applied on a text node, i.e., remove all leading and trailing white-space > ('\t', '\n', '\r', ' ', '\f'), given the text node can contain non > white-space characters. In Haskell one solution is the following: > trim :: [Char] -> [Char] > trim = applyTwice (reverse . trim1) > where trim1 = dropWhile (`elem` delim) > delim = [' ', '\t', '\n', '\r'] > applyTwice f = f . f This means that we have two functions: - a simple function "trim1" that "eats out" the starting group of predefined whitespace characters from a string. We could call this function "trimLeft". It uses the function "dropWhile", which drops from a list all starting elements satisfying a predicate argument. - the function "reverse", the XSLT implementation of which can be found at: http://aspn.activestate.com/ASPN/Mail/Message/XSL-List/926231 What is described above is quite simple -- we trim-left the string, then reverse it, then trim-left the reversed string (at this point all the trimming is done), then finally reverse the reversed and trimmed string. For brevity the function composition operator "." is used: (f . g) x = f(g(x)) We can implement exactly the same algorithm in XSLT: trim.xsl: -------- <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:myTrimDropController="f:myTrimDropController" xmlns:myTrim1="f:myTrim1" xmlns:myReverse="f:myReverse" exclude-result-prefixes="xsl myTrimDropController myTrim1 myReverse" > <xsl:import href="str-dropWhile.xsl"/> <xsl:import href="compose-flist.xsl"/> <xsl:import href="reverse.xsl"/> <myTrimDropController:myTrimDropController/> <xsl:template name="trim"> <xsl:param name="pStr"/> <xsl:variable name="vrtfParam"> <myReverse:myReverse/> <myTrim1:myTrim1/> <myReverse:myReverse/> <myTrim1:myTrim1/> </xsl:variable> <xsl:call-template name="compose-flist"> <xsl:with-param name="pFunList" select="msxsl:node-set($vrtfParam)/*"/> <xsl:with-param name="pArg1" select="$pStr"/> </xsl:call-template> </xsl:template> <xsl:template name="trim1" match="myTrim1:*"> <xsl:param name="pArg1"/> <xsl:variable name="vTab" select="'	'"/> <xsl:variable name="vNL" select="' '"/> <xsl:variable name="vCR" select="' '"/> <xsl:variable name="vWhitespace" select="concat(' ', $vTab, $vNL, $vCR)"/> <xsl:variable name="vFunController" select="document('')/*/myTrimDropController:*[1]"/> <xsl:call-template name="str-dropWhile"> <xsl:with-param name="pStr" select="$pArg1"/> <xsl:with-param name="pController" select="$vFunController"/> <xsl:with-param name="pContollerParam" select="$vWhitespace"/> </xsl:call-template> </xsl:template> <xsl:template match="myTrimDropController:*"> <xsl:param name="pChar"/> <xsl:param name="pParams"/> <xsl:if test="contains($pParams, $pChar)">1</xsl:if> </xsl:template> <xsl:template name="myReverse" match="myReverse:*"> <xsl:param name="pArg1"/> <xsl:call-template name="strReverse"> <xsl:with-param name="pStr" select="$pArg1"/> </xsl:call-template> </xsl:template> </xsl:stylesheet> The "trim" template is short and simple: <xsl:template name="trim"> <xsl:param name="pStr"/> <xsl:variable name="vrtfParam"> <myReverse:myReverse/> <myTrim1:myTrim1/> <myReverse:myReverse/> <myTrim1:myTrim1/> </xsl:variable> <xsl:call-template name="compose-flist"> <xsl:with-param name="pFunList" select="msxsl:node-set($vrtfParam)/*"/> <xsl:with-param name="pArg1" select="$pStr"/> </xsl:call-template> </xsl:template> It just calls the "compose-flist" template -- a generic template, which is passed a list of template references "pFunList" and an initial argument "pArg1". It performs the functional composition of the functions referred to by the template references in reverse order. In this specific case it will instantiate the "trim1" template passing to it the string contained in "pArg1", then will pass the result to the "reverse" template, then will pass the result to the "trim1" template, then finally will pass the result to the "reverse" template. If we have the following source xml document: <someText> This is some text </someText> and apply the following transformation to it: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="trim.xsl"/> <xsl:output method="text"/> <xsl:template match="/"> '<xsl:call-template name="trim"> <xsl:with-param name="pStr" select="string(/*)"/> </xsl:call-template>' </xsl:template> </xsl:stylesheet> the result is: 'This is some text' This is obviously the correct result -- the string is trimmed both from left and from right, while white space within the string is left intact as it should be. The "trim1" template is also very simple: <xsl:template name="trim1" match="myTrim1:*"> <xsl:param name="pArg1"/> <xsl:variable name="vTab" select="'	'"/> <xsl:variable name="vNL" select="' '"/> <xsl:variable name="vCR" select="' '"/> <xsl:variable name="vWhitespace" select="concat(' ', $vTab, $vNL, $vCR)"/> <xsl:variable name="vFunController" select="document('')/*/myTrimDropController:*[1]"/> <xsl:call-template name="str-dropWhile"> <xsl:with-param name="pStr" select="$pArg1"/> <xsl:with-param name="pController" select="$vFunController"/> <xsl:with-param name="pContollerParam" select="$vWhitespace"/> </xsl:call-template> </xsl:template> It has a single parameter -- the string to be left-trimmed. It just calls the standard and generic "str-dropWhile" template to do the processing, passing to it in "pArg1" the string, from which some starting characters must be dropped, a function "pController" (template reference to a template) that will signal if any individual character passed to it is to be dropped, and parameter "pContollerParam" to be passed to this controller. The code of the "drop controller" is straightforward: <xsl:template match="myTrimDropController:*"> <xsl:param name="pChar"/> <xsl:param name="pParams"/> <xsl:if test="contains($pParams, $pChar)">1</xsl:if> </xsl:template> It just tests if the current "pChar" character belongs to the group of characters "pParams" and returns 1 if this is so, otherwise it doesn't return anything. In the last case this will signal to "str-dropWhile" that all the necessary starting characters have been dropped and the remainder of the string will be returned. Finally, let me present the code of the two templates used by "trim" and "trim1" -- "compose-flist" and "str-dropWhile". As already described, "compose-flist" performs the functional composition of a list of functions: (f1 . f2 . ... fn)(x) = f1(f2(...(fn(x))...)) This can be defined in Haskell as follows: multCompose :: [a -> a] -> a -> a multCompose xs y = foldr ($) y xs where "$" is the function application operator, xs is a list of functions the composition of which must be produced, and y is the the argument on which the functional composition will be applied. We could use an XSLT implementation of foldr in implementng compose-flist, however this time I prefer to produce directly the code to make it more understandable: compose-flist.xsl: ----------------- <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"> <xsl:template name="compose-flist"> <xsl:param name="pFunList" select="/.."/> <xsl:param name="pArg1"/> <xsl:choose> <xsl:when test="not($pFunList)"> <xsl:copy-of select="$pArg1"/> </xsl:when> <xsl:otherwise> <xsl:variable name="vrtfFunRest"> <xsl:call-template name="compose-flist"> <xsl:with-param name="pFunList" select="$pFunList[position() > 1]"/> <xsl:with-param name="pArg1" select="$pArg1"/> </xsl:call-template> </xsl:variable> <xsl:apply-templates select="$pFunList[1]"> <xsl:with-param name="pArg1" select="msxsl:node-set($vrtfFunRest)/node()"/> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> It is worth to note that functional composition is one of the most powerful tools with which we can create new functions dynamically on the fly. And finally here's the code of "str-dropWhile": str-dropWhile.xsl: ----------------- <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="xsl msxsl" > <xsl:template name="str-dropWhile"> <xsl:param name="pStr" select="''"/> <xsl:param name="pController" select="/.."/> <xsl:param name="pContollerParam" select="/.."/> <xsl:if test="not($pController)"> <xsl:message terminate="yes"> [str-dropWhile]Error: pController not specified. </xsl:message> </xsl:if> <xsl:if test="$pStr"> <xsl:variable name="vDrop"> <xsl:apply-templates select="$pController"> <xsl:with-param name="pChar" select="substring($pStr, 1, 1)"/> <xsl:with-param name="pParams" select="$pContollerParam"/> </xsl:apply-templates> </xsl:variable> <xsl:choose> <xsl:when test="string($vDrop)"> <xsl:call-template name="str-dropWhile"> <xsl:with-param name="pStr" select="substring($pStr, 2)" /> <xsl:with-param name="pController" select="$pController"/> <xsl:with-param name="pContollerParam" select="$pContollerParam"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$pStr"/> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> </xsl:stylesheet> On every character this function calls the provided controller "pController" passing to it the provided parameters "pContollerParam" and only continues processing if the controller has output something. In conclusion, this solution is yet another example how using the functional programming style and the FP standard library functions in XSLT can help to produce solutions to a variety of problems by simply combining and reusing existing functions. Cheers, Dimitre Novatchev. __________________________________________________ Do You Yahoo!? Send your FREE holiday greetings online! http://greetings.yahoo.com XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
[xsl] Re: Wishes for XSL revisions , Dimitre Novatchev | Thread | Re: [xsl] Functional trim(), Adriano Rodrigues Fe |
RE: Assignment no, dynamic scoping , Oleg Tkachenko | Date | RE: [xsl] Wishes for XSL revisions , Wendell Piez |
Month |