在 HTML 中搜索 2 个短语(忽略所有标签)并去除其他所有内容


Search HTML for 2 phrases (ignoring all tags) and strip everything else

我将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 code'nrandom code'n<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 演示

算法:

  • 通过将分隔符字符串拆分为单个字素来创建动态正则表达式模式(因为这些可以是 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+;))*'. - 前导和尾随分隔符,带有用于标记匹配的可选子模式和内部的(.*?)(可能不需要捕获(。
  • ~u修饰符是必需的,因为要处理 Unicode 字符串。
  • 更新:要考虑 1+ 空格,beginend模式中的任何空格都可以替换为's+子模式,以匹配输入字符串中任何类型的 1+ 空格字符。
  • 更新 2:辅助$begin = preg_replace('~'s+~u', ' ', $begin);$end = preg_replace('~'s+~u', ' ', $end);是必需的,以考虑输入字符串中的 1+ 空格。
  • 要考虑 HTML 实体,请在可选部分添加另一个子模式:&#?''w+; ,它还将匹配&nbsp;和类似&#123;实体。它还在前面加上 's* 以匹配可选的空格,并使用 *(可以是零或更多(进行量化。

我真的很想写一个正则表达式解决方案。但是我之前有一些很好和复杂的解决方案。因此,这是一个非正则表达式解决方案。

简短解释:主要问题是保留 HTML 标记。如果去除了HTML标签,我们可以很容易地搜索文本。所以:剥离这些!我们可以轻松地搜索剥离的内容,并生成要剪切的子字符串。然后,尝试从 HTML 中删除此子字符串,同时保留标记。

优势:

  • 搜索很容易并且独立于 HTML,如果需要,您也可以使用正则表达式进行搜索
  • 需求是可扩展的:您可以轻松添加完整的多字节支持、对实体和空白折叠的支持等
  • 相对较快(有可能,直接正则表达式可以更快(
  • 不接触原始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 时有其局限性。像我之前的许多人一样,我将提到这个著名的答案。

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

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

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

许多正则表达式解决方案会偶然发现这一点,并返回一个从这个开始p标签中间开始的字符串。

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

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

或者,出现一些松散的小于号和大于号(假设在注释或属性值中(:

<!-- 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>.

您会发现此代码也比正则表达式替代方案更容易维护。

看到它在 eval.in 上运行。

到目前为止,这可能不是最佳解决方案,但我喜欢为这样的"谜语"而绞尽脑汁,所以这是我的方法。

<?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/]+>)?,空格字符有一个特殊大小写。
你可以

试试这个正则表达式:

(.*?)  # 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捕获组

正则表达式101上的现场演示

正则表达式可以缩短为:

(.*?)(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 code'n        random code'n        <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

中的一个解决方案,OOP 是一个小类,旨在处理同一 HTML 源上的许多搜索。它已经是一种处理扩展类似问题的方法,例如在下一个标记边界之前添加前面和后面的内容。它还没有声称是一个完美的解决方案,但它很容易扩展。

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

二分搜索的工作方式是这样的:需要一个排序列表。存储第一个和最后一个元素+1的索引。通过加法和整数除以 2 来计算平均值。划分和地板由右位移完成。如果找到的值为低,则将较少的索引 var 设置为当前索引,否则设置为较大的索引。在索引差异 1 时停止。如果搜索精确值,请在找到元素时尽早中断。0,(14+1( => 7 ;7,15 => 11 ;7,11 => 9 ;7,9 => 8 ;8-7 = 差异 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.="([ 't'r'n'v'f]*<''/?[a-zA-Z]+>[ 't'r'n'v'f]*)*''$c";
    else
        $pattern.="([ 't'r'n'v'f]*<''/?[a-zA-Z]+>[ 't'r'n'v'f]*)*$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.="([ 't'r'n'v'f]*<''/?[a-zA-Z]+>[ 't'r'n'v'f]*)*''$c";
    else
        $pattern.="([ 't'r'n'v'f]*<''/?[a-zA-Z]+>[ 't'r'n'v'f]*)*$c";
    }
    else
        $pattern.=$c;
}
$pattern[17]='?';
$pattern.='(<''/?[a-zA-Z]+>)?/';
preg_match($pattern,$html,$a);
$match=$a[0];

PHP 解决方案:

PHPFiddle 演示

$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(:

正则表达式 101 演示

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

你可以用这个概念,代码给出如下

        <html lang="en-US">
        <head>
        <title>HTML Unicode UTF-8</title>
        <meta charset="utf-8">
        </head>
        <body>
        <?php
        $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.';
        $stripped =strip_tags($html);
        if (strpos($stripped, $end) !== false) {
            $final =str_replace($begin,"",$stripped);
           echo str_replace($end,"",$final);
        }
        ?>
        </body>  
        </html>

不要在尝试使用正则表达式时打破你的思想。

使用 PHP 的 DOM 库: http://php.net/manual/en/book.dom.php

<?php
    header('Content-Type: text/html; charset=UTF-8');
    $html = '
            <html>
            <body>
            <p>Hello <em>進撃の巨人</em>!</p>
            random code
            random code
            <p>Lorem <span>ipsum<span>.</p>
            </body>
            </html>
            ';
    $doc = new DOMDocument();
    $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    $body_elements = $doc->getElementsByTagName("body"); 
    $code = '';
    foreach ($body_elements as $element) { 
        $children  = $element->childNodes;
        foreach ($children as $child) 
        { 
            $code.= $element->ownerDocument->saveHTML($child);
        }
    }
    echo $code;
?>

如果在 php 示例文件中运行该代码,则应使用浏览器中的"查看源代码"检查网页的源代码以查看 html 标记。

应该在那里;-(

相关文章: