如何获取 HTML 内容中最中间元素的字符串位置?
How to get the string position of the middle-most element within HTML content?
我正在处理来自所见即所得编辑器的 HTML 格式的新闻文章,我需要找到它的中间部分,但是在 visual/HTML 上下文中,这意味着一个空的地方在两个根元素之间。比如说,如果您想将文章分成两页,那么每页的段落数应尽可能相同。
所有的根元素似乎都以段落的形式出现,这很容易计数,一个简单的
$p_count = substr_count($article_text, '<p');
Returns 开头段落标记的总数,然后我可以查找第 ($p_count/2)
次出现的段落的 strpos
。
但问题在于嵌入的推文,其中包含段落,有时出现在 blockquote > p
下,有时出现在 center > blockquote > p
.
下
所以我求助于 DOMDocument。这个小片段给了我中间的第 n 个元素(即使元素是 div 而不是段落,这很酷):
$dom = new DOMDocument();
$dom->loadHTML($article_text);
$body = $dom->getElementsByTagName('body');
$rootNodes = $body->item(0)->childNodes;
$empty_nodes = 0;
foreach($rootNodes as $node) {
if($node->nodeType === XML_TEXT_NODE && strlen(trim($node->nodeValue)) === 0) {
$empty_nodes++;
}
}
$total_elements = $rootNodes->length - $empty_nodes;
$middle_element = floor($total_elements / 2);
但是我现在如何在我的原始 HTML 源中找到这个中间元素的字符串偏移量,以便我可以指向文章文本字符串中的这个中间位置?特别是考虑到 DOMDocument 将我给它的 HTML 转换成一个完整的 HTML 页面(带有文档类型和头部等等),所以它的输出 HTML 比我的大原创HTML文章来源。
好的,我解决了。
我所做的是匹配文章中的所有 HTML 标签,使用 preg_match_all
的 PREG_OFFSET_CAPTURE
标志,它会记住模式匹配的字符偏移量。然后我依次遍历所有这些,并计算我在哪个深度;如果它是一个开始标签,我计算深度 +1
,并计算结束 -1
(注意 self-closing 标签)。每次在结束标记后深度变为零,我都将其视为关闭的另一个根元素。如果最后我到达深度 0
,我假设我算对了。
现在,我可以将我计数的根元素的数量除以 2 得到 middle-ish 个(奇数为 +-1),然后查看该元素的偏移量preg_match_all
先前报告的指数。
如果有人需要做同样的事情,完整的代码如下。
如果使用正则表达式编写 is_self_closing()
函数然后检查 in_array($self_closing_tags)
而不是 foreach
循环,它可能会加速,但在我的情况下它没有足以让我费心了。
function calculate_middle_of_article(string $text, bool $debug=false): ?int {
function is_self_closing(string $input, array $self_closing_tags): bool {
foreach($self_closing_tags as $tag) {
if(substr($input, 1, strlen($tag)) === $tag) {
return true;
}
}
return false;
}
$self_closing_tags = [
'!--',
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr',
'command',
'keygen',
'menuitem',
];
$regex = '/<("[^"]*"|\'[^\']*\'|[^\'">])*>/';
preg_match_all($regex, $text, $matches, PREG_OFFSET_CAPTURE);
$debug && print count($matches[0]) . " tags found \n";
$root_elements = [];
$depth = 0;
foreach($matches[0] as $match) {
if(!is_self_closing($match[0], $self_closing_tags)) {
$depth+= (substr($match[0], 1, 1) === '/') ? -1 : 1;
}
$debug && print "level {$depth} after tag: " . htmlentities($match[0]) . "\n";
if($depth === 0) {
$root_elements[]= $match;
}
}
$ok = ($depth === 0);
$debug && print ($ok ? 'ok' : 'not ok') . "\n";
// has to end at depth zero to confirm counting is correct
if(!$ok) {
return null;
}
$debug && print count($root_elements) . " root elements\n";
$element_index_at_middle = floor(count($root_elements)/2);
$half_char = $root_elements[$element_index_at_middle][1];
$debug && print "which makes the half the {$half_char}th character at the {$element_index_at_middle}th element\n";
return $half_char;
}
我正在处理来自所见即所得编辑器的 HTML 格式的新闻文章,我需要找到它的中间部分,但是在 visual/HTML 上下文中,这意味着一个空的地方在两个根元素之间。比如说,如果您想将文章分成两页,那么每页的段落数应尽可能相同。
所有的根元素似乎都以段落的形式出现,这很容易计数,一个简单的
$p_count = substr_count($article_text, '<p');
Returns 开头段落标记的总数,然后我可以查找第 ($p_count/2)
次出现的段落的 strpos
。
但问题在于嵌入的推文,其中包含段落,有时出现在 blockquote > p
下,有时出现在 center > blockquote > p
.
所以我求助于 DOMDocument。这个小片段给了我中间的第 n 个元素(即使元素是 div 而不是段落,这很酷):
$dom = new DOMDocument();
$dom->loadHTML($article_text);
$body = $dom->getElementsByTagName('body');
$rootNodes = $body->item(0)->childNodes;
$empty_nodes = 0;
foreach($rootNodes as $node) {
if($node->nodeType === XML_TEXT_NODE && strlen(trim($node->nodeValue)) === 0) {
$empty_nodes++;
}
}
$total_elements = $rootNodes->length - $empty_nodes;
$middle_element = floor($total_elements / 2);
但是我现在如何在我的原始 HTML 源中找到这个中间元素的字符串偏移量,以便我可以指向文章文本字符串中的这个中间位置?特别是考虑到 DOMDocument 将我给它的 HTML 转换成一个完整的 HTML 页面(带有文档类型和头部等等),所以它的输出 HTML 比我的大原创HTML文章来源。
好的,我解决了。
我所做的是匹配文章中的所有 HTML 标签,使用 preg_match_all
的 PREG_OFFSET_CAPTURE
标志,它会记住模式匹配的字符偏移量。然后我依次遍历所有这些,并计算我在哪个深度;如果它是一个开始标签,我计算深度 +1
,并计算结束 -1
(注意 self-closing 标签)。每次在结束标记后深度变为零,我都将其视为关闭的另一个根元素。如果最后我到达深度 0
,我假设我算对了。
现在,我可以将我计数的根元素的数量除以 2 得到 middle-ish 个(奇数为 +-1),然后查看该元素的偏移量preg_match_all
先前报告的指数。
如果有人需要做同样的事情,完整的代码如下。
如果使用正则表达式编写 is_self_closing()
函数然后检查 in_array($self_closing_tags)
而不是 foreach
循环,它可能会加速,但在我的情况下它没有足以让我费心了。
function calculate_middle_of_article(string $text, bool $debug=false): ?int {
function is_self_closing(string $input, array $self_closing_tags): bool {
foreach($self_closing_tags as $tag) {
if(substr($input, 1, strlen($tag)) === $tag) {
return true;
}
}
return false;
}
$self_closing_tags = [
'!--',
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr',
'command',
'keygen',
'menuitem',
];
$regex = '/<("[^"]*"|\'[^\']*\'|[^\'">])*>/';
preg_match_all($regex, $text, $matches, PREG_OFFSET_CAPTURE);
$debug && print count($matches[0]) . " tags found \n";
$root_elements = [];
$depth = 0;
foreach($matches[0] as $match) {
if(!is_self_closing($match[0], $self_closing_tags)) {
$depth+= (substr($match[0], 1, 1) === '/') ? -1 : 1;
}
$debug && print "level {$depth} after tag: " . htmlentities($match[0]) . "\n";
if($depth === 0) {
$root_elements[]= $match;
}
}
$ok = ($depth === 0);
$debug && print ($ok ? 'ok' : 'not ok') . "\n";
// has to end at depth zero to confirm counting is correct
if(!$ok) {
return null;
}
$debug && print count($root_elements) . " root elements\n";
$element_index_at_middle = floor(count($root_elements)/2);
$half_char = $root_elements[$element_index_at_middle][1];
$debug && print "which makes the half the {$half_char}th character at the {$element_index_at_middle}th element\n";
return $half_char;
}