Can this preg_replace_callback method be done in xPath or XQuery?
The script below takes the $myContent block and does the doReplace() function on the $myKeyword value. The problem I have is that it does not know if the replacement text is already inside a tag or not.
I need to modify the doReplace() function so that it does not touch any text inside of or as attributes of the named tags (h1, h2, h3, i, u, b, strong, em, img).
I think it would be better to convert this over to an xPath method and looking for suggestions on how it might be done with xPath. So the question is: "How would you convert this to xP开发者_开发百科ath"?
Note: The count variable in the replace function is there because I'm only replacing on the first three appearances of the keyword. First time it appears it gets bolded, next time it gets italicized and the third time it appears, it gets underlined.
$myKeyword = "test keyword";
$myContent = "My content contains the "test keyword".
Don't do the replace if the test keyword is inside:
h1, h2, h3, i, u, b, strong, em, tags.
<h1>This test keyword would not be replaced</h1>";
$myContent = preg_replace_callback("/\b($mykeyword)\b/i","doReplace", $myContent);
function doReplace($matches)
{
static $count = 0;
switch($count++) {
case 0: return ' <b>'.trim($matches[1]).'</b>';
case 1: return ' <em>'.trim($matches[1]).'</em>';
case 2: return ' <u>'.trim($matches[1]).'</u>';
default: return $matches[1];
}
}
You can't in XPath 1.0 nor 2.0, because you need recursion to express this algorithm. You could use an extension function, of course.
This XQuery:
declare variable $match as xs:string external;
declare variable $replace as xs:string external;
declare variable $preserve as xs:string external;
declare variable $vPreserve := tokenize($preserve,',');
declare function local:copy-replace($element as element()) {
element {node-name($element)}
{$element/@*,
for $child in $element/node()
return if ($child instance of element())
then local:copy-replace($child)
else if ($child instance of text() and
not(name($element)=$vPreserve))
then replace($child,$match,$replace)
else $child
}
};
local:copy-replace(/*)
With this input:
<html>
<h1>This test keyword would not be replaced</h1>
<p>This test keyword should be replaced</p>
</html>
Output:
<html>
<h1>This test keyword would not be replaced</h1>
<p>This replaced should be replaced</p>
</html>
Also this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:param name="pMatch" select="'test keyword'"/>
<xsl:param name="pReplace" select="'replaced'"/>
<xsl:param name="pPreserve" select="'h1,h2,h3,i,u,b,strong,em'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:choose>
<xsl:when test="contains(concat(',',$pPreserve,','),
concat(',',name(..),','))">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="replace"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="pString" select="."/>
<xsl:choose>
<xsl:when test="contains($pString,$pMatch)">
<xsl:value-of
select="concat(substring-before($pString,
$pMatch),
$pReplace)"/>
<xsl:call-template name="replace">
<xsl:with-param name="pString"
select="substring-after($pString,
$pMatch)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
EDIT: Better XQuery.
精彩评论