php preg_match 排除 html 标签/属性中的文本以找到剪切字符串的正确位置

我试图确定某些单词在 html 块中的绝对位置,但前提是它们在实际的 html 标记之外。例如,如果我想在本文中使用 preg_match 确定单词“join”的位置:


<p>There are 14 more days until our <a href="/somepage.html" target="_blank" rel="noreferrer noopener" aria-label="join us">holiday special</a> so come join us!</p>

我可以使用:


preg_match('/join/', $post_content, $matches, PREG_OFFSET_CAPTURE, $offset);

问题是这是在匹配 aria-label 属性中的词,而我需要的是紧接在链接之后的词。可以在<a>和之间进行匹配</a>,只是不在括号内。


我的实际最终目标是(我认为)除了最后一个元素之外的大部分内容:我正在修剪一个 html 块(不是完整的文档)以在特定的字数处截断。我试图确定最后一个单词以哪个字符结尾,然后将 html 块的左侧与右侧的 html 连接起来,因此所有 html 标签都可以优雅地关闭。我以为我让它工作了,直到我遇到一个例子,比如我展示了最后一个词也在 html 属性中,导致我在错误的位置拆分字符串。到目前为止,这是我的代码:


$post_content = strip_tags ( $p->post_content, "<a><br><p><ul><li>" );

$post_content_stripped = strip_tags ( $p->post_content );

$post_content_stripped = preg_replace("/[^A-Za-z0-9 ]/", ' ', $post_content_stripped);

$post_content_stripped = preg_replace("/\s+/", ' ', $post_content_stripped);

$post_content_stripped_array = explode ( " " , trim($post_content_stripped) );

$excerpt_wordcount = count( $post_content_stripped_array );

$cutpos = 0;

while($excerpt_wordcount>48){

    $thiswordrev = "/" . strrev($post_content_stripped_array[$excerpt_wordcount - 1]) . "/";

    preg_match($thiswordrev, strrev($post_content), $matches, PREG_OFFSET_CAPTURE, $cutpos);

    $cutpos = $matches[0][1] + (strlen($thiswordrev) - 2);

    array_pop($post_content_stripped_array);

    $excerpt_wordcount = count( $post_content_stripped_array );

}

if($pwordcount>$excerpt_wordcount){

    preg_match_all('/<\/?[^>]*>/', substr( $post_content, strlen($post_content) - $cutpos ), $closetags_result);

    $excerpt_closetags = "" . $closetags_result[0][0];

    $post_excerpt = substr( $post_content, 0, strlen($post_content) - $cutpos ) . $excerpt_closetags;

}else{

    $post_excerpt = $post_content;

}


但是在执行 preg_match 之前翻转所有括号很容易,或者我假设应该很容易让 preg_match 考虑到这一点。


一只萌萌小番薯
浏览 138回答 2
2回答

慕容3067478

不要使用正则表达式来解析 HTML。您有一个简单的目标:将文本内容限制为给定的字数,确保 HTML 保持有效。为此,我建议循环遍历文本节点,直到您计算出一定数量的单词,然后删除之后的所有内容。$dom = new DOMDocument();$dom->loadHTML($post_content);$xpath = new DOMXPath($dom);$all_text_nodes = $xpath->query("//text()");$words_left = 48;foreach( $all_text_nodes as $text_node) {&nbsp; &nbsp; $text = $text_node->textContent;&nbsp; &nbsp; $words = explode(" ", $text); // TODO: maybe preg_split on /\s/ to support more whitespace types&nbsp; &nbsp; $word_count = count($words);&nbsp; &nbsp; if( $word_count < $words_left) {&nbsp; &nbsp; &nbsp; &nbsp; $words_left -= $word_count;&nbsp; &nbsp; &nbsp; &nbsp; continue;&nbsp; &nbsp; }&nbsp; &nbsp; // reached the threshold&nbsp; &nbsp; $words_that_fit = implode(" ", array_slice($words, 0, $words_left));&nbsp; &nbsp; // If the above TODO is implemented, this will need to be adjusted to keep the specific whitespace characters&nbsp; &nbsp; $text_node->textContent = $words_that_fit;&nbsp; &nbsp; $remove_after = $text_node;&nbsp; &nbsp; while( $remove_after->parentNode) {&nbsp; &nbsp; &nbsp; &nbsp; while( $remove_after->nextSibling) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $remove_after->parentNode->removeChild($remove_after->nextSibling);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; $remove_after = $remove_after->parentNode;&nbsp; &nbsp; }&nbsp; &nbsp; break;}$output = substr($dom->saveHTML($dom->getElementsByTagName("body")->item(0)), strlen("<body>"), -strlen("</body>"));

喵喵时光机

好的,我想出了一个解决方法。我不知道这是否是最优雅的解决方案,所以如果有人看到更好的解决方案,我仍然很想听听,但现在我意识到我不必在我正在搜索的字符串中实际包含 html确定切割的位置,我只需要它是相同的长度。我抓取了所有的 html 元素,并创建了一个虚拟字符串,用相同数量的星号替换了所有元素:// create faux string with placeholders instead of html for search purposespreg_match_all('/<\/?[^>]*>/', $post_content, $alltags_result);$tagcount = count( $alltags_result );$post_content_dummy = $post_content;foreach($alltags_result[0] as $thistag){&nbsp; &nbsp; $post_content_dummy = str_replace($thistag, str_repeat("*",strlen($thistag)), $post_content_dummy);}然后我只是$post_content_dummy在 while 循环中使用而不是$post_content,以便找到切割位置,然后$post_content进行实际切割。到目前为止似乎工作正常。
打开App,查看更多内容
随时随地看视频慕课网APP