Here is one XPath 2.0 expression that processes any mix of strings with quotes and without quotes in the desired way - in any order :
string-join( (for $str in tokenize(replace(., "(.*?)("".*?"")([^""]*)", "|$1|$2|$3|", "x"),"\|") return if(not(contains($str, """"))) then lower-case($str) else $str ), "")
For a comprehensive test, I evaluate the above expression in the following XML document:
<node>Some "Text""and Some" More "Text" XXX "Even More"</node>
The obtained, correct result is obtained :
some "Text""and Some" more "Text" xxx "Even More"
XSLT 2.0 validation :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/"> <xsl:sequence select= 'string-join( (for $str in tokenize(replace(., "(.*?)("".*?"")([^""]*)", "|$1|$2|$3|", "x"),"\|") return if(not(contains($str, """"))) then lower-case($str) else $str ), "") '/> </xsl:template> </xsl:stylesheet>
When this conversion is applied to the above XML document, the XPath expression is evaluated, and the result of this evaluation is copied to the output :
some "Text""and Some" more "Text" xxx "Even More"
Finally, the XSLT 2.0 solution is much easier to write and understand:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/*"> <xsl:analyze-string select="." regex='".*?"'> <xsl:non-matching-substring> <xsl:sequence select="lower-case(.)"/> </xsl:non-matching-substring> <xsl:matching-substring><xsl:sequence select="."/></xsl:matching-substring> </xsl:analyze-string> </xsl:template> </xsl:stylesheet>