我将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>進 撃の巨人</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+ 空格,
begin
和end
模式中的任何空格都可以替换为's+
子模式,以匹配输入字符串中任何类型的 1+ 空格字符。 - 更新 2:辅助
$begin = preg_replace('~'s+~u', ' ', $begin);
和$end = preg_replace('~'s+~u', ' ', $end);
是必需的,以考虑输入字符串中的 1+ 空格。 - 要考虑 HTML 实体,请在可选部分添加另一个子模式:
&#?''w+;
,它还将匹配
和类似{
实体。它还在前面加上'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 接口的解决方案,使用以下算法:
获取 HTML 文档的文本内容,并确定两个子字符串(开始/结束(所在的两个偏移量。
然后遍历 DOM 文本节点,跟踪这些节点所在的偏移量。在两个边界偏移中的任何一个相交的节点中,将插入预定义的分隔符(
|
(。该分隔符不应存在于 HTML 字符串中。因此,它加倍(||
,||||
,...(,直到满足该条件;最后通过此分隔符拆分 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 是一个小类,旨在处理同一 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 标记。
或应该在那里;-(