替换一些文本后保留 HTML 格式(使用 PHP 和 JS)

Posted

技术标签:

【中文标题】替换一些文本后保留 HTML 格式(使用 PHP 和 JS)【英文标题】:keep HTMLformat after replace some text (using PHP and JS) 【发布时间】:2011-02-03 13:21:03 【问题描述】:

我想修改 html 之类的

I am <b>Sadi, novice</b> programmer.

I am <b>Sadi, learner</b> programmer.

为此,我将使用字符串“新手程序员”进行搜索。请问我该怎么做?有什么想法吗?

它使用多个单词“新手程序员”进行搜索。这可能是一个完整的句子。额外的空格(例如新行、制表符)应该被忽略,并且在搜索过程中必须忽略任何标签。但在更换期间必须保留标签。

它是一种转换器。如果它不区分大小写会更好。

谢谢

萨迪


更多说明:

我得到了一些不错的答复,并提供了可能的解决方案。但是,如果您有任何想法,请继续发布。

我想进一步澄清这个问题,以防有人错过。主帖将问题作为示例场景展示。

1) 现在的问题是在不考虑标签的情况下查找和替换一些字符串。标签可能会出现在一个单词中。字符串可能包含多个单词。 标签只出现在内容字符串或文档中搜索词组从不包含任何标签

我们可以很方便的去掉所有的标签,做一些文字操作。但是这里出现了另一个问题。

2) 标记必须保留,即使在替换文本之后也是如此。这就是示例所显示的内容。

再次感谢您的帮助

【问题讨论】:

+1 对此技术感兴趣.... 这可能比看起来更复杂。如果我想用“学习编程”来代替“新手程序员”怎么办?代码应该如何确定将哪些单词放在元素内以及放在元素之后? 就像 matti 说的,基本上你在你的例子中所做的就是用“学习者”替换“新手”,这可以通过一个简单的 str_replace 来实现。如果我们正在讨论用包含更多单词的字符串替换“新手程序员”,那么您可能会遇到问题,您可能无法通过代码解决。 你总是可以用代码解决它,你只需要知道你的规则是什么。正则表达式很棒,可以做很多很棒的事情。 @Matti Virkkunen:你是对的。你明白了:)。这是主要问题:(它不能总是通过简单地替换单词来完成。 【参考方案1】:

除非 cOm 已经写好了,否则正则表达式是最好的选择:

$cleaned_string = preg_replace('/\<.\>/', $raw_text, "");

或者类似的东西。我需要研究/测试正则表达式。

然后你可以用一个简单的$foobar = str_replace($find, $replace_with, $cleaned_string); 来找到你要替换的文本。

没有意识到他想把 HTML 放回去。这都是正则表达式,而且比我目前知道的要多。

知道我所知道的,从技术上讲,我可能会使用一个表达式,它不会忽略单词之间之间的空格,而是在&lt;&gt; 括号之间忽略空格,然后使用正则表达式输出的包含变量的能力。

【讨论】:

这将完全删除 HTML 格式,并且该帖子专门关于 保持 HTML 格式。 是的,刚刚注意到。很抱歉混淆了。【参考方案2】:

嗯,可能有更好的方法,但我没想到(假设标签不会出现在单词中间,HTML 格式正确,等等)...

基本上,您需要三样东西(抱歉,如果这听起来很傲慢,不是故意的): 1.一种忽略标签的子串匹配方法。 2.一种保留标签的替换方法。 3. 一种将它们放在一起的方式。

1 - 这可能是最困难的一点。一种方法是遍历源字符串中的所有字符(字符串基本上是字符数组,因此您可以像访问数组元素一样访问字符),尝试从搜索字符串中匹配尽可能多的字符,停止当您匹配所有字符或用完字符匹配时。任何介于 '' 之间的字符都应该被忽略。一些伪代码(检查一下,来晚了,可能有错误):

findMatch(startingPos : integer, subject : string, searchString : string)
    //Variables for keeping track of characters matched, positions, etc.
    inTag = false;
    matchFound = false;
    matchedCharacters = 0;
    matchStart = 0;
    matchEnd = 0;

    for(i from startingPos to length(searchString))
        //Work out when entering or exiting tags, ignore tag contents
        if(subject[i] == '<' || subject[i] == '>')
            inTag = !inTag;
        
        else if(!inTag)
            //Check if the character matches expected in search string
            if(subject[i] == searchString[matchedCharacters])
                if(!matchFound)
                    matchFound = true;
                    matchStart = i;
                
                matchedCharacters++;

                //If all of the characters have been matched, return the start and end positions of the substring
                if(matchedCharacters + 1 == length(searchString))
                    matchEnd = i - matchStart;
                    return matchStart, matchEnd;
                
            
            else
                //Reset counts if not found
                matchFound = false;
                matchCharacters = 0;
            
        
    
    //If no full matches were found, return error
    return -1;

2 - 将 HTML 源代码拆分为三个字符串 - 您要处理的位(在匹配函数返回的两个位置之间)和之前和之后的部分。拆分您要修改的位,例如:

$parts = preg_split("/(<[^>]*>)/",$string, -1, PREG_SPLIT_DELIM_CAPTURE);

记录标记的位置,连接非标记段并照常对此执行子字符串替换,然后再次拆分修改后的字符串并重新组合到位的标记。

3 - 这是简单的部分,只需将修改后的部分和其他两个位连接在一起。

我的想法可能过于复杂了,如果是这样,请忽略我。

【讨论】:

当我第一次遇到这个问题时,我把它分成了两个问题。 1)查找字符串并忽略标签& 2)在保留标签的同时替换字符串。剥离所有标签并替换很容易,但是当我必须保留标签时会出现问题。我需要一点时间来看看你的解决方案。【参考方案3】:

我会这样做:

if (preg_match('/(.*)novice((?:<.*>)?\s(?:<.*>)?programmer.*)/',$inString,$attributes) 
  $inString = $attributes[1].'learner'.$attributes[2];

它应该匹配以下任何一个:

novice programmer
novice</b> programmer
novice </b>programmer
novice<span> programmer

正则表达式状态的测试版本类似于:匹配任何字符集,直到达到“新手”并将其放入捕获组,然后可能匹配以“' 结尾(但不要捕获它),但随后只匹配带有空格的内容,然后可能再次匹配以 '' 结尾(但不要捕获它),然后程序员必须跟在后面跟着任意数量的字符并将其放入捕获组中。

我会做一些具体的测试,因为我可能错过了一些东西。正则表达式是程序员最好的朋友!

【讨论】:

这是非常硬编码的,但可能是一个可能的解决方案,谢谢 还有一件事,新手也被替换了,只是你看不到效果,因为两个词(搜索替换)都是“新手”。 不,它不是preg_replace...它是preg_match,它只会在模式匹配并且捕获组被移动到$attributes然后重新组合到所需的情况下才会触发细绳。就硬编码而言,它是为您提供所需的内容,但正则表达式可以适应您真正需要的任何内容。 "我是Sadi,新手程序员。我很简单。我是Sadi,新手程序员。我很简单" -- 无法正常工作使用此字符串,此处的结果会出现两次。我尝试过使用 preg_match_all 和 preg_match。而且它永远不会取代程序员。它保持原样。请问有什么想法吗?【参考方案4】:

有趣的问题。

我会使用 DOM 和 XPath 来查找包含该文本的最近节点,然后使用子字符串匹配来找出字符串的哪个位在哪个节点中。不过,这将涉及到每个字符的匹配和可能的回溯。

这是第一部分,寻找容器节点:

<?php
error_reporting(E_ALL);
header('Content-Type: text/plain; charset=UTF-8');

$doc = new DOMDocument();
$doc->loadHTML(<<<EOD
<p>
    <span>
        <i>
            I am <b>Sadi, novice</b> programmer.
        </i>
    </span>
</p>
<ul>
    <li>
        <div>
            I am <em>Cornholio, novice</em> programmer of television shows.
        </div>
    </li>
</ul>
EOD
);
$xpath = new DOMXPath($doc);
// First, get a list of all nodes containing the text anywhere in their tree.
$nodeList = $xpath->evaluate('//*[contains(string(.), "programmer")]');
$deepestNodes = array();
// Now only keep the deepest nodes, because the XPath query will also return HTML, BODY, ...
foreach ($nodeList as $node) 
    $deepestNodes[] = $node;
    $ancestor = $node;
    while (($ancestor = $ancestor->parentNode) && ($ancestor instanceof DOMElement)) 
        $deepestNodes = array_filter($deepestNodes, function ($existingNode) use ($ancestor) 
            return ($ancestor !== $existingNode);
        );
    

foreach ($deepestNodes as $node) 
    var_dump($node->tagName);

希望对你有所帮助。

【讨论】:

“不过,这将涉及到每个字符的匹配和可能的回溯。”虽然听起来不错,但它可能不是生产环境的好解决方案。但我会看看你的解决方案。谢谢【参考方案5】:

由于您没有具体说明您将使用它的具体用途,我将使用您的示例“我是 sadi,新手 程序员”。

$before = 'I am <b>sadi, novice</b> programmer';
$after = preg_replace ('/I am (<.*>)?(.*), novice(<.*>)? programmer/','/I am $1$2,     learner$3 programmer/',$string);

或者,对于任何文本:

$string = '<b>Hello</b>, world!';
$orig = 'Hello';
$replace = 'Goodbye';
$pattern = "/(<.*>)?$orig(<.*>)?/";
$final = "/$1$replace$2/";
$result = preg_replace($pattern,$final,$string);
//$result should now be 'Goodbye, world!'

希望对您有所帮助。 :d

编辑:您的示例的示例,第二段代码: $string = '我是sadi,新手程序员。'; $orig = '新手'; $replace = '学习者'; $pattern = "/(<.>>)?$orig(<.>>)?/"; $final = "$1$replace$2"; $result = htmlspecialchars(preg_replace($pattern,$final,$string)); 回显$结果;

唯一的问题是,如果您要搜索的内容超过一个单词。

编辑 2:终于想出了一种跨多个单词的方法。代码如下:

function htmlreplace($string,$orig,$replace) 
 
  $orig = explode(' ',$orig);
  $replace = explode(' ',$replace);
  $result = $string;
  while (count($orig)>0)
   
    $shift = array_shift($orig);
    $rshift = array_shift($replace);

    $pattern = "/$shift\s?(<.*>)?/";
    $replacement = "$rshift$1";
    $result = preg_replace($pattern,$replacement,$result);
   
  $result .= implode(' ',$replace);
  return $result;
 

玩得开心! :d

【讨论】:

请看例子。它使用多个单词“新手程序员”进行搜索。这可能是一个完整的句子。搜索过程中应忽略多余的空格(例如换行符、制表符)和任何标记。 嗯,我认为它没有考虑空格...另一个修复即将到来,请等待几分钟。 无法正常工作。它的工作原理就像用单词替换。即使用单词替换也不总是有效。示例: $inString = '我是 Sadi,新手 程序员。我很简单。我是 Sadi,新手 程序员。我是简单的程序员'; echo htmlreplace($inString, '新手程序员', '蹩脚的开发者');结果:我是 Sadi,蹩脚的开发人员。我很简单。我是 Sadi,新手开发者。我是简单的开发者【参考方案6】:

好的,我认为这就是您想要的。它接受您的输入搜索和替换,将它们拆分为由空格分隔的字符串数组,生成一个正则表达式,查找具有任意数量的空白/html标签的输入句子,并将其替换为替换句子,并在单词之间替换相同的标签.

如果搜索语句的字数高于替换的,它只是在任何多余的单词之间使用空格,如果替换的字数高于搜索,它会在末尾添加所有“孤儿”标签。它还处理查找和替换中的正则表达式字符。

<?php
function htmlFriendlySearchAndReplace($find, $replace, $subject) 
    $findWords = explode(" ", $find);
    $replaceWords = explode(" ", $replace);

    $findRegexp = "/";
    for ($i = 0; $i < count($findWords); $i++) 
        $findRegexp .= preg_replace("/([\\$\\^\\|\\.\\+\\*\\?\\(\\)\\[\\]\\\\\\\\\\-])/", "\\\\$1", $findWords[$i]);
        if ($i < count($findWords) - 1) 
            $findRegexp .= "(\s?(?:<[^>]*>)?\s(?:<[^>]*>)?)";
        
    
    $findRegexp .= "/i";

    $replaceRegexp = "";
    for ($i = 0; $i < count($findWords) || $i < count($replaceWords); $i++) 
        if ($i < count($replaceWords)) 
            $replaceRegexp .= str_replace("$", "\\$", $replaceWords[$i]);
        
        if ($i < count($findWords) - 1) 
            $replaceRegexp .= "$" . ($i + 1);
         else 
            if ($i < count($replaceWords) - 1) 
                $replaceRegexp .= " ";
            
        
    

    return preg_replace($findRegexp, $replaceRegexp, $subject);

?>

这里是一些测试的结果:

Original : <b>Novice Programmer</b>
Search : Novice Programmer
Replace : Advanced Programmer
Result : <b>Advanced Programmer</b>

Original : Hi, <b>Novice Programmer</b>
Search : Novice Programmer
Replace : Advanced Programmer
Result : Hi, <b>Advanced Programmer</b>

Original : I am not a <b>Novice</b> Programmer
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b>Advanced</b> Programmer

Original : Novice <b>Programmer</b> in the house
Search : Novice Programmer
Replace : Advanced Programmer
Result : Advanced <b>Programmer</b> in the house

Original : <i>I am not a <b>Novice</b> Programmer</i>
Search : Novice Programmer
Replace : Advanced Programmer
Result : <i>I am not a <b>Advanced</b> Programmer</i>

Original : I am not a <b><i>Novice</i> Programmer</b> any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b><i>Advanced</i> Programmer</b> any more

Original : I am not a <b><i>Novice</i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b><i>Advanced</i></b> Programmer any more

Original : I am not a Novice<b> <i> </i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a Advanced<b> <i> </i></b> Programmer any more

Original : I am not a Novice <b><i> </i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a Advanced <b><i> </i></b> Programmer any more

Original : <i>I am a <b>Novice</b> Programmer</i> too, now
Search : Novice Programmer too
Replace : Advanced Programmer
Result : <i>I am a <b>Advanced</b> Programmer</i> , now

Original : <i>I am a <b>Novice</b> Programmer</i>, now
Search : Novice Programmer
Replace : Advanced Programmer Too
Result : <i>I am a <b>Advanced</b> Programmer Too</i>, now

Original : <i>I make <b>No money</b>, now</i>
Search : No money
Replace : Mucho$1 Dollar$
Result : <i>I make <b>Mucho$1 Dollar$</b>, now</i>

Original : <i>I like regexp, you can do [A-Z]</i>
Search : [A-Z]
Replace : [Z-A]
Result : <i>I like regexp, you can do [Z-A]</i>

【讨论】:

我喜欢这个解决方案。但这里有一个小错误。 $inString = '我是Sadi,新手程序员。我很简单。我是 Sadi,新手 程序员。我很简单'; echo htmlFriendlySearchAndReplace('新手程序员', '蹩脚的开发者', $inString);结果是:我是 Sadi,蹩脚的程序员。我很简单。我是 Sadi,新手开发者。我很简单 抱歉,已编辑答案以修复。更改此行:$findRegexp .= "(\s?(?:&lt;[^&gt;]*&gt;)?\s(?:&lt;[^&gt;]*&gt;)?)"; 谢谢,现在它工作得很好。唯一的问题是如果在单词中间找到标签,它就无法工作。例如新手ce 当然,这很难解决,因为我们不能轻易确定标签的位置。如果可以,请发布它的解决方案。 您可以向前或向后移动标签 :) 非常感谢您的解决方案。我尝试过类似的解决方案(作为你的函数)但失败了,因为我对正则表达式不好:( 呃!!!我无法接受答案 :( 接受按钮不见了 :( 可能是因为赏金......但这是最好的解决方案

以上是关于替换一些文本后保留 HTML 格式(使用 PHP 和 JS)的主要内容,如果未能解决你的问题,请参考以下文章

替换字符串中的 html 标记,但保留文本并用自定义标记重新换行

Python-docx和保留格式

python-docx替换word文档中有格式文本

如何替换html标签但将文本保留在两者之间?

如何使用 php 保留一些限制的空格?

PHP GD - 水平居中对齐文本并减小字体大小以将其保留在图像内