关闭字符串中的打开HTML标记

时间:2010-09-28 06:33:08

标签: php regex string

情境是一个字符串,结果如下:

<p>This is some text and here is a <strong>bold text then the post stop here....</p>

因为该函数返回文本的预告片(摘要),所以它会在某些单词后停止。在这种情况下,标签强不关闭。但整个字符串都包含在一个段落中。

是否可以将上述结果/输出转换为以下内容:

<p>This is some text and here is a <strong>bold text then the post stop here....</strong></p>

我不知道从哪里开始。问题是..我在网上发现了一个函数,它使用正则表达式,但它将结束标记放在字符串之后..因此它不会验证,因为我想要段落标记中的所有打开/关闭标记。我找到的功能也是这样做的错误:

<p>This is some text and here is a <strong>bold text then the post stop here....</p></strong>

我想知道标签可以是强大的,斜体的,任何东西。这就是为什么我无法附加函数并在函数中手动关闭它。任何可以为我做的模式?

9 个答案:

答案 0 :(得分:32)

这是我之前使用过的一个功能,效果非常好:

function closetags($html) {
    preg_match_all('#<(?!meta|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result);
    $openedtags = $result[1];
    preg_match_all('#</([a-z]+)>#iU', $html, $result);
    $closedtags = $result[1];
    $len_opened = count($openedtags);
    if (count($closedtags) == $len_opened) {
        return $html;
    }
    $openedtags = array_reverse($openedtags);
    for ($i=0; $i < $len_opened; $i++) {
        if (!in_array($openedtags[$i], $closedtags)) {
            $html .= '</'.$openedtags[$i].'>';
        } else {
            unset($closedtags[array_search($openedtags[$i], $closedtags)]);
        }
    }
    return $html;
} 

就个人而言,我不会使用regexp,而是像Tidy这样的库。这将类似于以下内容:

$str = '<p>This is some text and here is a <strong>bold text then the post stop here....</p>';
$tidy = new Tidy();
$clean = $tidy->repairString($str, array(
    'output-xml' => true,
    'input-xml' => true
));
echo $clean;

答案 1 :(得分:8)

原始答案的一个小修改......而原始答案正确剥离了标签。我发现在我的截断期间,我最终可能会被切碎的标签。例如:

This text has some <b>in it</b>

截断字符21会导致:

This text has some <

以下代码构建于下一个最佳答案并修复此问题。

function truncateHTML($html, $length)
{
    $truncatedText = substr($html, $length);
    $pos = strpos($truncatedText, ">");
    if($pos !== false)
    {
        $html = substr($html, 0,$length + $pos + 1);
    }
    else
    {
        $html = substr($html, 0,$length);
    }

    preg_match_all('#<(?!meta|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result);
    $openedtags = $result[1];

    preg_match_all('#</([a-z]+)>#iU', $html, $result);
    $closedtags = $result[1];

    $len_opened = count($openedtags);

    if (count($closedtags) == $len_opened)
    {
        return $html;
    }

    $openedtags = array_reverse($openedtags);
    for ($i=0; $i < $len_opened; $i++)
    {
        if (!in_array($openedtags[$i], $closedtags))
        {
            $html .= '</'.$openedtags[$i].'>';
        }
        else
        {
            unset($closedtags[array_search($openedtags[$i], $closedtags)]);
        }
    }


    return $html;
}


$str = "This text has <b>bold</b> in it</b>";
print "Test 1 - Truncate with no tag: " . truncateHTML($str, 5) . "<br>\n";
print "Test 2 - Truncate at start of tag: " . truncateHTML($str, 20) . "<br>\n";
print "Test 3 - Truncate in the middle of a tag: " . truncateHTML($str, 16) . "<br>\n";
print "Test 4: - Truncate with less text: " . truncateHTML($str, 300) . "<br>\n";

希望它可以帮助那些人。

答案 2 :(得分:3)

还有许多其他变量需要解决才能提供完整的解决方案,但不在您的问题范围内。

但是,我建议使用HTML Tidy之类的内容,特别是repairFilerepaireString方法。

答案 3 :(得分:3)

这个PHP方法总是对我有用。它将关闭所有未关闭的HTML标记。

function closetags($html) {
    preg_match_all('#<([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result);
    $openedtags = $result[1];

    preg_match_all('#</([a-z]+)>#iU', $html, $result);
    $closedtags = $result[1];
    $len_opened = count($openedtags);
    if (count($closedtags) == $len_opened) {
        return $html;
    }
    $openedtags = array_reverse($openedtags);
    for ($i=0; $i < $len_opened; $i++) {
        if (!in_array($openedtags[$i], $closedtags)){
            $html .= '</'.$openedtags[$i].'>';
        } else {
            unset($closedtags[array_search($openedtags[$i], $closedtags)]);
        }
    }
    return $html;
}

答案 4 :(得分:1)

那么使用PHP的原生DOMDocument类呢?它固有地解析HTML并纠正语法错误...... E.g:

$fragment = "<article><h3>Title</h3><p>Unclosed";
$doc = new DOMDocument();
$doc->loadHTML($fragment);
$correctFragment = $doc->getElementsByTagName('body')->item(0)->C14N();
echo $correctFragment;

然而,这种方法有几个缺点。 首先,它将原始片段包装在<body>标记内。您可以通过类似(preg_)replace()或通过某些自定义innerHTML()函数替换...->C14N()函数来轻松删除它,例如在http://php.net/manual/en/book.dom.php#89718中建议的。 第二个陷阱是PHP在实体中抛出了无效标签。如果使用HTML5或自定义标签,则会发出警告(尽管如此,它仍会正确进行)。

答案 5 :(得分:0)

使用正则表达式不是理想的方法。您应该使用html解析器来创建有效的文档对象模型。

作为第二个选项,根据您的需要,您可以使用正则表达式从字符串中删除任何和所有html标记,然后再将其放入<p>标记。

答案 6 :(得分:0)

我已经完成了这段代码,因此非常正确地完成了这项工作......

这是旧学但有效率,我添加了一个标志来删除未完成的标签,例如“blah blah http:// stackoverfl”

public function getOpennedTags(&$string, $removeInclompleteTagEndTagIfExists = true) {

    $tags = array();
    $tagOpened = false;
    $tagName = '';
    $tagNameLogged = false;
    $closingTag = false;

    foreach (str_split($string) as $c) {
        if ($tagOpened && $c == '>') {
            $tagOpened = false;
            if ($closingTag) {
                array_pop($tags);
                $closingTag = false;
                $tagName = '';
            }
            if ($tagName) {
                array_push($tags, $tagName);
            }
        }
        if ($tagOpened && $c == ' ') {
            $tagNameLogged = true;
        }
        if ($tagOpened && $c == '/') {
            if ($tagName) {
                //orphan tag
                $tagOpened = false;
                $tagName = '';
            } else {
                //closingTag
                $closingTag = true;
            }
        }
        if ($tagOpened && !$tagNameLogged) {
            $tagName .= $c;
        }
        if (!$tagOpened && $c == '<') {
            $tagNameLogged = false;
            $tagName = '';
            $tagOpened = true;
            $closingTag = false;
        }
    }

    if ($removeInclompleteTagEndTagIfExists && $tagOpened) {
        // an tag has been cut for exemaple ' blabh blah <a href="sdfoefzofk' so closing the tag will not help...
        // let's remove this ugly piece of tag
        $pos = strrpos($string, '<');
        $string = substr($string, 0, $pos);
    }

    return $tags;
}

用法示例:

$tagsToClose = $stringHelper->getOpennedTags($val);
$tagsToClose = array_reverse($tagsToClose);

foreach ($tagsToClose as $tag) {
    $val .= "</$tag>";
}

答案 7 :(得分:0)

如果安装了整洁模块,请使用php tidy extension:

tidy_repair_string($html)

reference

答案 8 :(得分:0)

这对我来说可以关闭脚本中所有打开的HTML标记。

<?php
function closetags($html) {
preg_match_all('#<([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result);
$openedtags = $result[1];
preg_match_all('#</([a-z]+)>#iU', $html, $result);
$closedtags = $result[1];
$len_opened = count($openedtags);
if (count($closedtags) == $len_opened) {
    return $html;
}
$openedtags = array_reverse($openedtags);
for ($i=0; $i < $len_opened; $i++) {
    if (!in_array($openedtags[$i], $closedtags)) {
        $html .= '</'.$openedtags[$i].'>';
    } else {
        unset($closedtags[array_search($openedtags[$i], $closedtags)]);
    }
}
return $html;
}