Close open HTML tags in a string

Viewed 28560

Situation is a string that results in something like this:

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

Because the function returns a teaser (summary) of the text, it stops after certain words. Where in this case the tag strong is not closed. But the whole string is wrapped in a paragraph.

Is it possible to convert the above result/output to the following:

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

I do not know where to begin. The problem is that.. I found a function on the web which does it regex, but it puts the closing tag after the string.. therefore it won't validate because I want all open/close tags within the paragraph tags. The function I found does this which is wrong also:

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

I want to know that the tag can be strong, italic, anything. That's why I cannot append the function and close it manually in the function. Any pattern that can do it for me?

10 Answers

if tidy module is installed, use php tidy extension:

tidy_repair_string($html)

reference

This is works for me to close any open HTML tags in a script.

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

An up-to-date solution with parsing HTML would be:

function fix_html($html) {
    $dom = new DOMDocument();
    $dom->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
    return $dom->saveHTML();
}

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD is needed to avoid implementing doctype, html and body.. the rest looks pretty obvious :)

UPDATE: After some testing noticed, that the solution above ruins a correct layout time-after-time. The following works well, though:

function fix_html($html) {
    $dom = new DOMDocument();
    $dom->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) );
    $return = '';
    foreach ( $dom->getElementsByTagName( 'body' )->item(0)->childNodes as $v ) {
        $return .= $dom->saveHTML( $v );
    }
    return $return;
}
Related