(xslt 1.0) How to replace the space with some string from all the text values in xml?

后端 未结 3 1958
北荒
北荒 2021-01-14 20:20

EDIT: [it started with character replacement and I ended up with discovering string replacements with help of Dimitre Novatchev and Rol

相关标签:
3条回答
  • 2021-01-14 21:00

    The solution to the "prety-printed xml" is not really a solution.

    Imagine having a document like this:

    <a>
     <b>
      <c>O M G</c>
      <d>D I Y</d>
     </b>
    </a>
    

    The output from the currently accepted solution (after wrapping it in an <xsl:stylesheet> and adding the identity rule is:

    <a>
    %20<b>
    %20%20<c>O$M$G</c>
    %20%20<d>D$I$Y</d>
    %20</b>
    </a>
    

    Now, why doesn't the proposed workaround save the situation? As we see from the above example, an element can have more than one child element that has text nodes...

    What is the real solution?

    The creators of XSLT have thought about this problem. Using the right terminology, we want all insignificant white-space-only text nodes to be ignored by the XSLT processor, as if they were not part of the document tree at all. This is achieved by the <xsl:strip-space> instruction.

    Just add this at a global level (as a child of <xsl:stylesheet> and, for readability, before any templates):

     <xsl:strip-space elements="*"/>
    

    and now you really have a working solution.

    0 讨论(0)
  • 2021-01-14 21:09

    Check out the XPath translate function: http://www.w3.org/TR/xpath/#function-translate

    <xsl:template match="text()">
        <xsl:value-of select="translate(., ' ', '$')"/>
    </xsl:template>
    

    If it's not a single character, but a string you have to replace, it takes considerably more effort, and you need a template to recursively replace the string:

    <xsl:template match="text()[not(../*)]">
        <xsl:call-template name="replace">
            <xsl:with-param name="text" select="."/>
            <xsl:with-param name="search" select="' '"/>
            <xsl:with-param name="replace" select="'%20'"/>
        </xsl:call-template>
    </xsl:template>
    
    <xsl:template name="replace">
        <xsl:param name="text"/>
        <xsl:param name="search"/>
        <xsl:param name="replace"/>
        <xsl:choose>
            <xsl:when test="contains($text, $search)">
                <xsl:variable name="replace-next">
                    <xsl:call-template name="replace">
                        <xsl:with-param name="text" select="substring-after($text, $search)"/>
                        <xsl:with-param name="search" select="$search"/>
                        <xsl:with-param name="replace" select="$replace"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:value-of 
                    select="
                        concat(
                            substring-before($text, $search)
                        ,   $replace
                        ,   $replace-next
                        )
                    "
                />
            </xsl:when>
            <xsl:otherwise><xsl:value-of select="$text"/></xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    

    Edit:changed match="text()" to match="text()[not(../*)]", so that the input xml need not be a kind of "pretty print XML" .. (so as to remove unwanted replacements of space with "%20" string in such xml file)

    0 讨论(0)
  • 2021-01-14 21:15

    As per the wish of Roland, here is a tail-recursive solution:

     <xsl:template name="replace">
      <xsl:param name="ptext"/>
      <xsl:param name="ppattern"/>
      <xsl:param name="preplacement"/>
    
      <xsl:choose>
         <xsl:when test="not(contains($ptext, $ppattern))">
          <xsl:value-of select="$ptext"/>
         </xsl:when>
         <xsl:otherwise>
           <xsl:value-of select="substring-before($ptext, $ppattern)"/>
           <xsl:value-of select="$preplacement"/>
           <xsl:call-template name="replace">
             <xsl:with-param name="ptext"
               select="substring-after($ptext, $ppattern)"/>
             <xsl:with-param name="ppattern" select="$ppattern"/>
             <xsl:with-param name="preplacement" select="$preplacement"/>
           </xsl:call-template>
         </xsl:otherwise>
      </xsl:choose>
     </xsl:template>
    

    Note that the recursive call is the last instruction in the template -- this is what makes it tail-recursive. The property of being tail-recursive allows a smart XSLT processor (such as Saxon or .NET XslCompiledTransform) to optimize the code, replacing the recursion with simple iteration.

    Such code will not end up with a stack-overflow exception even when the "nesting" of calls is millions, whereas non-tail-recursive (and recursive) code typically raises this stack-overflow at a depth of about 1000 nested calls (this really depends on the amount of the available memory).

    What if the XSLT processor is not "smart enough"? Is there another technique to avoid deep-level recursive calls stack overflow, that works with every XSLT processor?

    Ask me in a separate question and I might tell you :)

    0 讨论(0)
提交回复
热议问题