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

时间:2016-04-18 09:49:41

标签: php html regex dom

我有一个存储在字符串中的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句子不匹配。在运行strip_tags($html)之前我可以strpos(),但是显然我会在没有标签的情况下结束$html

  • 搜索变量的一部分,例如Hello,但这绝不安全,会提供很多匹配。

12 个答案:

答案 0 :(得分:12)

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

$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 demo

算法:

  • 通过将分隔符字符串拆分为单个字形来创建动态正则表达式模式(因为这些字符可以是Unicode字符,我建议使用preg_split('~(?<!^)(?=\X)~u', $end))并通过添加匹配模式(?:<[^<>]+>)?的可选标记来回填。 / LI>
  • 然后,当(?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*以匹配可选空格,并使用*进行量化(可以为零或更多)。

答案 1 :(得分:8)

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

简短说明:主要问题是保留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";

答案 2 :(得分:7)

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

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

例如,假设此标记出现在必须提取的部分之前的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表示,并将中间部分作为结果提取。

  4. 以下是代码:

    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上看到它。

答案 3 :(得分:5)

到目前为止,这可能不是最佳解决方案,但我喜欢开头讨论这样的&#34;谜语&#34;,所以这就是我的方法。

<?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/]+>)?,其中包含空格字符的特殊情况。

答案 4 :(得分:4)

你可以试试这个RegEx:

2nd

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

Live Demo on Regex101

正则表达式可缩短为:

docker build -t opencv:2.4.11 .

答案 5 :(得分:4)

只是为了好玩

<?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>

答案 6 :(得分:4)

这个怎么样?

$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];

答案 7 :(得分:4)

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

正则表达式专为文本处理而设计。尽管由于开销而使用正则表达式并不是最快的,但preg_函数是一种合理的折衷方案,可以保持代码小而简洁,同时只有在防止模式变得过于复杂时才会对性能产生很大的影响。

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

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

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

二进制搜索的工作方式如下:需要排序列表。您存储第一个和最后一个元素的索引+ 1。通过加法和整数除以2来计算平均值。通过右位移来执行除法和除法。如果找到的值为低,则将较小的索引var设置为当前索引,否则设置较大的索引var。停止索引差异1.如果搜索确切的值,请在找到的元素上提前中断。 0,(14 + 1)=&gt; 7; 7,15 =&gt; 11; 7,11 =&gt; 9; 7,9 =&gt; 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);
?>

答案 8 :(得分:2)

PHP解决方案:

PHPFiddle Demo

index(zamkniecie3) <- as.Date(index(zamkniecie3))

#MERGE! into one
all.in.one <- merge(zamkniecie1, zamkniecie2, zamkniecie3)

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

Regex101 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];

答案 9 :(得分:2)

假设您的示例中的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

答案 10 :(得分:2)

你可以使用这个概念,代码如下:

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

答案 11 :(得分:1)

不要试图使用正则表达式。

使用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示例文件中运行该代码,则应使用&#34;查看源代码&#34;来检查网页的来源。在浏览器中查看html标签。 &lt; p&gt;或者&lt; em&gt;应该在那里; - )