public static function truncate($html, $length, $opts=array()) {
		if (is_string($opts)) $opts = array('ellipsis' => $opts);
		$opts = array_merge(static::$default_options, $opts);
		// wrap the html in case it consists of adjacent nodes like <p>foo</p><p>bar</p>
		$html = "<div>".static::utf8_for_xml($html)."</div>";

		$root_node = null;

		// Parse using HTML5Lib if it's available.
		if (class_exists('HTML5Lib\\Parser')) {
			try {
				$doc = \HTML5Lib\Parser::parse($html);
				$root_node = $doc->documentElement->lastChild->lastChild;
			}
			catch (\Exception $e) {
				;
			}
		}

		if ($root_node === null) {
			// HTML5Lib not available so we'll have to use DOMDocument
			// We'll only be able to parse HTML5 if it's valid XML
			$doc = new DOMDocument;
			$doc->formatOutput = false;
			$doc->preserveWhitespace = true;
			// loadHTML will fail with HTML5 tags (article, nav, etc)
			// so we need to suppress errors and if it fails to parse we
			// retry with the XML parser instead
			$prev_use_errors = libxml_use_internal_errors(true);
			if ($doc->loadHTML($html)) {
				$root_node = $doc->documentElement->lastChild->lastChild;
			}
			else if ($doc->loadXML($html)) {
				$root_node = $doc->documentElement;
			}
			else {
				libxml_use_internal_errors($prev_use_errors);
				throw new InvalidHtmlException;
			}
			libxml_use_internal_errors($prev_use_errors);
		}

		list($text, $_, $opts) = static::_truncate_node($doc, $root_node, $length, $opts);
		$text = ht_substr(ht_substr($text, 0, -6), 5);
		return $text;
	}