问题描述

我有 html 代码存储在一个字符串中,例如:

$html = '
        <html>
        <body>
        <p>Hello <em> 進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.</p>
        </body>
        </html>
        ';

然后我有两个句子存储在变量中:

$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

我想搜索 $html 这两个句子,并剥离他们之前和之后的一切。所以 $html 将成为:

$html = 'Hello <em> 進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.';

我该如何实现?请注意,$begin$end 变量没有 html 标签,但 $html 中的句子很可能具有如上所示的标签。

也许是正则表达式?

到目前为止我已经尝试过

  • strpos()方法。问题是 $html 包含句子中的标签,使得 $begin$end 句子不匹配。在运行 strpos()之前,我可以 strip_tags($html),但是我明显会得到没有标签的 $html

  • 搜索变量的一部分,如 Hello,但这是永远不会安全的,并会给出许多匹配。

最佳解决办法

这是一个基于懒惰点匹配正则表达式的简短但我相信的工作解决方案 (可以通过创建一个更长的,展开的正则表达式来改进,但应该足够,除非你有很大的文本块) 。

$html = "<html>n<body>n<p><p>H<div>ello</div><script></script> <em> 進&nbsp;&nbsp;&nbsp; 撃の巨人</em>!</p>nrandom codenrandom coden<p>Lorem <span>ipsum<span>.</p>n</body>n </html>";
$begin = 'Hello     進撃の巨人!';
$end = 'Lorem ipsum.';
$begin = preg_replace_callback('~s++(?!z)|(s++z)~u', function ($m) { return !empty($m[1]) ? '' : ' '; }, $begin);
$end = preg_replace_callback('~s++(?!z)|(s++z)~u', function ($m) { return !empty($m[1]) ? '' : ' '; }, $end);
$begin_arr = preg_split('~(?=X)~u', $begin, -1, PREG_SPLIT_NO_EMPTY);
$end_arr = preg_split('~(?=X)~u', $end, -1, PREG_SPLIT_NO_EMPTY);
$reg = "(?s)(?:<[^<>]+>)?(?:&#?\w+;)*\s*" .  implode("", array_map(function($x, $k) use ($begin_arr) { return ($k < count($begin_arr) - 1 ? preg_quote($x, "~") . "(?:s*(?:<[^<>]+>|&#?\w+;))*" : preg_quote($x, "~"));}, $begin_arr, array_keys($begin_arr)))
        . "(.*?)" . 
        implode("", array_map(function($x, $k) use ($end_arr) { return ($k < count($end_arr) - 1 ? preg_quote($x, "~") . "(?:s*(?:<[^<>]+>|&#?\w+;))*" : preg_quote($x, "~"));}, $end_arr, array_keys($end_arr))); 
echo $reg .PHP_EOL;
preg_match('~' . $reg . '~u', $html, $m);
print_r($m[0]);

参见 IDEONE demo

算法:

  • 通过将分隔符字符串分割为单个字形来创建动态正则表达式模式 (因为这些可以是 Unicode 字符,我建议使用 preg_split('~(?<!^)(?=X)~u', $end)),并通过添加可选标记匹配模式 (?:<[^<>]+>)? 来回滚。

  • 然后,当. 与包含换行符的任何字符匹配时,(?s)启用 DOTALL 模式,.*? 将匹配从前到尾分隔符的 0+个字符。

正则表达式详细信息:

  • '~(?<!^)(?=X)~u 匹配每个字母之前的字符串开头以外的每个位置

  • (样本最终正则表达式)(?s)(?:<[^<>]+>)?(?:&#?w+;)*s*H(?:s*(?:<[^<>]+>|&#?w+;))*e(?:s*(?:<[^<>]+>|&#?w+;))*l(?:s*(?:<[^<>]+>|&#?w+;))*l(?:s*(?:<[^<>]+>|&#?w+;))*o(?:s*(?:<[^<>]+>|&#?w+;))* (?:s*(?:<[^<>]+>|&#?w+;))*進 (?:s*(?:<[^<>]+>|&#?w+;))*撃 (?:s*(?:<[^<>]+>|&#?w+;))*の(?:s*(?:<[^<>]+>|&#?w+;))*巨 (?:s*(?:<[^<>]+>|&#?w+;))*人 (?:s*(?:<[^<>]+>|&#?w+;))*!(?:s*(?:<[^<>]+>|&#?w+;))* + (.*?) + L(?:s*(?:<[^<>]+>|&#?w+;))*o(?:s*(?:<[^<>]+>|&#?w+;))*r(?:s*(?:<[^<>]+>|&#?w+;))*e(?:s*(?:<[^<>]+>|&#?w+;))*m(?:s*(?:<[^<>]+>|&#?w+;))* (?:s*(?:<[^<>]+>|&#?w+;))*i(?:s*(?:<[^<>]+>|&#?w+;))*p(?:s*(?:<[^<>]+>|&#?w+;))*s(?:s*(?:<[^<>]+>|&#?w+;))*u(?:s*(?:<[^<>]+>|&#?w+;))*m(?:s*(?:<[^<>]+>|&#?w+;))*. – 带有用于标签匹配的可选子模式的前导和后跟分隔符,以及 (.*?)(可能不需要捕获) 。

  • 要处理 Unicode 字符串,~u 修饰符是必需的。

  • 更新:为了占用 1+空格,beginend 模式中的空格可以用 s+子模式替换,以匹配输入字符串中任何种类的空白字符。

  • 更新 2:辅助 $begin = preg_replace('~s+~u', ' ', $begin);$end = preg_replace('~s+~u', ' ', $end); 需要在输入字符串中占用 1 +空格。

  • 要占用 HTML 实体,可以在&#?\w+; 的可选部分添加另一个子模式,它也将与&nbsp;{ 相似。它也预先添加了 s*来匹配可选的空格,并用*进行量化 (可以为零或更多) 。

次佳解决办法

我真的想写一个正则表达式的解决方案。但是我有一些很好的和复杂的解决方案。所以,这里是一个 non-regex 解决方案。

简短说明:主要问题是保留 HTML 标签。如果 HTML 标签被剥离,我们可以轻松搜索文本。所以:剥离这些!我们可以轻松地在剥离的内容中进行搜索,并生成一个我们要剪切的子字符串。然后,尝试在保留标签的同时从 HTML 剪切此子字符串。

优点:

  • 搜索是很容易和独立于 HTML,你可以搜索正则表达式,如果你需要

  • 要求是可扩展的:您可以轻松添加完整的多字节支持,支持实体和 white-space 崩溃等

  • 相对快 (有可能直接正则表达式可以更快)

  • 不接触原始 HTML,并适应其他标记语言

此方案的静态实用程序类:

class HtmlExtractUtil
{

    const FAKE_MARKUP = '<>';
    const MARKUP_PATTERN = '#<[^>]+>#u';

    static public function extractBetween($html, $startTextToFind, $endTextToFind)
    {
        $strippedHtml = preg_replace(self::MARKUP_PATTERN, '', $html);
        $startPos = strpos($strippedHtml, $startTextToFind);
        $lastPos = strrpos($strippedHtml, $endTextToFind);

        if ($startPos === false || $lastPos === false) {
            return "";
        }

        $endPos = $lastPos + strlen($endTextToFind);
        if ($endPos <= $startPos) {
            return "";
        }

        return self::extractSubstring($html, $startPos, $endPos);
    }

    static public function extractSubstring($html, $startPos, $endPos)
    {
        preg_match_all(self::MARKUP_PATTERN, $html, $matches, PREG_OFFSET_CAPTURE);
        $start = -1;
        $end = -1;
        $previousEnd = 0;
        $stripPos = 0;
        $matchArray = $matches[0];
        $matchArray[] = [self::FAKE_MARKUP, strlen($html)];
        foreach ($matchArray as $match) {
            $diff = $previousEnd - $stripPos;
            $textLength = $match[1] - $previousEnd;
            if ($start == (-1)) {
                if ($startPos >= $stripPos && $startPos < $stripPos + $textLength) {
                    $start = $startPos + $diff;
                }
            }
            if ($end == (-1)) {
                if ($endPos > $stripPos && $endPos <= $stripPos + $textLength) {
                    $end = $endPos + $diff;
                    break;
                }
            }
            $tagLength = strlen($match[0]);
            $previousEnd = $match[1] + $tagLength;
            $stripPos += $textLength;
        }

        if ($start == (-1)) {
            return "";
        } elseif ($end == (-1)) {
            return substr($html, $start);
        } else {
            return substr($html, $start, $end - $start);
        }
    }

}

用法:

$html = '
<html>
<body>
<p>Any string before</p>
<p>Hello <em> 進撃の巨人</em>!</p>
random code
random code
<p>Lorem <span>ipsum<span>.</p>
<p>Any string after</p>
</body>
</html>
';
$startTextToFind = 'Hello 進撃の巨人!';
$endTextToFind = 'Lorem ipsum.';

$extractedText = HtmlExtractUtil::extractBetween($html, $startTextToFind, $endTextToFind);

header("Content-type: text/plain; charset=utf-8");
echo $extractedText . "n";

第三种解决办法

正则表达式在解析 HTML 时有其局限性。像许多人在我之前做过的,我会参考这个 famous answer

依赖正则表达式时的潜在问题

例如,假设这个标签出现在必须提取的部分之前的 HTML 中:

<p attr="Hello 進撃の巨人!">This comes before the match</p>

许多正则表达式解决方案将会绊倒,并返回一个字符串,该字符串从 p 标签的中间开始。

或者考虑在 HTML 部分中必须匹配的注释:

<!-- Next paragraph will display "Lorem ipsum." -->

或者,出现一些松散的 less-than 和 greater-than 标志 (假设在评论或属性值中):

<!-- Next paragraph will display >-> << Lorem ipsum. >> -->
<p data-attr="->->->" class="myclass">

那些正则表达式会怎么做?

这些只是例子… 有无数的其他情况对基于正则表达式的解决方案构成问题。

有更可靠的方式来解析 HTML 。

将 HTML 加载到 DOM 中

我将在这里提出一个基于 DOMDocument 接口的解决方案,使用这个算法:

  1. 获取 HTML 文档的文本内容,并标识两个子字符串 (开始/结束) 所在的两个偏移量。

  2. 然后通过 DOM 文本节点跟踪这些节点所适合的偏移量。在两个边界偏移中的任一个交叉的节点中,插入一个预定义的分隔符 (|) 。该分隔符不应该存在于 HTML 字符串中。因此,在满足条件之前,将其加倍 (||||||,…)

  3. 最后,将此分隔符分割为 HTML 表示,并将其中间部分作为结果。

这是代码:

function extractBetween($html, $begin, $end) {
    $dom = new DOMDocument();
    // Load HTML in DOM, making sure it supports UTF-8; double HTML tags are no problem
    $dom->loadHTML('<html><head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
        </head></html>' . $html);
    // Get complete text content
    $text = $dom->textContent;
    // Get positions of the beginning/ending text; exit if not found.
    if (($from = strpos($text, $begin)) === false) return false;
    if (($to = strpos($text, $end, $from + strlen($begin))) === false) return false;
    $to += strlen($end);
    // Define a non-occurring delimiter by repeating `|` enough times:
    for ($delim = '|'; strpos($html, $delim) !== false; $delim .= $delim);
    // Use XPath to traverse the DOM
    $xpath = new DOMXPath($dom);
    // Go through the text nodes keeping track of total text length.
    // When exceeding one of the two offsets, inject a delimiter at that position.
    $pos = 0;
    foreach($xpath->evaluate("//text()") as $node) {
        // Add length of node's text content to total length
        $newpos = $pos + strlen($node->nodeValue);
        while ($newpos > $from || ($from === $to && $newpos === $from)) {
            // The beginning/ending text starts/ends somewhere in this text node.
            // Inject the delimiter at that position:
            $node->nodeValue = substr_replace($node->nodeValue, $delim, $from - $pos, 0);
            // If a delimiter was inserted at both beginning and ending texts,
            // then get the HTML and return the part between the delimiters
            if ($from === $to) return explode($delim, $dom->saveHTML())[1];
            // Delimiter was inserted at beginning text. Now search for ending text
            $from = $to;
        }
        $pos = $newpos;
    }
}

你会这样称呼:

// Sample input data
$html = '
        <html>
        <body>
        <p>This comes before the match</p>
        <p>Hey! Hello <em> 進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>. la la la</p>
        <p>This comes after the match</p>
        </body>
        </html>
        ';

$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

// Call
$html = extractBetween($html, $begin, $end);

// Output result
echo $html;

输出:

Hello <em> 進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.

你会发现这个代码比 regex 替代方案更容易维护。

看到它运行在 eval.in

第四种办法

这可能远远不是最佳的解决方案,但是我喜欢打破这个”riddles” 的头脑,所以这里是我的方法。

<?php
$subject = ' <html> 
<body> 
<p>He<i>l</i>lo <em>Lydia</em>!</p> 
random code 
random code 
<p>Lorem <span>ipsum</span>.</p> 
</body> 
</html>';

$begin = 'Hello Lydia!';
$end = 'Lorem ipsum.';

$begin_chars = str_split($begin);
$end_chars = str_split($end);

$begin_re = '';
$end_re = '';

foreach ($begin_chars as $c) {
    if ($c == ' ') {
        $begin_re .= '(s|(<[a-z/]+>))+';
    }
    else {
        $begin_re .= $c . '(<[a-z/]+>)?';
    }
}
foreach ($end_chars as $c) {
    if ($c == ' ') {
        $end_re .= '(s|(<[a-z/]+>))+';
    }
    else {
        $end_re .= $c . '(<[a-z/]+>)?';
    }
}

$re = '~(.*)((' . $begin_re . ')(.*)(' . $end_re . '))(.*)~ms';

$result = preg_match( $re, $subject , $matches );
$start_tag = preg_match( '~(<[a-z/]+>)$~', $matches[1] , $stmatches );

echo $stmatches[1] . $matches[2];

输出:

<p>He<i>l</i>lo <em>Lydia</em>!</p> 
random code 
random code 
<p>Lorem <span>ipsum</span>.</p>

这是匹配这种情况,但我认为这将需要更多的逻辑来转义正则表达式特殊字符如句点。

一般来说,这段代码片段:

  • 将字符串拆分为数组,每个数组值表示单个字符。这需要做,因为 Hello 也需要匹配 Hel<i>l</i>o

  • 为了做到这一点,对于正则表达式,每个字符之后插入一个额外的 (<[a-z/]+>)?,其中包含空格字符的特殊情况。

第五种办法

你可以试试这个 RegEx:

(.*?)  # Data before sentences (to be removed)
(      # Capture Both sentences and text in between
  H.*?e.*?l.*?l.*?o.*?s    # Hello[space]
  (<.*?>)*                  # Optional Opening Tag(s)
  進.*? 撃.*?の.*? 巨.*? 人.*?   # 進撃の巨人
  (</.*?>)*                # Optional Closing Tag(s)
  (.*?)                     # Optional Data in between sentences
  (<.*?>)*                  # Optional Opening Tag(s)
  L.*?o.*?r.*?e.*?m.*?s    # Lorem[space]
  (<.*?>)*                  # Optional Opening Tag(s)
  i.*?p.*?s.*?u.*?m.*?      # ipsum
)
(.*)   # Data after sentences (to be removed)

2nd 捕获组代替

Live Demo on Regex101

正则表达式可以缩短为:

(.*?)(H.*?e.*?l.*?l.*?o.*?s(<.*?>)*進.*? 撃.*?の.*? 巨.*? 人.*?(</.*?>)*(.*?)(<.*?>)*L.*?o.*?r.*?e.*?m.*?s(<.*?>)*i.*?p.*?s.*?u.*?m.*?)(.*)

第六种办法

只是为了好玩

<?php
$begin = 'Hello Moto!';
$end = 'Lorem ipsum.';
//https://regex101.com/r/mC8aO6/1
$re = "/[\w\W]/"; 
$str = $begin.$end; 
$subst = "$0.*?"; 

$result = preg_replace($re, $subst, $str);
//Hello Moto! 
//to
//H.*?e.*?l.*?l.*?o.*? .*?M.*?o.*?t.*?o.*?!.*?

//https://regex101.com/r/fS6zG2/1
$re = "/(\!|\.\.)/"; 
$str = $result; 
$subst = "\\$1";

$result = preg_replace($re, $subst, $str);

$re = "/.*(<p.*?$result.*?p>).*/s"; 
$str = "        <html>n        <body>n        <p>He<i>l</i>lo <em>Moto</em>!n        random coden        random coden        <p>Lorem <span>ipsum<span>.<p>n        </body>n        </html>n        "; 
$subst = "$1"; 

$result = preg_replace($re, $subst, $str);
echo $result."n";
?>

输入

$begin = 'Hello Moto!';
$end = 'Lorem ipsum.';

    <html>
    <body>
    <p>He<i>l</i>lo <em>Moto</em>!
    random code
    random code
    <p>Lorem <span>ipsum<span>.<p>
    </body>
    </html>

产量

<p>He<i>l</i>lo <em>Moto</em>!
        random code
        random code
        <p>Lorem <span>ipsum<span>.<p>

第七种办法

在 HTML 源上进行内容搜索有几种不同的方法。它们都有优点和缺点。如果未知代码中的结构是一个问题,最安全的方法是使用 XML 解析器,但是这些结构很复杂,因此相当慢。

正则表达式设计用于文本处理。虽然正则表达式不是由于开销而最快的事情,但 preg_函数是一个合理的妥协,以保持代码小而简洁,而不会因为防止模式变得太复杂而影响到很多性能。

HTML 结构的分析可以通过递归正则表达式来实现。由于处理速度较慢,难以调试,我更喜欢在 PHP 中编写基本逻辑,并利用 preg_功能来执行较小的快速任务。

这是 OOP 中的一个解决方案,这是一个用于处理同一 HTML 源的许多搜索的小类。它已经是一种处理扩展的类似问题的方法,如添加前一个和后续内容直到下一个标记边界。它并不是一个完美的解决方案,但它很容易扩展。

逻辑是:为初始化支付一些运行时间,以便相对于纯文本,带标签存储标签位置,并将字符串存储在<...> 和长度总和之间。然后在每个内容搜索匹配针与普通内容。通过二进制搜索找到 HTML 源中的开始/结束位置。

二进制搜索工作类似:需要排序列表。您存储第一个和最后一个元素的索引+1 。通过加法和整数除以 2 计算平均值。通过正确的位移执行分割和分层。如果找到的值为低,则将 index 值设置为当前索引的值越小,否则越大。停止索引差异 1. 如果搜索确切的值,请早点删除元素。 0,(14 + 1)=> 7; 7,15 => 11; 7,11 => 9; 7,9 => 8; 8-7 = diff.1 而不是 15 次迭代,只有 4 个完成。开始值越大,指数地保存的时间越多。

PHP 类:

<?php
class HtmlTextSearch
{
  protected 
    $html            = '',
    $heystack        = '',
    $tags            = [],
    $current_tag_idx = null
  ;

  const
    RESULT_NO_MODIFICATION      = 0,
    RESULT_PREPEND_TAG          = 1,
    RESULT_PREPEND_TAG_CONTENT  = 2,
    RESULT_APPEND_TAG           = 4,
    RESULT_APPEND_TAG_CONTENT   = 8,
    MATCH_CASE_INSENSITIVE      =16,
    MATCH_BLANK_AS_WHITESPACE   =32,
    MATCH_BLANK_MULTIPLE        =64
  ;

  public function __construct($html)
  {
    $this->set_html($html);
  }

  public function set_html($html)
  {
    $this->html = $html;
    $regexp = '~<.*?>~su';
    preg_match_all($regexp, $html, $this->tags, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);
    $this->tags = $this->tags[0];
    # we use exact the same algorithm to strip html
    $this->heystack = preg_replace($regexp, '', $html);

    # convert positions to plain content
    $sum_length = 0;
    foreach($this->tags as &$tag)
    { $tag['pos_in_content'] = $tag[1] - $sum_length;
      $tag['sum_length'    ] = $sum_length += strlen($tag[0]);
    }

    # zero length dummy tags to mark start/end position of strings not beginning/ending with a tag
    array_unshift($this->tags , [0 => '', 1 => 0, 'pos_in_content' => 0, 'sum_length' => 0 ]); 
    array_push   ($this->tags , [0 => '', 1 => strlen($html)-1]); 
  }

  public function translate_pos_plain2html($content_position)
  {
    # binary search
    $idx = [true => 0, false => count($this->tags)-1];
    while(1 < $idx[false] - $idx[true])
    { $i = ($idx[true] + $idx[false]) >>1;                               // integer half of both array indexes
      $idx[$this->tags[$i]['pos_in_content'] <= $content_position] = $i; // hold one index less and the other greater
    }

    $this->current_tag_idx = $idx[true];
    return $this->tags[$this->current_tag_idx]['sum_length'] + $content_position;
  }

  public function &find_content($needle_start, $needle_end = '', $result_modifiers = self::RESULT_NO_MODIFICATION)
  {
    $needle_start = preg_quote($needle_start, '~');
    $needle_end   = '' == $needle_end ? '' : preg_quote($needle_end  , '~');
    if((self::MATCH_BLANK_MULTIPLE | self::MATCH_BLANK_AS_WHITESPACE) & $result_modifiers)
    { 
      $replacement  = self::MATCH_BLANK_AS_WHITESPACE & $result_modifiers ? 's' : ' ';
      if(self::MATCH_BLANK_MULTIPLE & $result_modifiers)
      { $replacement .= '+';
        $multiplier = '+';
      }
      else
        $multiplier = '';
      $repl_pattern = "~ $multiplier~";
      $needle_start = preg_replace($repl_pattern, $replacement, $needle_start);
      $needle_end   = preg_replace($repl_pattern, $replacement, $needle_end);
    }

    $icase = self::MATCH_CASE_INSENSITIVE & $result_modifiers ? 'i' : '';
    $search_pattern = "~{$needle_start}.*?{$needle_end}~su$icase";
    preg_match_all($search_pattern, $this->heystack, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);

    foreach($matches[0] as &$match)
    { $pre = $post = '';

      $pos_start = $this->translate_pos_plain2html($match[1]);
      if(self::RESULT_PREPEND_TAG_CONTENT & $result_modifiers)
        $pos_start = $this->tags[$this->current_tag_idx][1]
          +( self::RESULT_PREPEND_TAG & $result_modifiers ? 0 : strlen ($this->tags[$this->current_tag_idx][0]) );
      elseif(self::RESULT_PREPEND_TAG     & $result_modifiers)
        $pre = $this->tags[$this->current_tag_idx][0];

      $pos_end   = $this->translate_pos_plain2html($match[1] + strlen($match[0]));
      if(self::RESULT_APPEND_TAG_CONTENT & $result_modifiers)
      { $next_tag = $this->tags[$this->current_tag_idx+1];
        $pos_end = $next_tag[1]
          +( self::RESULT_APPEND_TAG  & $result_modifiers ? strlen ($next_tag[0]) : 0);
      }
      elseif(self::RESULT_APPEND_TAG     & $result_modifiers)
        $post = $this->tags[$this->current_tag_idx+1][0];

      $match = $pre . substr($this->html, $pos_start, $pos_end - $pos_start) . $post;
    };
    return $matches[0];
  }
}

一些测试用例:

$html_source = get($_POST['html'], <<< ___
<html>
  <body>
    <p>He said: "Hello <em> 進撃の巨人</em>!"</p>
    random code
    random code
    <p>Lorem <span>ipsum</span>. foo bar</p>
  </body>
</html>
___
);


  function get(&$ref, $default=null) { return isset($ref) ? $ref : $default; }

  function attr_checked($name, $method = "post")
  { $req = ['post' => '_POST', 'get' => '_GET'];
    return isset($GLOBALS[$req[$method]][$name]) ? ' checked="checked"' : '';
  }

  $begin = get($_POST['begin'], '"Hello 進撃の巨人!"');
  $end   = get($_POST['end'  ], 'Lorem ipsum.'   );
?>

<form action="" method="post">
  <textarea name="html" cols="80" rows="10"><?php
echo $html_source;
?></textarea>

  <br><input type="text"  name="begin" value="<?php echo $begin;?>">
  <br><input type="text"  name="end"   value="<?php echo $end  ;?>">

  <br><input type="checkbox" name="tag-pre" id="tag-pre"<?php echo attr_checked('tag-pre');?>>
      <label for="tag-pre">prefix tag</label>
      <br><input type="checkbox" name="txt-pre" id="txt-pre"<?php echo attr_checked('txt-pre');?>>
      <label for="txt-pre">prefix content</label>
  <br><input type="checkbox" name="txt-suf" id="txt-suf"<?php echo attr_checked('txt-suf');?>>
      <label for="txt-suf">suffix content</label>
  <br><input type="checkbox" name="tag-suf" id="tag-suf"<?php echo attr_checked('tag-suf');?>>
      <label for="tag-suf">suffix tag</label>
  <br>
  <br><input type="checkbox" name="wspace" id="wspace"<?php echo attr_checked('wspace');?>>
      <label for="wspace">blanc (#32) matches any whitespace character</label>
  <br><input type="checkbox" name="multiple" id="wspace"<?php echo attr_checked('multiple');?>>
      <label for="multiple">one or more blancs match any number of blancs/whitespaces</label>
  <br><input type="checkbox" name="icase"    id="icase"<?php echo attr_checked('icase');?>>
      <label for="icase">case insensitive</label>

  <br><button type="submit">submit</button>
</form>

<?php
  $html = new HtmlTextSearch($html_source);

  $opts=
  [ 'tag-pre' => HtmlTextSearch::RESULT_PREPEND_TAG,
    'txt-pre' => HtmlTextSearch::RESULT_PREPEND_TAG_CONTENT,
    'txt-suf' => HtmlTextSearch::RESULT_APPEND_TAG_CONTENT,
    'tag-suf' => HtmlTextSearch::RESULT_APPEND_TAG,
    'wspace'  => HtmlTextSearch::MATCH_BLANK_AS_WHITESPACE,
    'multiple'=> HtmlTextSearch::MATCH_BLANK_MULTIPLE,
    'icase'   => HtmlTextSearch::MATCH_CASE_INSENSITIVE
  ];
  $options = 0;
  foreach($opts as $k => $v)
    if(isset($_POST[$k]))
      $options |= $v;
  $results = $html->find_content($begin, $end, $options);
  var_dump($results);
?>

第八种办法

这个怎么样?

$escape=array('\'=>1,'^'=>1,'?'=>1,'+'=>1,'*'=>1,'{'=>1,'}'=>1,'('=>1,')'=>1,'['=>1,']'=>1,'|'=>1,'.'=>1,'$'=>1,'+'=>1,'/'=>1);
$pattern='/';
for($i=0;isset($begin[$i]);$i++){
if(ord($c=$begin[$i])<0x80||ord($c)>0xbf){
    if(isset($escape[$c]))
        $pattern.="([ trnvf]*<\/?[a-zA-Z]+>[ trnvf]*)*\$c";
    else
        $pattern.="([ trnvf]*<\/?[a-zA-Z]+>[ trnvf]*)*$c";
    }
    else
        $pattern.=$c;
}
$pattern.="(.|n|r)*";
for($i=0;isset($end[$i]);$i++){
if(ord($c=$end[$i])<0x80||ord($c)>0xbf){
    if(isset($escape[$c]))
        $pattern.="([ trnvf]*<\/?[a-zA-Z]+>[ trnvf]*)*\$c";
    else
        $pattern.="([ trnvf]*<\/?[a-zA-Z]+>[ trnvf]*)*$c";
    }
    else
        $pattern.=$c;
}
$pattern[17]='?';
$pattern.='(<\/?[a-zA-Z]+>)?/';
preg_match($pattern,$html,$a);
$match=$a[0];

第九种办法

PHP 解决方案:

PHPFiddle Demo

$html = '
        <html>
        <body>
        <p>Hello <em> 進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.</p>
        </body>
        </html>
        ';
$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

$matchHtmlTag = '(?:<.*?>)?';
$matchAllNonGreedy = '(?:.|r?n)*?';
$matchUnescapedCharNotAtEnd = '([^\\](?!$)|\.(?!$))';
$matchBeginWithTags = preg_replace(
    $matchUnescapedCharNotAtEnd, '$0' . $matchHtmlTag, preg_quote($begin));
$matchEndWithTags = preg_replace(
    $matchUnescapedCharNotAtEnd, '$0' . $matchHtmlTag, preg_quote($end));
$pattern = '/' . $matchBeginWithTags . $matchAllNonGreedy . $matchEndWithTags . '/';

preg_match($pattern, $html, $matches);
$html = $matches[0];

生成的正则表达式 ($ pattern):

Regex101 Demo

H(?:<.*?>)?e(?:<.*?>)?l(?:<.*?>)?l(?:<.*?>)?o(?:<.*?>)? (?:<.*?>)? 進 (?:<.*?>)? 撃 (?:<.*?>)?の(?:<.*?>)? 巨 (?:<.*?>)? 人 (?:<.*?>)?!(?:.|r?n)*?L(?:<.*?>)?o(?:<.*?>)?r(?:<.*?>)?e(?:<.*?>)?m(?:<.*?>)? (?:<.*?>)?i(?:<.*?>)?p(?:<.*?>)?s(?:<.*?>)?u(?:<.*?>)?m(?:<.*?>)?.

第十种办法

假设您的示例中的 random code<p></p> 内,我建议使用 domdocument 和 xpath,而不是正则表达式在您尝试做什么。

$html = '
        <html>
        <body>
        <div>nada blahhh <p>test paragraph</p> <em>blahh</em></div>
        <p>test</p>
        <span>this is test</span>
        <p>Hello <em> 進撃の巨人</em>!</p>
        <p>random code</p>
        <p>random code</p>
        <p>Lorem <span>ipsum<span>.</p>
        <div>nada blahhh <p>test paragraph</p> <em>blahh</em></div>
        <p>test</p>
        <span>this is test</span>
        </body>
        </html>
        ';
$begin = 'Hello 進撃の巨人!';
$begin = iconv ( 'iso-8859-1','utf-8' , $begin ); // had to use iconv it won't be needed in your case
$end = 'Lorem ipsum.';       
$doc = new DOMDocument();
$doc->loadHTML($html);

$xpath = new DOMXpath($doc);
// example 3: same as above with wildcard
$elements = $xpath->query("*/p");

if (!is_null($elements)) {
    $flag = 'no_output';
  foreach ($elements as $element) {
      if($flag=='prepare_for_output'){$flag='output';}
      if($element->nodeValue==$begin){
      $flag='prepare_for_output';
      }
      if($element->nodeValue==$end){
      $flag='no_output';
      }
      if($flag=='output') {
      echo $element->nodeValue."n";
      }
  }
}

http://sandbox.onlinephpfunctions.com/code/fa1095d98c6ef5c600f7b06366b4e0c4798a112f

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。