Wie speichere ich HTML von DOMDocument ohne HTML-Wrapper?


116

Ich bin die Funktion unten, ich kämpfen , um die DOMDocument ohne es anhängt die XML, HTML, zur Ausgabe von Körper und p - Tag - Wrapper vor der Ausgabe des Inhalts. Die vorgeschlagene Lösung:

$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));

Funktioniert nur, wenn der Inhalt keine Elemente auf Blockebene enthält. Wenn dies jedoch der Fall ist, wie im folgenden Beispiel mit dem Element h1, wird die resultierende Ausgabe von saveXML auf ... abgeschnitten.

<p> Wenn Sie möchten </ p>

Ich wurde auf diesen Beitrag als mögliche Problemumgehung hingewiesen, kann aber nicht verstehen, wie er in diese Lösung implementiert werden kann (siehe auskommentierte Versuche unten).

Irgendwelche Vorschläge?

function rseo_decorate_keyword($postarray) {
    global $post;
    $keyword = "Jasmine Tea"
    $content = "If you like <h1>jasmine tea</h1> you will really like it with Jasmine Tea flavors. This is the last ocurrence of the phrase jasmine tea within the content. If there are other instances of the keyword jasmine tea within the text what happens to jasmine tea."
    $d = new DOMDocument();
    @$d->loadHTML($content);
    $x = new DOMXpath($d);
    $count = $x->evaluate("count(//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and (ancestor::b or ancestor::strong)])");
    if ($count > 0) return $postarray;
    $nodes = $x->query("//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and not(ancestor::h1) and not(ancestor::h2) and not(ancestor::h3) and not(ancestor::h4) and not(ancestor::h5) and not(ancestor::h6) and not(ancestor::b) and not(ancestor::strong)]");
    if ($nodes && $nodes->length) {
        $node = $nodes->item(0);
        // Split just before the keyword
        $keynode = $node->splitText(strpos($node->textContent, $keyword));
        // Split after the keyword
        $node->nextSibling->splitText(strlen($keyword));
        // Replace keyword with <b>keyword</b>
        $replacement = $d->createElement('strong', $keynode->textContent);
        $keynode->parentNode->replaceChild($replacement, $keynode);
    }
$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->item(1));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->childNodes);
return $postarray;
}

Antworten:


216

Alle diese Antworten sind jetzt falsch , da ab PHP 5.4 und Libxml 2.6 loadHTMLnun ein $optionParameter vorhanden ist, der Libxml anweist, wie der Inhalt analysiert werden soll.

Daher, wenn wir den HTML-Code mit diesen Optionen laden

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

wenn es das saveHTML()gibt, wird es nein doctype, nein <html>und nein geben <body>.

LIBXML_HTML_NOIMPLIEDLIBXML_HTML_NODEFDTDDeaktiviert das automatische Hinzufügen impliziter HTML- / Body-Elemente und verhindert, dass ein Standard-Doctype hinzugefügt wird, wenn einer nicht gefunden wird.

Die vollständige Dokumentation zu Libxml-Parametern finden Sie hier

(Beachten Sie, dass in den loadHTMLDokumenten angegeben ist, dass Libxml 2.6 benötigt wird, jedoch LIBXML_HTML_NODEFDTDnur in Libxml 2.7.8 und LIBXML_HTML_NOIMPLIEDin Libxml 2.7.7 verfügbar ist.)


10
Das funktioniert wie ein Zauber. Sollte die akzeptierte Antwort sein. Ich habe nur eine Flagge hinzugefügt und alle meine Kopfschmerzen gingen weg ;-)
Just Plain High

8
Dies funktioniert nicht mit PHP 5.4 und Libxml 2.9. loadHTML akzeptiert keine Optionen :(
Acyra

11
Beachten Sie, dass dies nicht ganz perfekt ist. Siehe stackoverflow.com/questions/29493678/…
Josh Levinson

4
Entschuldigung, aber dies scheint überhaupt keine gute Lösung zu sein (zumindest nicht in der Praxis). Es sollte wirklich nicht die akzeptierte Antwort sein. Neben den genannten Themen gibt es auch eine böse Codierung Problem mit , DOMDocumentdass auch Sie den Code in dieser Antwort betrifft. Afaik DOMDocumentinterpretiert Eingabedaten immer als Latin-1, es sei denn, die Eingabe gibt einen anderen Zeichensatz an . Mit anderen Worten: Das <meta charset="…">Tag scheint für Eingabedaten benötigt zu werden, die nicht Latin-1 sind. Andernfalls wird die Ausgabe für z. B. UTF-8-Multibyte-Zeichen unterbrochen.
Mermshaus

1
LIBXML_HTML_NOIMPLIED bringt auch den HTML-Code durcheinander, indem die Tabulatoren, Einrückungen und Zeilenumbrüche entfernt werden
Zoltán Süle

72

Entfernen Sie einfach die Knoten direkt nach dem Laden des Dokuments mit loadHTML ():

# remove <!DOCTYPE 
$doc->removeChild($doc->doctype);           

# remove <html><body></body></html> 
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Das ist die sauberere Antwort für mich.
KnF

39
Es ist zu beachten, dass dies funktioniert, wenn <body> nur einen untergeordneten Knoten hat.
Yann Milin

Hat super funktioniert. Danke dir! Viel sauberer und schneller als die andere Preg-Antwort.
Ligemer

Danke dafür! Ich habe gerade einen weiteren Ausschnitt unten hinzugefügt, um leere Knoten zu behandeln.
Redaxmedia

2
Der zu entfernende Code <!DOCTYPE funktioniert. Die zweite Zeile wird unterbrochen, wenn <body>mehr als eine untergeordnete Note vorhanden ist.
Free Radical

21

Verwenden Sie saveXML()stattdessen und übergeben Sie das documentElement als Argument.

$innerHTML = '';
foreach ($document->getElementsByTagName('p')->item(0)->childNodes as $child) {
    $innerHTML .= $document->saveXML($child);
}
echo $innerHTML;

http://php.net/domdocument.savexml


Das ist besser, aber ich bekomme immer noch <html> <body> <p>, um den Inhalt zu verpacken.
Scott B


2
Es ist zu beachten, dass saveXML () XHTML und nicht HTML speichert.
Alexantd

@ Scott: Das ist wirklich seltsam. Im Beispielabschnitt wird gezeigt, was Sie genau dort versuchen. Sind Sie sicher, dass Sie diesen HTML-Code nicht in Ihrem DOM haben? Was genau ist HTML in Ihrem DOMDocument? Es kann sein, dass wir auf einen untergeordneten Knoten zugreifen müssen.
Jonah

@ Jonah es ist nicht seltsam. Wenn Sie dies loadHTMLtun, verwendet libxml das HTML-Parser-Modul, wodurch das fehlende HTML-Skelett eingefügt wird. Folglich $dom->documentElementwird das Stamm-HTML-Element sein. Ich habe Ihren Beispielcode korrigiert. Es sollte jetzt tun, was Scott verlangt.
Gordon

18

Das Problem mit der Top-Antwort ist, dass LIBXML_HTML_NOIMPLIEDes instabil ist .

Es kann Elemente neu anordnen (insbesondere das schließende Tag des oberen Elements an den unteren Rand des Dokuments verschieben), zufällige pTags hinzufügen und möglicherweise eine Reihe anderer Probleme [1] . Es kann die Tags htmlund bodyfür Sie entfernen , jedoch auf Kosten eines instabilen Verhaltens. In der Produktion ist das eine rote Fahne. Zusamenfassend:

Nicht benutzenLIBXML_HTML_NOIMPLIED . Verwenden Sie stattdessensubstr .


Denk darüber nach. Die Längen von <html><body>und </body></html>sind fest und an beiden Enden des Dokuments - ihre Größen ändern sich nie und ihre Positionen auch nicht. Dies ermöglicht es uns, substrsie wegzuschneiden:

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

echo substr($dom->saveHTML(), 12, -15); // the star of this operation

( Dies ist jedoch nicht die endgültige Lösung! Siehe unten für die vollständige Antwort , lesen Sie weiter für den Kontext)

Wir schneiden 12vom Anfang des Dokuments weg, weil <html><body>= 12 Zeichen ( <<>>+html+body= 4 + 4 + 4), und wir gehen rückwärts und schneiden 15 vom Ende ab, weil \n</body></html>= 15 Zeichen ( \n+//+<<>>+body+html= 1 + 2 + 4 + 4 + 4)

Beachten Sie, dass ich immer noch LIBXML_HTML_NODEFDTDweglasse, dass das !DOCTYPEnicht aufgenommen wird. Dies vereinfacht zunächst das substrEntfernen der HTML / BODY-Tags. Zweitens entfernen wir den Doctype nicht mit, substrweil wir nicht wissen, ob das ' default doctype' immer eine feste Länge haben wird. Aber am wichtigsten,LIBXML_HTML_NODEFDTD der DOM-Parser keinen Nicht-HTML5-Doctype auf das Dokument anwendet. Dies verhindert zumindest, dass der Parser Elemente behandelt, die er nicht als losen Text erkennt.

Wir wissen, dass die HTML / BODY-Tags feste Längen und Positionen haben, und wir wissen, dass Konstanten wie diese LIBXML_HTML_NODEFDTDniemals ohne irgendeine Art von Verfallserklärung entfernt werden, daher sollte die obige Methode auch in Zukunft funktionieren , ABER ...


... die einzige Einschränkung ist, dass die DOM-Implementierung könnte den Weg in HTML / BODY - Tags im Dokument platziert werden ändern - zum Beispiel, das Newline am Ende des Dokuments zu entfernen, das Hinzufügen Leerzeichen zwischen den Tags oder dem Hinzufügen von Zeilenumbrüchen.

Dies kann behoben werden, indem nach den Positionen der öffnenden und schließenden Tags gesucht bodywird und diese Offsets für unsere Längen zum Abschneiden verwendet werden. Wir verwenden strposund strrposfinden die Offsets von vorne bzw. hinten:

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
// PositionOf<body> + 6 = Cutoff offset after '<body>'
// 6 = Length of '<body>'

$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());
// ^ PositionOf</body> - LengthOfDocument = Relative-negative cutoff offset before '</body>'

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

Abschließend eine Wiederholung der endgültigen, zukunftssicheren Antwort :

$dom = new domDocument; 
$dom->loadHTML($html, LIBXML_HTML_NODEFDTD);

$trim_off_front = strpos($dom->saveHTML(),'<body>') + 6;
$trim_off_end = (strrpos($dom->saveHTML(),'</body>')) - strlen($dom->saveHTML());

echo substr($dom->saveHTML(), $trim_off_front, $trim_off_end);

Kein Doctype, kein HTML-Tag, kein Body-Tag. Wir können nur hoffen, dass der DOM-Parser bald einen neuen Anstrich erhält, und wir können diese unerwünschten Tags direkter beseitigen.


Tolle Antwort, ein kleiner Kommentar, warum nicht $html = $dom -> saveHTML();statt $dom -> saveHTML();wiederholt?
Steven

15

Ein ordentlicher Trick ist es, loadXMLund dann zu verwenden saveHTML. Die Tags htmlund bodywerden auf der loadBühne eingefügt , nicht auf der saveBühne.

$dom = new DOMDocument;
$dom->loadXML('<p>My DOMDocument contents are here</p>');
echo $dom->saveHTML();

NB, dass dies ein bisschen hacky ist und Sie Jonahs Antwort verwenden sollten, wenn Sie es zum Laufen bringen können.


4
Dies schlägt jedoch für ungültiges HTML fehl.
Gordon

1
@ Gordon Genau, warum ich den Haftungsausschluss unten gesetzt habe!
einsamer

1
Wenn ich dies versuche und $ dom-> saveHTML () wiedergebe, wird nur eine leere Zeichenfolge zurückgegeben. Als ob loadXML ($ content) leer ist. Wenn ich dasselbe mit $ dom-> loadHTML ($ content) mache und dann $ dom-> saveXML () wiedergebe, erhalte ich den Inhalt wie erwartet.
Scott B

Die Verwendung von loadXML, wenn Sie bereit sind, HTMl zu laden, ist der Daumen. Vor allem, weil LoadXML nicht weiß, wie man mit HTML umgeht.
Botenvouwer

15

Verwenden Sie DOMDocumentFragment

$html = 'what you want';
$doc = new DomDocument();
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html);
$doc->appendChild($fragment);
echo $doc->saveHTML();

3
Die sauberste Antwort für Pre-PHP5.4.
Nick Johnson

Dies funktioniert für mich sowohl älter als auch neuer als die Version Libxml 2.7.7. Warum sollte dies nur für Pre-PHP5.4 sein?
RobbertT

Dies sollte mehr Stimmen haben. Hervorragende Option für Versionen von libxml, die LIBXML_HTML_NOIMPLIED | nicht unterstützen LIBXML_HTML_NODEFDTD. Vielen Dank!
Marty Mulligan

13

Es ist 2017 und für diese Frage 2011 mag ich keine der Antworten. Viele Regex, große Klassen, loadXML etc ...

Einfache Lösung, die die bekannten Probleme löst:

$dom = new DOMDocument();
$dom->loadHTML( '<html><body>'.mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8').'</body></html>' , LIBXML_HTML_NODEFDTD);
$html = substr(trim($dom->saveHTML()),12,-14);

Einfach, einfach, solide, schnell. Dieser Code funktioniert in Bezug auf HTML-Tags und Codierung wie:

$html = '<p>äöü</p><p>ß</p>';

Wenn jemand einen Fehler findet, sagen Sie bitte, ich werde diesen selbst verwenden.

Bearbeiten , Andere gültige Optionen, die fehlerfrei funktionieren (sehr ähnlich zu den bereits angegebenen):

@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$saved_dom = trim($dom->saveHTML());
$start_dom = stripos($saved_dom,'<body>')+6;
$html = substr($saved_dom,$start_dom,strripos($saved_dom,'</body>') - $start_dom );

Sie können selbst Körper hinzufügen, um seltsame Dinge auf dem Fell zu verhindern.

Dreißig Option:

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());

3
Sie sollten Ihre Antwort verbessern, indem Sie die teureren vermeiden mb_convert_encodingund stattdessen entsprechend hinzufügen <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>und ändern substr. Übrigens ist Ihre Lösung hier die eleganteste. Upvoted.
Hlsg

10

Ich bin etwas spät im Club, wollte aber keine Methode teilen, von der ich erfahren habe. Zunächst habe ich die richtigen Versionen für loadHTML (), um diese netten Optionen zu akzeptieren, aber LIBXML_HTML_NOIMPLIEDauf meinem System nicht funktioniert. Auch Benutzer melden Probleme mit dem Parser (zum Beispiel hier und hier ).

Die Lösung, die ich erstellt habe, ist ziemlich einfach.

Zu ladendes HTML wird in ein <div>Element eingefügt, sodass es einen Container enthält, der alle zu ladenden Knoten enthält.

Dann wird dieses Containerelement aus dem Dokument entfernt (aber das DOMElement davon existiert noch).

Dann werden alle direkten untergeordneten Elemente aus dem Dokument entfernt. Dazu gehören jede hinzugefügt <html>, <head>und <body>Tags (effektiv LIBXML_HTML_NOIMPLIEDOption) sowie die <!DOCTYPE html ... loose.dtd">Erklärung (effektivLIBXML_HTML_NODEFDTD ).

Dann werden alle direkten untergeordneten Elemente des Containers erneut zum Dokument hinzugefügt und es kann ausgegeben werden.

$str = '<p>Lorem ipsum dolor sit amet.</p><p>Nunc vel vehicula ante.</p>';

$doc = new DOMDocument();

$doc->loadHTML("<div>$str</div>");

$container = $doc->getElementsByTagName('div')->item(0);

$container = $container->parentNode->removeChild($container);

while ($doc->firstChild) {
    $doc->removeChild($doc->firstChild);
}

while ($container->firstChild ) {
    $doc->appendChild($container->firstChild);
}

$htmlFragment = $doc->saveHTML();

XPath funktioniert wie gewohnt. Achten Sie jedoch darauf, dass jetzt mehrere Dokumentelemente vorhanden sind, also kein einziger Stammknoten:

$xpath = new DOMXPath($doc);
foreach ($xpath->query('/p') as $element)
{   #                   ^- note the single slash "/"
    # ... each of the two <p> element

  • PHP 5.4.36-1 + deb.sury.org ~ präzise + 2 (cli) (erstellt: 21. Dezember 2014 20:28:53)

Bei komplexeren HTML-Quellen hat es bei mir nicht funktioniert. Es wurde auch ein bestimmter Teil des HTML-Codes entfernt.
Zoltán Süle

4

Keine der anderen Lösungen zum Zeitpunkt dieses Schreibens (Juni 2012) konnte meine Anforderungen vollständig erfüllen, daher habe ich eine geschrieben, die die folgenden Fälle behandelt:

  • Akzeptiert Nur-Text-Inhalte ohne Tags sowie HTML-Inhalte.
  • Hat anhängen keine Tags (einschließlich <doctype>, <xml>, <html>, <body>, und <p>Tags)
  • Lässt alles <p>alleine eingewickelt .
  • Lässt leeren Text in Ruhe.

Hier ist eine Lösung, die diese Probleme behebt:

class DOMDocumentWorkaround
{
    /**
     * Convert a string which may have HTML components into a DOMDocument instance.
     *
     * @param string $html - The HTML text to turn into a string.
     * @return \DOMDocument - A DOMDocument created from the given html.
     */
    public static function getDomDocumentFromHtml($html)
    {
        $domDocument = new DOMDocument();

        // Wrap the HTML in <div> tags because loadXML expects everything to be within some kind of tag.
        // LIBXML_NOERROR and LIBXML_NOWARNING mean this will fail silently and return an empty DOMDocument if it fails.
        $domDocument->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);

        return $domDocument;
    }

    /**
     * Convert a DOMDocument back into an HTML string, which is reasonably close to what we started with.
     *
     * @param \DOMDocument $domDocument
     * @return string - The resulting HTML string
     */
    public static function getHtmlFromDomDocument($domDocument)
    {
        // Convert the DOMDocument back to a string.
        $xml = $domDocument->saveXML();

        // Strip out the XML declaration, if one exists
        $xmlDeclaration = "<?xml version=\"1.0\"?>\n";
        if (substr($xml, 0, strlen($xmlDeclaration)) == $xmlDeclaration) {
            $xml = substr($xml, strlen($xmlDeclaration));
        }

        // If the original HTML was empty, loadXML collapses our <div></div> into <div/>. Remove it.
        if ($xml == "<div/>\n") {
            $xml = '';
        }
        else {
            // Remove the opening <div> tag we previously added, if it exists.
            $openDivTag = "<div>";
            if (substr($xml, 0, strlen($openDivTag)) == $openDivTag) {
                $xml = substr($xml, strlen($openDivTag));
            }

            // Remove the closing </div> tag we previously added, if it exists.
            $closeDivTag = "</div>\n";
            $closeChunk = substr($xml, -strlen($closeDivTag));
            if ($closeChunk == $closeDivTag) {
                $xml = substr($xml, 0, -strlen($closeDivTag));
            }
        }

        return $xml;
    }
}

Ich habe auch einige Tests geschrieben, die in derselben Klasse leben würden:

public static function testHtmlToDomConversions($content)
{
    // test that converting the $content to a DOMDocument and back does not change the HTML
    if ($content !== self::getHtmlFromDomDocument(self::getDomDocumentFromHtml($content))) {
        echo "Failed\n";
    }
    else {
        echo "Succeeded\n";
    }
}

public static function testAll()
{
    self::testHtmlToDomConversions('<p>Here is some sample text</p>');
    self::testHtmlToDomConversions('<div>Lots of <div>nested <div>divs</div></div></div>');
    self::testHtmlToDomConversions('Normal Text');
    self::testHtmlToDomConversions(''); //empty
}

Sie können überprüfen, ob es für Sie funktioniert. DomDocumentWorkaround::testAll()gibt dies zurück:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

1
HTML = / = XML, Sie sollten den HTML-Loader für HTML verwenden.
hakre

4

Okay, ich habe eine elegantere Lösung gefunden, aber es ist einfach langweilig:

$d = new DOMDocument();
@$d->loadHTML($yourcontent);
...
// do your manipulation, processing, etc of it blah blah blah
...
// then to save, do this
$x = new DOMXPath($d);
$everything = $x->query("body/*"); // retrieves all elements inside body tag
if ($everything->length > 0) { // check if it retrieved anything in there
      $output = '';
      foreach ($everything as $thing) {
           $output .= $d->saveXML($thing);
      }
      echo $output; // voila, no more annoying html wrappers or body tag
}

Okay, hoffentlich lässt dies nichts aus und hilft jemandem?


2
Behandelt den Fall nicht, wenn loadHTML einen String ohne Markup
lädt

3

Verwenden Sie diese Funktion

$layout = preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $layout);

13
Es kann einige Leser geben, die über diesen Beitrag über diesen Beitrag gestolpert sind , sich entschieden haben, Regex nicht zum Parsen ihres HTML-Codes und stattdessen zum Verwenden eines DOM-Parsers zu verwenden, und möglicherweise eine Regex-Antwort benötigen, um eine vollständige Lösung zu erhalten ... ironisch
Robbie Averill

Ich verstehe nicht, warum Noboy nur den Inhalt von BODY zurückgibt. Wird angenommen, dass dieses Tag nicht immer vorhanden ist, wenn der Parser den gesamten Dokumentkopf / Doctype hinzufügt? Der Regex oben wäre sogar noch kürzer.
Sergio

@boksiora "es macht den Job" - warum verwenden wir dann überhaupt DOM-Parser-Methoden?
Danke

@naomik Ich habe nicht gesagt, dass ich keinen DOM-Parser verwenden soll. Es gibt natürlich viele verschiedene Möglichkeiten, um das gleiche Ergebnis zu erzielen. Es liegt an Ihnen. Als ich diese Funktion verwendete, hatte ich ein Problem mit dem eingebauten PHP-Dom Parser, der HTML5 nicht richtig analysiert hat.
Boksiora

1
Ich musste verwenden, preg_replaceweil die Verwendung von DOMDocument-basierten Methoden zum Entfernen der HTML- und Body-Tags die UTF-8-Codierung nicht
beibehielt

3

Wenn die von Alessandro Vendruscolo beantwortete Flags-Lösung nicht funktioniert, können Sie Folgendes versuchen:

$dom = new DOMDocument();
$dom->loadHTML($content);

//do your stuff..

$finalHtml = '';
$bodyTag = $dom->documentElement->getElementsByTagName('body')->item(0);
foreach ($bodyTag->childNodes as $rootLevelTag) {
    $finalHtml .= $dom->saveHTML($rootLevelTag);
}
echo $finalHtml;

$bodyTagenthält Ihren vollständig verarbeiteten HTML-Code ohne all diese HTML-Wraps, mit Ausnahme des <body>Tags, das die Wurzel Ihres Inhalts darstellt. Dann können Sie einen regulären Ausdruck oder eine Trimmfunktion verwenden, um ihn aus der endgültigen Zeichenfolge (nach saveHTML) zu entfernen, oder, wie im obigen Fall, alle seine Kinder durchlaufen, ihren Inhalt in einer temporären Variablen speichern $finalHtmlund ihn zurückgeben (was ich glaube) sicherer).


3

Ich habe Probleme damit auf RHEL7 mit PHP 5.6.25 und LibXML 2.9. (Altes Zeug im Jahr 2018, ich weiß, aber das ist Red Hat für dich.)

Ich habe festgestellt, dass die von Alessandro Vendruscolo vorgeschlagene vielbeachtete Lösung den HTML- Code durch Neuanordnung von Tags zerstört. Dh:

<p>First.</p><p>Second.</p>'

wird:

<p>First.<p>Second.</p></p>'

Dies gilt für beide Optionen, die er Ihnen vorschlägt: LIBXML_HTML_NOIMPLIEDund LIBXML_HTML_NODEFDTD.

Die von Alex vorgeschlagene Lösung reicht zur Hälfte aus, funktioniert jedoch nicht, wenn <body>mehr als ein untergeordneter Knoten vorhanden ist.

Die Lösung, die für mich funktioniert, ist die folgende:

Um das DOMDocument zu laden, verwende ich zunächst:

$doc = new DOMDocument()
$doc->loadHTML($content);

Um das Dokument nach dem Massieren des DOMDocument zu speichern, verwende ich:

// remove <!DOCTYPE 
$doc->removeChild($doc->doctype);  
$content = $doc->saveHTML();
// remove <html><body></body></html> 
$content = str_replace('<html><body>', '', $content);
$content = str_replace('</body></html>', '', $content);

Ich bin der erste, der zustimmt, dass dies keine sehr elegante Lösung ist - aber es funktioniert.


2

Das Hinzufügen des <meta>Tags löst das Fixierungsverhalten von aus DOMDocument. Das Gute daran ist, dass Sie dieses Tag überhaupt nicht hinzufügen müssen. Wenn Sie keine Codierung Ihrer Wahl verwenden möchten, übergeben Sie sie einfach als Konstruktorargument.

http://php.net/manual/en/domdocument.construct.php

$doc = new DOMDocument('1.0', 'UTF-8');
$node = $doc->createElement('div', 'Hello World');
$doc->appendChild($node);
echo $doc->saveHTML();

Ausgabe

<div>Hello World</div>

Danke an @Bart


2

Ich hatte auch diese Anforderung und mochte die Lösung, die Alex oben gepostet hat. Es gibt jedoch einige Probleme: Wenn das <body>Element mehr als ein untergeordnetes Element enthält, enthält das resultierende Dokument nur das erste untergeordnete Element von <body>, nicht alle. Außerdem brauchte ich das Strippen, um die Dinge bedingt zu behandeln - nur wenn Sie ein Dokument mit den HTML-Überschriften hatten. Also habe ich es wie folgt verfeinert. Anstatt es zu entfernen <body>, habe ich es in a umgewandelt <div>und die XML-Deklaration und entfernt <html>.

function strip_html_headings($html_doc)
{
    if (is_null($html_doc))
    {
        // might be better to issue an exception, but we silently return
        return;
    }

    // remove <!DOCTYPE 
    if (!is_null($html_doc->firstChild) &&
        $html_doc->firstChild->nodeType == XML_DOCUMENT_TYPE_NODE)
    {
        $html_doc->removeChild($html_doc->firstChild);     
    }

    if (!is_null($html_doc->firstChild) &&
        strtolower($html_doc->firstChild->tagName) == 'html' &&
        !is_null($html_doc->firstChild->firstChild) &&
        strtolower($html_doc->firstChild->firstChild->tagName) == 'body')
    {
        // we have 'html/body' - replace both nodes with a single "div"        
        $div_node = $html_doc->createElement('div');

        // copy all the child nodes of 'body' to 'div'
        foreach ($html_doc->firstChild->firstChild->childNodes as $child)
        {
            // deep copies each child node, with attributes
            $child = $html_doc->importNode($child, true);
            // adds node to 'div''
            $div_node->appendChild($child);
        }

        // replace 'html/body' with 'div'
        $html_doc->removeChild($html_doc->firstChild);
        $html_doc->appendChild($div_node);
    }
}

2

Ähnlich wie bei anderen Mitgliedern schwelgte ich zuerst in der Einfachheit und unglaublichen Kraft der Antwort von @Alessandro Vendruscolo. Die Fähigkeit, einfach einige markierte Konstanten an den Konstruktor zu übergeben, schien zu gut, um wahr zu sein. Für mich war es. Ich habe die richtigen Versionen von LibXML und PHP, unabhängig davon, wie das HTML-Tag zur Knotenstruktur des Document-Objekts hinzugefügt wird.

Meine Lösung hat viel besser funktioniert als die ...

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

Flaggen oder ....

# remove <!DOCTYPE 
$doc->removeChild($doc->firstChild);            

# remove <html><body></body></html>
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Knotenentfernung, die ohne strukturierte Reihenfolge im DOM chaotisch wird. Wiederum haben Codefragmente keine Möglichkeit, die DOM-Struktur vorzugeben.

Ich habe diese Reise begonnen, um einen einfachen Weg zu finden, wie DQuery DOM-Traversal durchführt, oder zumindest auf eine Art und Weise, bei der ein strukturierter Datensatz entweder einfach verknüpft, doppelt verknüpft oder mit einem Baum verknüpft ist. Es war mir egal, wie lange ich eine Zeichenfolge wie HTML analysieren konnte und auch die erstaunliche Leistung der Eigenschaften der Knotenentitätsklasse hatte, die ich unterwegs verwenden konnte.

Bisher hat mich DOMDocument Object verlassen ... Wie bei vielen anderen Programmierern scheint es ... Ich weiß, dass ich in dieser Frage viel Frust gesehen habe, seit ich ENDLICH ... (nach ungefähr 30 Stunden Versuch und Misserfolg) Typprüfung) Ich habe einen Weg gefunden, alles zu bekommen. Ich hoffe das hilft jemandem ...

Zunächst einmal bin ich zynisch gegenüber ALLEN ... lol ...

Ich wäre ein Leben lang gegangen, bevor ich jemandem zugestimmt hätte, dass in diesem Anwendungsfall ohnehin eine Klasse von Drittanbietern benötigt wird. Ich war und bin kein Fan von Klassenstrukturen von Drittanbietern, aber ich bin auf einen großartigen Parser gestoßen. (Ungefähr 30 Mal in Google, bevor ich nachgab. Fühlen Sie sich also nicht allein, wenn Sie es vermieden haben, weil es in irgendeiner Weise inoffiziell lahm aussah ...)

Wenn Sie Codefragmente verwenden und den Code sauber und vom Parser in keiner Weise beeinflusst benötigen, ohne dass zusätzliche Tags verwendet werden, verwenden Sie simplePHPParser .

Es ist erstaunlich und verhält sich sehr ähnlich wie JQuery. Ich habe nicht oft beeindruckt, aber diese Klasse verwendet viele gute Tools und ich hatte noch keine Analysefehler. Ich bin ein großer Fan davon, das tun zu können, was diese Klasse tut.

Die Dateien zum Herunterladen finden Sie hier , die Startanweisungen hier und die API hier . Ich empfehle dringend, diese Klasse mit ihren einfachen Methoden zu verwenden, die auf .find(".className")die gleiche Weise wie eine JQuery-Suchmethode verwendet werden können, oder sogar mit bekannten Methoden wie getElementByTagName()oder getElementById()...

Wenn Sie einen Knotenbaum in dieser Klasse speichern, wird überhaupt nichts hinzugefügt. Sie können einfach sagen $doc->save();und es gibt den gesamten Baum ohne viel Aufhebens in eine Zeichenfolge aus.

Ich werde diesen Parser jetzt in Zukunft für alle Projekte ohne Bandbreite verwenden.


2

Ich habe PHP 5.3 und die Antworten hier haben bei mir nicht funktioniert.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);Ersetzte das gesamte Dokument nur durch das erste Kind, ich hatte viele Absätze und nur der erste wurde gespeichert, aber die Lösung gab mir einen guten Ausgangspunkt, um etwas zu schreiben, ohne regexeinige Kommentare zu hinterlassen, und ich bin mir ziemlich sicher, dass dies verbessert werden kann, aber wenn Jemand hat das gleiche Problem wie ich, es kann ein guter Ausgangspunkt sein.

function extractDOMContent($doc){
    # remove <!DOCTYPE
    $doc->removeChild($doc->doctype);

    // lets get all children inside the body tag
    foreach ($doc->firstChild->firstChild->childNodes as $k => $v) {
        if($k !== 0){ // don't store the first element since that one will be used to replace the html tag
            $doc->appendChild( clone($v) ); // appending element to the root so we can remove the first element and still have all the others
        }
    }
    // replace the body tag with the first children
    $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
    return $doc;
}

Dann könnten wir es so verwenden:

$doc = new DOMDocument();
$doc->encoding = 'UTF-8';
$doc->loadHTML('<p>Some html here</p><p>And more html</p><p>and some html</p>');
$doc = extractDOMContent($doc);

Beachten Sie, dass a appendChildakzeptiert wird, DOMNodesodass wir keine neuen Elemente erstellen müssen. Wir können nur vorhandene Elemente wiederverwenden, die implementiert DOMNodewerden. DOMElementDies kann wichtig sein, um den Code bei der Bearbeitung mehrerer HTML / XML-Dokumente "vernünftig" zu halten


Dies funktioniert nicht für Fragmente, sondern nur für ein einzelnes untergeordnetes Element, das Sie zum ersten untergeordneten Element des Dokuments machen möchten. Dies ist ziemlich begrenzt und macht die Arbeit der effektiv nicht, LIBXML_HTML_NOIMPLIEDda es das nur teilweise tut. Das Entfernen des Doctype ist effektiv LIBXML_HTML_NODEFDTD.
hakre

2

Ich bin auf dieses Thema gestoßen, um einen Weg zu finden, HTML-Wrapper zu entfernen. Die Verwendung LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTDfunktioniert hervorragend, aber ich habe ein Problem mit utf-8. Nach viel Mühe fand ich eine Lösung. Ich poste es unten für jeden, der das gleiche Problem hat.

Das Problem verursacht wegen <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Das Problem:

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->saveHTML();

Lösung 1:

$dom->loadHTML(mb_convert_encoding($document, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    $dom->saveHTML($dom->documentElement));

Lösung 2:

$dom->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
utf8_decode($dom->saveHTML($dom->documentElement));

1
Ich finde es schön, dass Sie Ihre Ergebnisse teilen, aber Lösung 2 enthält bereits genau diese Fragen, und Lösung 1 befindet sich an anderer Stelle. Auch für das Problem der Lösung 1 ist die gegebene Antwort unklar. Ich ehre Ihre guten Absichten, aber bitte beachten Sie, dass dies viel Lärm verursachen und andere daran hindern kann, die Lösungen zu finden, nach denen sie suchen. Ich denke, dies ist das Gegenteil von dem, was Sie mit Ihrer Antwort erreichen möchten. Stackoverflow funktioniert am besten, wenn Sie jeweils eine Frage bearbeiten. Nur ein Hinweis.
hakre

2

Ich habe 3 Probleme mit dem DOMDocumentUnterricht.

1- Diese Klasse lädt HTML mit ISO-Codierung und utf-8-Zeichen, die in der Ausgabe nicht angezeigt werden.

2- Auch wenn wir ‍‍‍ gebenLIBXML_HTML_NOIMPLIEDFlag loadhtml Methode, bis unsere Eingabe html kein Root - Tag enthält, wird es nicht Parse richtig sein.

3- Diese Klasse betrachtet die HTML5-Tags als ungültig.

Also habe ich diese Klasse überschrieben, um diese Probleme zu lösen, und einige der Methoden geändert.

class DOMEditor extends DOMDocument
{
    /**
     * Temporary wrapper tag , It should be an unusual tag to avoid problems
     */
    protected $tempRoot = 'temproot';

    public function __construct($version = '1.0', $encoding = 'UTF-8')
    {
        //turn off html5 errors
        libxml_use_internal_errors(true);
        parent::__construct($version, $encoding);
    }

    public function loadHTML($source, $options = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD)
    {
        // this is a bitwise check if LIBXML_HTML_NOIMPLIED is set
        if ($options & LIBXML_HTML_NOIMPLIED) {
            // it loads the content with a temporary wrapper tag and utf-8 encoding
            parent::loadHTML("<{$this->tempRoot}>" . mb_convert_encoding($source, 'HTML', 'UTF-8') . "</{$this->tempRoot}>", $options);
        } else {
            // it loads the content with utf-8 encoding and default options
            parent::loadHTML(mb_convert_encoding($source, 'HTML', 'UTF-8'), $options);
        }
    }

    private function unwrapTempRoot($output)
    {
        if ($this->firstChild->nodeName === $this->tempRoot) {
            return substr($output, strlen($this->tempRoot) + 2, -strlen($this->tempRoot) - 4);
        }
        return $output;
    }

    public function saveHTML(DOMNode $node = null)
    {
        $html = html_entity_decode(parent::saveHTML($node));
        if (is_null($node)) {
            $html = $this->unwrapTempRoot($html);
        }
        return $html;
    }

    public function saveXML(DOMNode $node = null, $options = null)
    {
        if (is_null($node)) {
            return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . PHP_EOL . $this->saveHTML();
        }
        return parent::saveXML($node);
    }

}

Jetzt benutze ich DOMEditorstatt DOMDocumentund es hat bisher gut für mich funktioniert

        $editor = new DOMEditor();
        $editor->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // works like a charm!
        echo $editor->saveHTML();

Ihr Punkt 1. wird mit mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8') gelöst. Bevor Sie loadHTML () und 2.nd verwenden, indem Sie ein DIV-Tag in Ihrer Hilfsfunktion verwenden, verwenden Sie beispielsweise mb_convert_encoding (). Hat für mich gut genug geklappt. In der Tat, wenn kein DIV vorhanden ist, fügt es in meinem Fall automatisch einen Absatz hinzu, was unpraktisch ist, da normalerweise ein gewisser Rand angewendet wird (Bootstrap ..)
Trainoasis

0

Ich bin auch auf dieses Problem gestoßen.

Leider fühlte ich mich mit einer der in diesem Thread bereitgestellten Lösungen nicht wohl, also ging ich zu einer, die mich zufriedenstellte.

Folgendes habe ich erfunden und es funktioniert ohne Probleme:

$domxpath = new \DOMXPath($domDocument);

/** @var \DOMNodeList $subset */
$subset = $domxpath->query('descendant-or-self::body/*');

$html = '';
foreach ($subset as $domElement) {
    /** @var $domElement \DOMElement */
    $html .= $domDocument->saveHTML($domElement);
}

Im Wesentlichen funktioniert es ähnlich wie die meisten hier bereitgestellten Lösungen, aber anstatt manuelle Arbeit zu leisten, verwendet es den xpath-Selektor, um alle Elemente im Körper auszuwählen und ihren HTML-Code zu verketten.


Wie alle Lösungen hier funktioniert es nicht in jedem Fall: Wenn die geladene Zeichenfolge nicht mit dem Markup begonnen hat, <p> </ p> hinzugefügt wurde, funktioniert Ihr Code nicht, da er die hinzufügt <p> </ p> Markup im gespeicherten Inhalt
copndz

Um fair zu sein, ich habe es nicht mit Rohtext getestet, aber theoretisch sollte es funktionieren. Für Ihren speziellen Fall müssen Sie möglicherweise den xpath in etwas wie ändern descendant-or-self::body/p/*.
Nikola Petkanski

0

Mein Server hat PHP 5.3 und kann diese Optionen nicht aktualisieren

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

sind nicht für mich.

Um dies zu lösen, fordere ich die SaveXML-Funktion auf, das Body-Element zu drucken und dann einfach den "body" durch "div" zu ersetzen.

Hier ist mein Code, hoffe er hilft jemandem:

<? 
$html = "your html here";
$tabContentDomDoc = new DOMDocument();
$tabContentDomDoc->loadHTML('<?xml encoding="UTF-8">'.$html);
$tabContentDomDoc->encoding = 'UTF-8';
$tabContentDomDocBody = $tabContentDomDoc->getElementsByTagName('body')->item(0);
if(is_object($tabContentDomDocBody)){
    echo (str_replace("body","div",$tabContentDomDoc->saveXML($tabContentDomDocBody)));
}
?>

Das utf-8 dient der hebräischen Unterstützung.


0

Die Antwort von Alex ist korrekt, kann aber auf leeren Knoten folgenden Fehler verursachen:

Das an DOMNode :: removeChild () übergebene Argument 1 muss eine Instanz von DOMNode sein

Hier kommt mein kleiner Mod:

    $output = '';
    $doc = new DOMDocument();
    $doc->loadHTML($htmlString); //feed with html here

    if (isset($doc->firstChild)) {

        /* remove doctype */

        $doc->removeChild($doc->firstChild);

        /* remove html and body */

        if (isset($doc->firstChild->firstChild->firstChild)) {
            $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
            $output = trim($doc->saveHTML());
        }
    }
    return $output;

Das Hinzufügen von trim () ist auch eine gute Idee, um Leerzeichen zu entfernen.


0

Ich vielleicht zu spät. Aber vielleicht hat jemand (wie ich) noch dieses Problem.
Also hat keines der oben genannten für mich funktioniert. Da $ dom-> loadHTML auch offene Tags schließt, fügen Sie nicht nur HTML- und Body-Tags hinzu.
Das Hinzufügen eines <div> -Elements funktioniert bei mir nicht, da ich manchmal 3-4 nicht geschlossene div im HTML-Teil mag.
Meine Lösung:

1.) Fügen Sie zum Schneiden einen Marker hinzu und laden Sie dann das HTML-Stück

$html_piece = "[MARK]".$html_piece."[/MARK]";
$dom->loadHTML($html_piece);

2.) Machen Sie mit dem Dokument, was Sie wollen.
3.) Speichern Sie HTML

$new_html_piece = $dom->saveHTML();

4.) Bevor Sie es zurückgeben, entfernen Sie <p> </ p> -Tags vom Marker. Seltsamerweise erscheint es nur auf [MARK], nicht aber auf [/ MARK] ...!?

$new_html_piece = preg_replace( "/<p[^>]*?>(\[MARK\]|\s)*?<\/p>/", "[MARK]" , $new_html_piece );

5.) Entfernen Sie alles vor und nach dem Marker

$pattern_contents = '{\[MARK\](.*?)\[\/MARK\]}is';
if (preg_match($pattern_contents, $new_html_piece, $matches)) {
    $new_html_piece = $matches[1];
}

6.) gib es zurück

return $new_html_piece;

Es wäre viel einfacher, wenn LIBXML_HTML_NOIMPLIED für mich funktionieren würde. Es sollte, aber es ist nicht. PHP 5.4.17, libxml Version 2.7.8.
Ich finde es wirklich seltsam, ich benutze den HTML-DOM-Parser und um dieses "Ding" zu reparieren, muss ich Regex verwenden ... Der springende Punkt war, keinen Regex zu verwenden;)


Sieht gefährlich aus, was Sie hier tun. Stackoverflow.com/a/29499718/367456 sollte die Arbeit für Sie erledigen.
hakre

Leider funktioniert dies ( stackoverflow.com/questions/4879946/… ) bei mir nicht. Wie gesagt: "Das Hinzufügen eines <div> -Elements funktioniert bei mir nicht, da ich manchmal 3-4 nicht geschlossene div im HTML-Teil mag." Aus irgendeinem Grund möchte das DOMDocument alle "nicht geschlossenen" Elemente schließen. In einem Fall erhalte ich eine Fregement innerhalb eines Shortcodes oder einer anderen Markierung, entferne die Fregmentierung und möchte das andere Stück des Dokuments bearbeiten. Wenn ich damit fertig bin, füge ich die Fregiation wieder ein.
Joe

Es sollte möglich sein, das div-Element wegzulassen und das body-Element zu bearbeiten, nachdem Sie stattdessen Ihren eigenen Inhalt geladen haben. Das body-Element sollte implizit hinzugefügt werden, wenn Sie ein Fragment laden.
hakre

Mein Problem ist, dass mein Fregment nicht geschlossenes Tag enthält. Es sollte nicht geschlossen bleiben und DOMDocument schließt diese Elemente. Fregabe wie : < div >< div > ... < /div >. Ich suche immer noch nach Lösungen.
Joe

Hmm, ich denke Div-Tags haben immer ein schließendes Paar. Vielleicht kann Tidy damit umgehen, es kann auch mit Fragmenten funktionieren.
hakre

0

Für jeden, der Drupal verwendet, gibt es eine integrierte Funktion, um dies zu tun:

https://api.drupal.org/api/drupal/modules!filter!filter.module/function/filter_dom_serialize/7.x

Code als Referenz:

function filter_dom_serialize($dom_document) {
  $body_node = $dom_document->getElementsByTagName('body')->item(0);
  $body_content = '';

  if ($body_node !== NULL) {
    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 preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
  }
  else {
    return $body_content;
  }
}

Upvoted. Die Verwendung dieser Funktion über die Drupal-API funktioniert auf meiner Drupal 7-Site einwandfrei. Ich denke, diejenigen, die Drupal nicht verwenden, können die Funktion einfach auf ihre eigene Site kopieren - da dies nichts Drupal-spezifisches ist.
Free Radical

0

Sie können ordentlich mit Show-Body-only verwenden:

$tidy = new tidy();
$htmlBody = $tidy->repairString($html, [
  'indent' =>  true,
  'output-xhtml' => true,
  'show-body-only' => true
], 'utf8');

Aber denken Sie daran: Entfernen Sie ordentlich einige Tags wie Font Awesome-Symbole: Probleme beim Einrücken von HTML (5) mit PHP


-1
#remove doctype tag
$doc->removeChild($doc->doctype); 

#remove html & body tags
$html = $doc->getElementsByTagName('html')[0];
$body = $html->getElementsByTagName('body')[0];
foreach($body->childNodes as $child) {
    $doc->appendChild($child);
}
$doc->removeChild($html);

Möchten Sie mitteilen, warum die -1?
Dylan Maxey

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.