代码之家  ›  专栏  ›  技术社区  ›  BrynJ

Regex/DOMDocument-匹配并替换不在链接中的文本

  •  12
  • BrynJ  · 技术社区  · 14 年前

    我需要以不区分大小写的方式查找和替换所有文本匹配项,除非文本位于锚定标记中-例如:

    <p>Match this text and replace it</p>
    <p>Don't <a href="/">match this text</a></p>
    <p>We still need to match this text and replace it</p>
    

    [编辑] 根据Gordon的评论,在这个实例中可能更倾向于使用DOMDocument。我对DOMDocument扩展一点也不熟悉,非常希望能有一些关于此功能的基本示例。

    7 回复  |  直到 11 年前
        1
  •  17
  •   Community CDub    7 年前

    这里有一个UTF-8安全的解决方案,它不仅适用于格式正确的文档,也适用于文档片段。

    需要mb_convert_编码,因为loadHtml()似乎有UTF-8编码的错误(请参见 here here ).

    <?php
    $html = '<p>Match this text and replace it</p>
    <p>Don\'t <a href="/">match this text</a></p>
    <p>We still need to match this text and replace itŐŰ</p>
    <p>This is <a href="#">a link <span>with <strong>don\'t match this text</strong> content</span></a></p>';
    
    $dom = new DOMDocument();
    // loadXml needs properly formatted documents, so it's better to use loadHtml, but it needs a hack to properly handle UTF-8 encoding
    $dom->loadHtml(mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"));
    
    $xpath = new DOMXPath($dom);
    
    foreach($xpath->query('//text()[not(ancestor::a)]') as $node)
    {
        $replaced = str_ireplace('match this text', 'MATCH', $node->wholeText);
        $newNode  = $dom->createDocumentFragment();
        $newNode->appendXML($replaced);
        $node->parentNode->replaceChild($newNode, $node);
    }
    
    // get only the body tag with its contents, then trim the body tag itself to get only the original content
    echo mb_substr($dom->saveXML($xpath->query('//body')->item(0)), 6, -7, "UTF-8");
    

    参考文献:
    1. find and replace keywords by hyperlinks in an html fragment, via php dom
    2. Regex / DOMDocument - match and replace text not in a link
    3. php problem with russian language
    4. Why Does DOM Change Encoding?

    我读了很多关于这个主题的答案,所以如果我忘记了某个人,我很抱歉(请评论一下,在这个例子中我也会加上你的答案)。

    感谢戈登,感谢他继续评论 my other answer .

        2
  •  6
  •   netcoder    14 年前

    $dom = new DOMDocument;
    $dom->loadHTML($html_content);
    
    function preg_replace_dom($regex, $replacement, DOMNode $dom, array $excludeParents = array()) {
      if (!empty($dom->childNodes)) {
        foreach ($dom->childNodes as $node) {
          if ($node instanceof DOMText && 
              !in_array($node->parentNode->nodeName, $excludeParents)) 
          {
            $node->nodeValue = preg_replace($regex, $replacement, $node->nodeValue);
          } 
          else
          {
            preg_replace_dom($regex, $replacement, $node, $excludeParents);
          }
        }
      }
    }
    
    preg_replace_dom('/match this text/i', 'IT WORKS', $dom->documentElement, array('a'));
    
        3
  •  3
  •   bcosca    14 年前

    这是使用DOM树的预顺序遍历的无堆栈非递归方法。

      libxml_use_internal_errors(TRUE);
      $dom=new DOMDocument('1.0','UTF-8');
    
      $dom->substituteEntities=FALSE;
      $dom->recover=TRUE;
      $dom->strictErrorChecking=FALSE;
    
      $dom->loadHTMLFile($file);
      $root=$dom->documentElement;
      $node=$root;
      $flag=FALSE;
      for (;;) {
          if (!$flag) {
              if ($node->nodeType==XML_TEXT_NODE &&
                  $node->parentNode->tagName!='a') {
                  $node->nodeValue=preg_replace(
                      '/match this text/is',
                      $replacement, $node->nodeValue
                  );
              }
              if ($node->firstChild) {
                  $node=$node->firstChild;
                  continue;
              }
         }
         if ($node->isSameNode($root)) break;
         if ($flag=$node->nextSibling)
              $node=$node->nextSibling;
         else
              $node=$node->parentNode;
     }
     echo $dom->saveHTML();
    

    libxml_use_internal_errors(TRUE); 后面的三行代码 $dom=new DOMDocument; 应该能够处理任何格式错误的HTML。

        4
  •  2
  •   lheurt    14 年前
    $a='<p>Match this text and replace it</p>
    <p>Don\'t <a href="/">match this text</a></p>
    <p>We still need to match this text and replace it</p>';
    
    echo preg_replace('~match this text(?![^<]*</a>)~i','replacement',$a);
    

    负lookahead确保只有在下一个标记不是关闭链接时才进行替换。它可以很好地与你的例子,虽然它不会工作,如果你碰巧使用其他标签在你的链接。

        5
  •  1
  •   Community CDub    7 年前

    你可以用 PHP Simple HTML DOM Parser 下面是与 Netcoder's DomDocument solution

    function replaceWithSimpleHtmlDom($html_content, $search, $replace, $excludedParents = array()) {
        require_once('simple_html_dom.php');
        $html = str_get_html($html_content);
        foreach ($html->find('text') as $element) {
            if (!in_array($element->parent()->tag, $excludedParents))
                $element->innertext = str_ireplace($search, $replace, $element->innertext);
        }
        return (string)$html;
    }
    

    我刚刚根据我的 DomDocument

        6
  •  0
  •   MnomrAKostelAni    14 年前
    <?php
    $a = '<p>Match this text and replace it</p>
    <p>Don\'t <a href="/">match this text</a></p>
    <p>We still need to match this text and replace it</p>
    ';
    $res = preg_replace("#[^<a.*>]match this text#",'replacement',$a);
    echo $res;
    ?>
    

    这样行。希望你要真正的大小写敏感,所以配合小字母。

        7
  •  0
  •   Nathan MacInnes    14 年前

    preg_replace('/match this text/i','replacement text');
    preg_replace('/(<a[^>]*>[^(<\/a)]*)replacement text(.*?<\/a)/is',"$1match this text$3");
    

    如果你的 replacement text