74 public function loadHTML($html, $options = 0): bool
76 $tmpDOM = new \DOMDocument();
78 $encoding = mb_http_input();
79 if ($encoding ==
'') {
84 $success = @$tmpDOM->loadHTML(
"<meta http-equiv=\"content-type\" content=\"text/html; charset=$encoding\">$html", $options);
86 $xpath = new \DOMXPath($tmpDOM);
87 $nodelist = $xpath->query(
"//body/node()");
89 $this->resolveExternals =
true;
90 $this->loadXML(
'<?xml version="1.0" encoding="utf-8"?>
92 <!ENTITY nbsp " ">
95 if ($tmpDOM->encoding !=
'') {
96 $this->encoding = $tmpDOM->encoding;
98 $rootnode = $this->documentElement;
100 foreach ($nodelist as $node) {
102 $newnode = $this->importNode($node,
true);
103 $rootnode->appendChild($newnode);
121 $xpath = new \DOMXPath($this);
134 preg_match(
"/([a-zA-Z0-9]*)(\.(.*))?/", $t, $matches);
136 $tag = $matches[1] ??
"";
137 $class = $matches[3] ??
"";
139 if (!isset($classByTag[$tag])) {
140 $classByTag[$tag] = [];
142 if (!empty($class)) {
143 $classByTag[$tag][] = $class;
145 $classByTag[$tag][] =
"";
150 $eventHandlerPattern =
'/^on(abort|activate|afterprint|animationend|animationiteration|animationstart|beforeprint|beforeunload|beforecopy|beforecut|beforeeditfocus|beforepaste|beforescriptactivate|blur|bounce|cancel|change|click|close|command|componentending|componentloaded|contextmenu|copy|cut|dbclick|dblclick|delay|delete|drag|dragstart|drop|error|eval|exception|finish|focus|focusin|focusout|formchange|forminput|full-screen-change|fullscreenchange|fullscreenerror|gesturerotate|gesturestart|gestureswipe|gesturechange|hashchange|help|hoveroff|hoveron|input|invalid|keydown|keypress|keyup|listitemsenter|listitemsexit|load|loadeddata|loadedmetadata|loadend|loadstart|MSGestureChange|MSGestureDoubleTap|MSGestureEnd|MSGestureHold|MSGestureStart|MSGestureTap|MSGotPointerCapture|MSGestureTapAndHold|MSGestureUpdate|MSGotPointerCapture|MSHold|MSInertiaStart|MSPointerCancel|MSPointerDown|MSPointerMove|MSPointerOver|MSPointerOut|MSPointerUp|MSPan|MSPanStarted|MSPanupdate|MSRubberSelect|MSRubberSelectEnd|MSRubberSelectStart|MSScroll|MSScrollUpdate|MSManipulationStateChanged|MSGotPointerrcapture|MSReleasePointerCapture|MSScroll|MSScrollUpdate|MSZoom|MSZoomBegin|MSZoomEnd|open|orientationchange|overflowchanged|pause|paste|pauseanimation|restart|restored|save|scroll|seek|show|showMessage|sleep|split|start|submit|suspend|touchcancel|touchenter|touchleave|touchmove|touchstart|transitionend|unload|unpause|unseek|unshow|vibrate|webkitAnimationEnd|webkitAnimationIteration|webkitAnimationStart|webkitTransitionEnd|volumechange|waiting)|$/i';
152 $dangerousSchemes = [
'javascript',
'vbscript',
'data',
'file',
'blob',
'about'];
154 if ($wrapTextNodes) {
155 $nodelist = $xpath->query(
"//body/text()");
157 for ($i = $nodelist->length - 1; $i >= 0; $i--) {
158 $node = $nodelist->item($i);
160 if (!empty(trim($node->textContent))) {
162 $paragraph = $node->parentNode->insertBefore($this->createElement(
"p"), $node);
163 $paragraph->appendChild($node);
166 $node->parentNode->removeChild($node);
171 $nodelist = $xpath->query(
"//body//*");
173 for ($i = $nodelist->length - 1; $i >= 0; $i--) {
174 $node = $nodelist->item($i);
176 if (!isset($tags[$node->nodeName])) {
178 while ($node->firstChild !=
null) {
179 if ($node->parentNode->nodeName ==
"body"
180 && $node->firstChild->nodeType == XML_TEXT_NODE
181 && !empty(trim($node->firstChild->textContent))
184 $paragraph = $node->parentNode->insertBefore($this->createElement(
"p"), $node);
185 $paragraph->appendChild($node->firstChild);
187 $node->parentNode->insertBefore($node->firstChild, $node);
192 $node->parentNode->removeChild($node);
195 for ($j = $node->attributes->length - 1; $j >= 0; $j--) {
196 $attr = $node->attributes->item($j);
197 $attrName = strtolower($attr->name);
200 if (strpos($attrName,
'on') === 0) {
201 $node->removeAttribute($attr->name);
207 $node->removeAttribute($attr->name);
212 if (in_array($attrName, [
'href',
'src',
'action',
'formaction'])) {
213 $attrValue = trim($attr->value);
214 foreach ($dangerousSchemes as $dangerous) {
215 $prefix = $dangerous .
':';
216 if (stripos($attrValue, $prefix) === 0) {
217 $node->removeAttribute($attr->name);
221 if (preg_match(
'/^\s*(?:\/\*.*\*\/\s*)?' . preg_quote($prefix,
'/') .
'/i', $attrValue)) {
222 $node->removeAttribute($attr->name);
230 if ($node->getAttribute(
"class") !=
"") {
231 $attr = implode(
" ", array_intersect(
232 explode(
" ", $node->getAttribute(
"class")),
233 $classByTag[$node->nodeName],
236 $node->removeAttribute(
"class");
238 $node->setAttribute(
"class", $attr);
244 $nodelist = $xpath->query(
"//p[. = '' and count(br) = 0]");
246 foreach ($nodelist as $node) {
247 $node->appendChild($this->createElement(
"br"));
250 $nodelist = $xpath->query(
"//li");
251 $parentNodes = [
"ul",
"ol",
"menu"];
253 foreach ($nodelist as $node) {
254 if (!in_array($node->parentNode->nodeName, $parentNodes)) {
256 $previous = $node->previousSibling;
257 while (!is_null($previous) && $previous->nodeType != XML_ELEMENT_NODE) {
258 $previous = $previous->previousSibling;
261 if (!is_null($previous) && in_array($previous->nodeName, $parentNodes)) {
263 $listNode->appendChild($node);
266 $listNode = $node->parentNode->insertBefore($this->createElement(
"ul"), $node);
267 $listNode->appendChild($node);
272 $nodelist = $xpath->query(
"//b[not(node())] | //i[not(node())] | //strong[not(node())] | //span[not(node())] | //a[not(node())] | //u[not(node())]");
274 for ($i = $nodelist->length - 1; $i >= 0; $i--) {
275 $node = $nodelist->item($i);
277 $node->parentNode->removeChild($node);
281 if ($nodes->length == 1) {
282 $node = $nodes->item(0);
283 if ($node->nodeName ==
"p" && $node->childNodes->length == 1 && $node->childNodes->item(0)->nodeName ==
"br") {
284 $node->parentNode->removeChild($node);
297 $charsToRemove = mb_strlen($this->documentElement->textContent) - $max;
299 if ($charsToRemove <= 0) {
303 $xpath = new \DOMXPath($this);
304 $textNodes = $xpath->query(
"//text()");
305 $i = $textNodes->length - 1;
306 while ($charsToRemove > 0 && $i >= 0) {
307 $n = $textNodes->item($i);
308 $len = mb_strlen($n->textContent);
309 $parent = $n->parentNode;
311 if ($len <= $charsToRemove) {
312 $parent->removeChild($n);
313 $charsToRemove -= $len;
315 $restNode = $n->splitText($len - $charsToRemove);
316 $parent->removeChild($restNode);
321 if (mb_strlen($parent->textContent) == 0) {
322 $parent->parentNode->removeChild($parent);