Commit a16b5639 authored by alexpott's avatar alexpott

Issue #2195745 by sun: Replace _filter_htmlcorrector() with a utility class in core.

parent e71d749e
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
* API functions for processing and sending e-mail. * API functions for processing and sending e-mail.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
/** /**
* Composes and optionally sends an e-mail message. * Composes and optionally sends an e-mail message.
* *
...@@ -288,7 +291,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) { ...@@ -288,7 +291,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags; $allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags;
// Make sure tags, entities and attributes are well-formed and properly nested. // Make sure tags, entities and attributes are well-formed and properly nested.
$string = _filter_htmlcorrector(filter_xss($string, $allowed_tags)); $string = Html::normalize(Xss::filter($string, $allowed_tags));
// Apply inline styles. // Apply inline styles.
$string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string); $string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string);
......
<?php
/**
* @file
* Contains \Drupal\Component\Utility\Html.
*/
namespace Drupal\Component\Utility;
/**
* Provides DOMDocument helpers for parsing and serializing HTML strings.
*/
class Html {
/**
* Normalizes an HTML snippet.
*
* This function is essentially \DOMDocument::normalizeDocument(), but
* operates on an HTML string instead of a \DOMDocument.
*
* @param string $html
* The HTML string to normalize.
*
* @return string
* The normalized HTML string.
*/
public static function normalize($html) {
$document = static::load($html);
return static::serialize($document);
}
/**
* Parses an HTML snippet and returns it as a DOM object.
*
* This function loads the body part of a partial (X)HTML document and returns
* a full \DOMDocument object that represents this document.
*
* Use \Drupal\Component\Utility\Html::serialize() to serialize this
* \DOMDocument back to a string.
*
* @param string $html
* The partial (X)HTML snippet to load. Invalid markup will be corrected on
* import.
*
* @return \DOMDocument
* A \DOMDocument that represents the loaded (X)HTML snippet.
*/
public static function load($html) {
$document = <<<EOD
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>
<body>!html</body>
</html>
EOD;
// PHP's \DOMDocument serialization adds straw whitespace in case the markup
// of the wrapping document contains newlines, so ensure to remove all
// newlines before injecting the actual HTML body to process.
$document = strtr($document, array("\n" => '', '!html' => $html));
$dom = new \DOMDocument();
// Ignore warnings during HTML soup loading.
@$dom->loadHTML($document);
return $dom;
}
/**
* Converts the body of a \DOMDocument back to an HTML snippet.
*
* The function serializes the body part of a \DOMDocument back to an (X)HTML
* snippet. The resulting (X)HTML snippet will be properly formatted to be
* compatible with HTML user agents.
*
* @param \DOMDocument $document
* A \DOMDocument object to serialize, only the tags below the first <body>
* node will be converted.
*
* @return string
* A valid (X)HTML snippet, as a string.
*/
public static function serialize(\DOMDocument $document) {
$body_node = $document->getElementsByTagName('body')->item(0);
$html = '';
foreach ($body_node->getElementsByTagName('script') as $node) {
static::escapeCdataElement($node);
}
foreach ($body_node->getElementsByTagName('style') as $node) {
static::escapeCdataElement($node, '/*', '*/');
}
foreach ($body_node->childNodes as $node) {
$html .= $document->saveXML($node);
}
return $html;
}
/**
* Adds comments around a <!CDATA section in a \DOMNode.
*
* \DOMDocument::loadHTML() in \Drupal\Component\Utility\Html::load() makes
* CDATA sections from the contents of inline script and style tags. This can
* cause HTML4 browsers to throw exceptions.
*
* This function attempts to solve the problem by creating a
* \DOMDocumentFragment to comment the CDATA tag.
*
* @param \DOMNode $node
* The element potentially containing a CDATA node.
* @param string $comment_start
* (optional) A string to use as a comment start marker to escape the CDATA
* declaration. Defaults to '//'.
* @param string $comment_end
* (optional) A string to use as a comment end marker to escape the CDATA
* declaration. Defaults to an empty string.
*/
public static function escapeCdataElement(\DOMNode $node, $comment_start = '//', $comment_end = '') {
foreach ($node->childNodes as $child_node) {
if ($child_node instanceof \DOMCdataSection) {
$embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
$embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";
// Prevent invalid cdata escaping as this would throw a DOM error.
// This is the same behavior as found in libxml2.
// Related W3C standard: http://www.w3.org/TR/REC-xml/#dt-cdsection
// Fix explanation: http://en.wikipedia.org/wiki/CDATA#Nesting
$data = str_replace(']]>', ']]]]><![CDATA[>', $child_node->data);
$fragment = $node->ownerDocument->createDocumentFragment();
$fragment->appendXML($embed_prefix . $data . $embed_suffix);
$node->appendChild($fragment);
$node->removeChild($child_node);
}
}
}
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Adds bindings for client-side "text editors" to text formats. * Adds bindings for client-side "text editors" to text formats.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\editor\Entity\Editor; use Drupal\editor\Entity\Editor;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
...@@ -661,7 +662,7 @@ function _editor_get_processed_text_fields(ContentEntityInterface $entity) { ...@@ -661,7 +662,7 @@ function _editor_get_processed_text_fields(ContentEntityInterface $entity) {
* An array of all found UUIDs. * An array of all found UUIDs.
*/ */
function _editor_parse_file_uuids($text) { function _editor_parse_file_uuids($text) {
$dom = filter_dom_load($text); $dom = Html::load($text);
$xpath = new \DOMXPath($dom); $xpath = new \DOMXPath($dom);
$uuids = array(); $uuids = array();
foreach ($xpath->query('//*[@data-editor-file-uuid]') as $node) { foreach ($xpath->query('//*[@data-editor-file-uuid]') as $node) {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* Attach custom data fields to Drupal entities. * Attach custom data fields to Drupal entities.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute;
use Drupal\entity\Entity\EntityViewDisplay; use Drupal\entity\Entity\EntityViewDisplay;
...@@ -304,7 +306,7 @@ function field_cache_clear() { ...@@ -304,7 +306,7 @@ function field_cache_clear() {
* UTF-8. * UTF-8.
*/ */
function field_filter_xss($string) { function field_filter_xss($string) {
return _filter_htmlcorrector(filter_xss($string, _field_filter_xss_allowed_tags())); return Html::normalize(Xss::filter($string, _field_filter_xss_allowed_tags()));
} }
/** /**
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Field module functionality for the File module. * Field module functionality for the File module.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\field\FieldInterface; use Drupal\field\FieldInterface;
/** /**
...@@ -181,8 +182,8 @@ function theme_file_upload_help($variables) { ...@@ -181,8 +182,8 @@ function theme_file_upload_help($variables) {
$descriptions = array(); $descriptions = array();
if (strlen($description)) { if (!empty($description)) {
$descriptions[] = _filter_htmlcorrector($description); $descriptions[] = Html::normalize($description);
} }
if (isset($cardinality)) { if (isset($cardinality)) {
if ($cardinality == -1) { if ($cardinality == -1) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Framework for handling the filtering of content. * Framework for handling the filtering of content.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
...@@ -693,103 +694,6 @@ function _filter_tips($format_id, $long = FALSE) { ...@@ -693,103 +694,6 @@ function _filter_tips($format_id, $long = FALSE) {
return $tips; return $tips;
} }
/**
* Parses an HTML snippet and returns it as a DOM object.
*
* This function loads the body part of a partial (X)HTML document and returns
* a full DOMDocument object that represents this document. You can use
* filter_dom_serialize() to serialize this DOMDocument back to a XHTML
* snippet.
*
* @param $text
* The partial (X)HTML snippet to load. Invalid markup will be corrected on
* import.
*
* @return
* A DOMDocument that represents the loaded (X)HTML snippet.
*/
function filter_dom_load($text) {
$dom_document = new DOMDocument();
// Ignore warnings during HTML soup loading.
@$dom_document->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $text . '</body></html>');
return $dom_document;
}
/**
* Converts a DOM object back to an HTML snippet.
*
* The function serializes the body part of a DOMDocument back to an XHTML
* snippet. The resulting XHTML snippet will be properly formatted to be
* compatible with HTML user agents.
*
* @param $dom_document
* A DOMDocument object to serialize, only the tags below
* the first <body> node will be converted.
*
* @return
* A valid (X)HTML snippet, as a string.
*/
function filter_dom_serialize($dom_document) {
$body_node = $dom_document->getElementsByTagName('body')->item(0);
$body_content = '';
foreach ($body_node->getElementsByTagName('script') as $node) {
filter_dom_serialize_escape_cdata_element($dom_document, $node);
}
foreach ($body_node->getElementsByTagName('style') as $node) {
filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
}
foreach ($body_node->childNodes as $child_node) {
$body_content .= $dom_document->saveXML($child_node);
}
return $body_content;
}
/**
* Adds comments around the <!CDATA section in a dom element.
*
* DOMDocument::loadHTML in filter_dom_load() makes CDATA sections from the
* contents of inline script and style tags. This can cause HTML 4 browsers to
* throw exceptions.
*
* This function attempts to solve the problem by creating a DocumentFragment
* and imitating the behavior in drupal_get_js(), commenting the CDATA tag.
*
* @param $dom_document
* The DOMDocument containing the $dom_element.
* @param $dom_element
* The element potentially containing a CDATA node.
* @param $comment_start
* (optional) A string to use as a comment start marker to escape the CDATA
* declaration. Defaults to '//'.
* @param $comment_end
* (optional) A string to use as a comment end marker to escape the CDATA
* declaration. Defaults to an empty string.
*/
function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') {
foreach ($dom_element->childNodes as $node) {
if (get_class($node) == 'DOMCdataSection') {
// See drupal_get_js(). This code is more or less duplicated there.
$embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
$embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";
// Prevent invalid cdata escaping as this would throw a DOM error.
// This is the same behavior as found in libxml2.
// Related W3C standard: http://www.w3.org/TR/REC-xml/#dt-cdsection
// Fix explanation: http://en.wikipedia.org/wiki/CDATA#Nesting
$data = str_replace(']]>', ']]]]><![CDATA[>', $node->data);
$fragment = $dom_document->createDocumentFragment();
$fragment->appendXML($embed_prefix . $data . $embed_suffix);
$dom_element->appendChild($fragment);
$dom_element->removeChild($node);
}
}
}
/** /**
* Prepares variables for text format guideline templates. * Prepares variables for text format guideline templates.
* *
...@@ -878,12 +782,12 @@ function _filter_html($text, $filter) { ...@@ -878,12 +782,12 @@ function _filter_html($text, $filter) {
$text = filter_xss($text, $allowed_tags); $text = filter_xss($text, $allowed_tags);
if ($filter->settings['filter_html_nofollow']) { if ($filter->settings['filter_html_nofollow']) {
$html_dom = filter_dom_load($text); $html_dom = Html::load($text);
$links = $html_dom->getElementsByTagName('a'); $links = $html_dom->getElementsByTagName('a');
foreach ($links as $link) { foreach ($links as $link) {
$link->setAttribute('rel', 'nofollow'); $link->setAttribute('rel', 'nofollow');
} }
$text = filter_dom_serialize($html_dom); $text = Html::serialize($html_dom);
} }
return trim($text); return trim($text);
...@@ -1127,13 +1031,6 @@ function _filter_url_trim($text, $length = NULL) { ...@@ -1127,13 +1031,6 @@ function _filter_url_trim($text, $length = NULL) {
return $text; return $text;
} }
/**
* Scans the input and makes sure that HTML tags are properly closed.
*/
function _filter_htmlcorrector($text) {
return filter_dom_serialize(filter_dom_load($text));
}
/** /**
* Converts line breaks into <p> and <br> in an intelligent fashion. * Converts line breaks into <p> and <br> in an intelligent fashion.
* *
...@@ -1219,7 +1116,7 @@ function _filter_html_image_secure_process($text) { ...@@ -1219,7 +1116,7 @@ function _filter_html_image_secure_process($text) {
// Find the directory on the server where index.php resides. // Find the directory on the server where index.php resides.
$local_dir = DRUPAL_ROOT . '/'; $local_dir = DRUPAL_ROOT . '/';
$html_dom = filter_dom_load($text); $html_dom = Html::load($text);
$images = $html_dom->getElementsByTagName('img'); $images = $html_dom->getElementsByTagName('img');
foreach ($images as $image) { foreach ($images as $image) {
$src = $image->getAttribute('src'); $src = $image->getAttribute('src');
...@@ -1245,7 +1142,7 @@ function _filter_html_image_secure_process($text) { ...@@ -1245,7 +1142,7 @@ function _filter_html_image_secure_process($text) {
// indicator. See filter_filter_secure_image_alter(). // indicator. See filter_filter_secure_image_alter().
\Drupal::moduleHandler()->alter('filter_secure_image', $image); \Drupal::moduleHandler()->alter('filter_secure_image', $image);
} }
$text = filter_dom_serialize($html_dom); $text = Html::serialize($html_dom);
return $text; return $text;
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\filter\Plugin\Filter; namespace Drupal\filter\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
...@@ -30,7 +31,7 @@ class FilterCaption extends FilterBase { ...@@ -30,7 +31,7 @@ class FilterCaption extends FilterBase {
public function process($text, $langcode, $cache, $cache_id) { public function process($text, $langcode, $cache, $cache_id) {
if (stristr($text, 'data-caption') !== FALSE || stristr($text, 'data-align') !== FALSE) { if (stristr($text, 'data-caption') !== FALSE || stristr($text, 'data-align') !== FALSE) {
$dom = filter_dom_load($text); $dom = Html::load($text);
$xpath = new \DOMXPath($dom); $xpath = new \DOMXPath($dom);
foreach ($xpath->query('//*[@data-caption or @data-align]') as $node) { foreach ($xpath->query('//*[@data-caption or @data-align]') as $node) {
$caption = NULL; $caption = NULL;
...@@ -82,7 +83,7 @@ public function process($text, $langcode, $cache, $cache_id) { ...@@ -82,7 +83,7 @@ public function process($text, $langcode, $cache, $cache_id) {
$altered_html = drupal_render($filter_caption); $altered_html = drupal_render($filter_caption);
// Load the altered HTML into a new DOMDocument and retrieve the element. // Load the altered HTML into a new DOMDocument and retrieve the element.
$updated_node = filter_dom_load($altered_html)->getElementsByTagName('body') $updated_node = Html::load($altered_html)->getElementsByTagName('body')
->item(0) ->item(0)
->childNodes ->childNodes
->item(0); ->item(0);
...@@ -94,7 +95,7 @@ public function process($text, $langcode, $cache, $cache_id) { ...@@ -94,7 +95,7 @@ public function process($text, $langcode, $cache, $cache_id) {
$node->parentNode->replaceChild($updated_node, $node); $node->parentNode->replaceChild($updated_node, $node);
} }
return filter_dom_serialize($dom); return Html::serialize($dom);
} }
return $text; return $text;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\filter\Plugin\Filter; namespace Drupal\filter\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\filter\Plugin\FilterBase; use Drupal\filter\Plugin\FilterBase;
/** /**
...@@ -25,7 +26,7 @@ class FilterHtmlCorrector extends FilterBase { ...@@ -25,7 +26,7 @@ class FilterHtmlCorrector extends FilterBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function process($text, $langcode, $cache, $cache_id) { public function process($text, $langcode, $cache, $cache_id) {
return _filter_htmlcorrector($text); return Html::normalize($text);
} }
} }
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\filter\Tests; namespace Drupal\filter\Tests;
use Drupal\Component\Utility\Html;
use Drupal\simpletest\DrupalUnitTestBase; use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\filter\FilterBag; use Drupal\filter\FilterBag;
...@@ -741,117 +742,117 @@ function testUrlFilterContent() { ...@@ -741,117 +742,117 @@ function testUrlFilterContent() {
*/ */
function testHtmlCorrectorFilter() { function testHtmlCorrectorFilter() {
// Tag closing. // Tag closing.
$f = _filter_htmlcorrector('<p>text'); $f = Html::normalize('<p>text');
$this->assertEqual($f, '<p>text</p>', 'HTML corrector -- tag closing at the end of input.'); $this->assertEqual($f, '<p>text</p>', 'HTML corrector -- tag closing at the end of input.');
$f = _filter_htmlcorrector('<p>text<p><p>text'); $f = Html::normalize('<p>text<p><p>text');
$this->assertEqual($f, '<p>text</p><p></p><p>text</p>', 'HTML corrector -- tag closing.'); $this->assertEqual($f, '<p>text</p><p></p><p>text</p>', 'HTML corrector -- tag closing.');
$f = _filter_htmlcorrector("<ul><li>e1<li>e2"); $f = Html::normalize("<ul><li>e1<li>e2");
$this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", 'HTML corrector -- unclosed list tags.'); $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", 'HTML corrector -- unclosed list tags.');
$f = _filter_htmlcorrector('<div id="d">content'); $f = Html::normalize('<div id="d">content');
$this->assertEqual($f, '<div id="d">content</div>', 'HTML corrector -- unclosed tag with attribute.'); $this->assertEqual($f, '<div id="d">content</div>', 'HTML corrector -- unclosed tag with attribute.');
// XHTML slash for empty elements. // XHTML slash for empty elements.
$f = _filter_htmlcorrector('<hr><br>'); $f = Html::normalize('<hr><br>');
$this->assertEqual($f, '<hr /><br />', 'HTML corrector -- XHTML closing slash.'); $this->assertEqual($f, '<hr /><br />', 'HTML corrector -- XHTML closing slash.');
$f = _filter_htmlcorrector('<P>test</P>'); $f = Html::normalize('<P>test</P>');
$this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.'); $this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
$f = _filter_htmlcorrector('<P>test</p>'); $f = Html::normalize('<P>test</p>');
$this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.'); $this->assertEqual($f, '<p>test</p>', 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
$f = _filter_htmlcorrector('test<hr />'); $f = Html::normalize('test<hr />');
$this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through.'); $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through.');
$f = _filter_htmlcorrector('test<hr/>'); $f = Html::normalize('test<hr/>');
$this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.'); $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.');
$f = _filter_htmlcorrector('test<hr />'); $f = Html::normalize('test<hr />');
$this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.'); $this->assertEqual($f, 'test<hr />', 'HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.');
$f = _filter_htmlcorrector('<span class="test" />'); $f = Html::normalize('<span class="test" />');
$this->assertEqual($f, '<span class="test"></span>', 'HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.'); $this->assertEqual($f, '<span class="test"></span>', 'HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.');
$f = _filter_htmlcorrector('test1<br class="test">test2'); $f = Html::normalize('test1<br class="test">test2');
$this->assertEqual($f, 'test1<br class="test" />test2', 'HTML corrector -- Automatically close single tags.'); $this->assertEqual($f, 'test1<br class="test" />test2', 'HTML corrector -- Automatically close single tags.');
$f = _filter_htmlcorrector('line1<hr>line2'); $f = Html::normalize('line1<hr>line2');
$this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.'); $this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.');
$f = _filter_htmlcorrector('line1<HR>line2'); $f = Html::normalize('line1<HR>line2');
$this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.'); $this->assertEqual($f, 'line1<hr />line2', 'HTML corrector -- Automatically close single tags.');
$f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>'); $f = Html::normalize('<img src="http://example.com/test.jpg">test</img>');
$this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', 'HTML corrector -- Automatically close single tags.'); $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', 'HTML corrector -- Automatically close single tags.');
$f = _filter_htmlcorrector('<br></br>'); $f = Html::normalize('<br></br>');
$this->assertEqual($f, '<br />', "HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY."); $this->assertEqual($f, '<br />', "HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY.");
$f = _filter_htmlcorrector('<div></div>'); $f = Html::normalize('<div></div>');
$this->assertEqual($f, '<div></div>', "HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY."); $this->assertEqual($f, '<div></div>', "HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY.");
$f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>');