web-dev-qa-db-fra.com

Comment sauvegarder HTML de DOMDocument sans encapsuleur HTML?

Je suis la fonction ci-dessous, j'ai du mal à sortir le DOMDocument sans l'ajout des enveloppeurs XML, HTML, body et p tag avant la sortie du contenu. Le correctif suggéré:

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

Ne fonctionne que lorsque le contenu ne contient aucun élément de niveau bloc. Toutefois, dans ce cas, comme dans l'exemple ci-dessous avec l'élément h1, la sortie résultante de saveXML est tronquée en ...

<p> Si vous aimez </ p>

Ce message est considéré comme une solution de contournement possible, mais je ne comprends pas comment le mettre en œuvre dans cette solution (voir les tentatives commentées ci-dessous).

Aucune suggestion?

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;
}
96
Scott B

Toutes ces réponses sont maintenant faux, car à partir de PHP 5.4 et de Libxml 2.6 loadHTML , le paramètre $option indique maintenant à Libxml comment analyser le contenu.

Donc, si on charge le HTML avec ces options

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

en faisant saveHTML(), il n'y aura pas de doctype, ni de <html> ni de <body>.

LIBXML_HTML_NOIMPLIED désactive l'ajout automatique d'éléments html/body implicites LIBXML_HTML_NODEFDTD empêche l'ajout d'un doctype par défaut lorsqu'il n'en trouve pas un.

Une documentation complète sur les paramètres Libxml est ici

(Notez que les documents loadHTML indiquent que Libxml 2.6 est nécessaire, mais que LIBXML_HTML_NODEFDTD est uniquement disponible en Libxml 2.7.8 et que LIBXML_HTML_NOIMPLIED est disponible en Libxml 2.7.7)

175

Supprimez simplement les nœuds directement après le chargement du document avec loadHTML (): 

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

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

Utilisez plutôt saveXML() et transmettez le documentElement en tant qu'argument.

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

http://php.net/domdocument.savexml

18
Jonah

Une astuce consiste à utiliser loadXML puis saveHTML . Les balises html et body sont insérées au stade load et non au stade save.

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

NB: ceci est un peu hacky et vous devriez utiliser la réponse de Jonah si vous pouvez le faire fonctionner.

13
lonesomeday

utiliser DOMDocumentFragment

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

Je suis un peu en retard dans le club mais je ne voulais pas pas partager une méthode que j'ai découverte. Tout d'abord, j'ai les bonnes versions pour que loadHTML () accepte ces options de Nice, mais LIBXML_HTML_NOIMPLIED ne fonctionnait pas sur mon système. De plus, les utilisateurs signalent des problèmes avec l'analyseur (par exemple ici et ici ).

La solution que j'ai créée est en fait assez simple.

Le code HTML à charger est placé dans un élément <div> de sorte qu'il contienne un conteneur contenant tous les nœuds à charger.

Ensuite, cet élément conteneur est supprimé du document (mais le DOMElement de celui-ci existe toujours).

Tous les enfants directs du document sont ensuite supprimés. Ceci inclut toutes les balises <html>, <head> et <body> ajoutées (option LIBXML_HTML_NOIMPLIED) ainsi que la déclaration <!DOCTYPE html ... loose.dtd"> (effectivement LIBXML_HTML_NODEFDTD).

Tous les enfants directs du conteneur sont ensuite ajoutés au document et celui-ci peut être généré.

$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 fonctionne comme d'habitude, veillez simplement à ce qu'il y ait plusieurs éléments de document maintenant, donc pas un seul nœud racine:

$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écise + 2 (cli) (construite le 21 décembre 2014 20:28:53) 
10
hakre

Nous sommes en 2017, et pour cette question de 2011, je n'aime aucune des réponses .

Solution facile qui résout les problèmes connus:

$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);

Facile, Simple, Solide, Rapide. Ce code fonctionnera en ce qui concerne les balises HTML et l’encodage comme:

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

Si quelqu'un trouve une erreur, dites-le-moi, je l'utiliserai moi-même.

Edit , Autres options valides qui fonctionnent sans erreur (très similaires à celles déjà données):

@$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 );

Vous pouvez ajouter du corps vous-même pour éviter toute chose étrange sur le furure.

Trente 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());
8
Vixxs

Utilisez cette fonction

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

D'accord, j'ai trouvé une solution plus élégante, mais c'est fastidieux:

$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
}

Bon, espérons que cela n'omette rien et aide quelqu'un?

4
rclai

Aucune des autres solutions au moment de la rédaction de cet article (juin 2012) n'a été en mesure de répondre complètement à mes besoins. J'ai donc écrit l'une de ces solutions: 

  • Accepte le contenu en texte brut sans balises, ainsi que le contenu HTML.
  • N'ajoute aucune balise (y compris les balises <doctype>, <xml>, <html>, <body> et <p>)
  • Laisse tout ce qui est enveloppé dans <p> seul.
  • Laisse le texte vide seul.

Voici donc une solution qui résout ces problèmes:

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

J'ai aussi écrit quelques tests qui vivraient dans cette même classe:

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
}

Vous pouvez vérifier que cela fonctionne pour vous-même. DomDocumentWorkaround::testAll() renvoie ceci:

    Succeeded
    Succeeded
    Succeeded
    Succeeded
4
plowman

L'ajout de la balise <meta> déclenchera le comportement de correction de DOMDocument. La bonne partie est que vous n'avez pas besoin d'ajouter cette balise du tout. Si vous ne souhaitez pas utiliser l'encodage de votre choix, transmettez-le en tant qu'argument constructeur.

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();

Sortie

<div>Hello World</div>

Merci à @Bart

2
botenvouwer

J'avais aussi cette exigence et j'ai aimé la solution publiée par Alex ci-dessus. Il existe cependant quelques problèmes: si l'élément <body> contient plusieurs éléments enfants, le document obtenu ne contiendra que le premier élément enfant de <body>, pas tous. En outre, j'avais besoin de la suppression pour gérer les choses de manière conditionnelle - uniquement lorsque vous aviez un document avec les en-têtes HTML. Alors je l'ai affiné comme suit. Au lieu de supprimer <body>, je l'ai transformé en <div>, et j'ai supprimé la déclaration XML et <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
blackcatweb

J'ai PHP 5.3 et les réponses ici ne fonctionnent pas pour moi.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild); a remplacé tout le document par le premier enfant seulement. J'avais plusieurs paragraphes et seul le premier était sauvegardé, mais la solution m'a donné un bon point de départ pour écrire quelque chose sans regex J'ai laissé quelques commentaires et je suis sûr que cela peut être amélioré mais si quelqu'un a le même problème que moi, cela peut être un bon point de départ.

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

Ensuite, nous pourrions l'utiliser comme ceci:

$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);

Notez que appendChild accepte un DOMNode afin de ne pas avoir besoin de créer de nouveaux éléments, nous pouvons simplement réutiliser ceux existants qui implémentent DOMNodesuch comme DOMElement cela peut être important de garder le code "sain" lorsque manipuler plusieurs documents HTML/XML

2

Comme beaucoup d'autres membres, je me suis d'abord régalé de la simplicité et de la puissance impressionnante de la réponse de @Alessandro Vendruscolo. La possibilité de simplement transmettre au constructeur certaines constantes marquées semblait trop belle pour être vraie. Pour moi c'était. J'ai les versions correctes de LibXML et de PHP, mais quoi qu'il en soit, la balise HTML serait ajoutée à la structure de nœud de l'objet Document. 

Ma solution a bien mieux fonctionné que d'utiliser le ...

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

Drapeaux ou ....

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

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

Suppression de nœud, qui devient désordonnée sans ordre structuré dans le DOM. Encore une fois, les fragments de code n'ont aucun moyen de prédéterminer la structure du DOM.

J'ai commencé ce voyage en cherchant un moyen simple d'effectuer une traversée du DOM comme JQuery le fait ou du moins d'une manière qui comporte un ensemble de données structuré, soit une traversée de nœud simple, double ou arborée. Je ne me souciais pas de savoir si, tant que je pouvais analyser une chaîne comme le fait HTML, je pouvais aussi utiliser l'incroyable pouvoir des propriétés de classe d'entité de nœud en cours de route. 

Jusqu'ici, DOMDocument Object m'a laissé vouloir ... Comme beaucoup d'autres programmeurs, il semble ... Je sais que j'ai vu beaucoup de frustration dans cette question, donc depuis FINALLY .... (après environ 30 heures d'essais et d'échecs tests de type) J'ai trouvé un moyen de tout obtenir. J'espère que ça aidera quelqu'un...

Tout d'abord, je suis cynique de TOUT ... lol ...

J'aurais passé toute ma vie avant de convenir avec quiconque qu'une classe de tiers est de toute façon nécessaire dans ce cas d'utilisation. Je n’étais vraiment pas et ne suis PAS un fan d’utiliser une structure de classe tierce, mais j’ai trébuché sur un excellent analyseur. (environ 30 fois dans Google avant de céder, alors ne vous sentez pas seul si vous l'évitiez, car cela avait l'air minable de façon officieuse ...)

Si vous utilisez des fragments de code et que vous avez besoin de, du code propre et non affecté par l’analyseur de quelque manière que ce soit, sans balises supplémentaires, utilisez simplePHPParser

C'est incroyable et agit beaucoup comme JQuery. Je ne suis pas souvent impressionné, mais cette classe utilise beaucoup de bons outils et je n’ai pas eu d’erreur d’analyse syntaxique pour le moment. Je suis un grand fan de pouvoir faire ce que cette classe fait.

Vous pouvez trouver ses fichiers à télécharger ici , ses instructions de démarrage ici et son API ici . Je recommande fortement d'utiliser cette classe avec ses méthodes simples qui peuvent créer un .find(".className") de la même manière qu'une méthode de recherche JQuery serait utilisée ou même des méthodes familières telles que getElementByTagName() ou getElementById()...

Lorsque vous enregistrez une arborescence de nœuds dans cette classe, cela n'ajoute rien. Vous pouvez simplement dire $doc->save(); et l’arborescence complète est générée sous forme de chaîne sans fioritures.

Je vais maintenant utiliser cet analyseur syntaxique pour tous les projets futurs à bande passante non limitée.

2
GoreDefex

Je suis tombé sur ce sujet pour trouver un moyen de supprimer le wrapper HTML. Utiliser LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD fonctionne très bien, mais j'ai un problème avec utf-8. Après beaucoup d'efforts, j'ai trouvé une solution. Je poste ci-dessous pour quiconque a le même problème.

Le problème causé par <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Le problème:

$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();

Solution 1:

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

Solution 2:

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

Si la solution flags de Alessandro Vendruscolo ne fonctionne pas, essayez ceci:

$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;

$bodyTag contiendra votre code HTML complet traité sans toutes ces enveloppes HTML, à l'exception de la balise <body>, qui est la racine de votre contenu. Ensuite, vous pouvez utiliser une expression rationnelle ou une fonction trim pour la supprimer de la chaîne finale (après saveHTML) ou, comme dans le cas précédent, parcourir l'ensemble de ses enfants, en enregistrant leur contenu dans une variable temporaire $finalHtml et le renvoyer (que croire être plus en sécurité).

#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);
0
Dylan Maxey

Je suis tombé sur cette question aussi.

Malheureusement, je ne me suis pas sentie à l'aise d'utiliser aucune des solutions fournies dans ce fil de discussion, alors je suis allée vérifier celle qui me conviendrait le mieux.

Voici ce que j'ai composé et cela fonctionne sans problème:

$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);
}

En substance, il fonctionne de manière similaire à la plupart des solutions fournies ici, mais au lieu de faire un travail manuel, il utilise le sélecteur xpath pour sélectionner tous les éléments du corps et concaténer leur code html.

0
Nikola Petkanski

mon serveur a php 5.3 et ne peut pas mettre à jour ces options

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

ne sont pas pour moi.

Pour résoudre ce problème, je demande à la fonction SaveXML d'imprimer l'élément Body, puis de remplacer le "body" par "div"

voici mon code, j'espère que ça aide quelqu'un:

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

le utf-8 est pour le support en hébreu.

0
Tomer Ofer

Cette bibliothèque simplifie le repérage/la modification du DOM et prend également en charge la suppression des wrappers doctype/html:

https://github.com/sunra/php-simple-html-dom-parser

0
GSTAR

La réponse d'Alex est correcte, mais peut provoquer une erreur suivante sur les nœuds vides:

L'argument 1 passé à DOMNode :: removeChild () doit être une instance de DOMNode

Voici mon petit 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;

L'ajout de la fonction trim () est également une bonne idée pour supprimer les espaces.

0
redaxmedia

Pour ceux qui utilisent Drupal, il existe une fonction intégrée pour le faire: 

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

Code pour référence:

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;
  }
}
0
leon.nk

Je suis aux prises avec cette situation sur RHEL7 sous PHP 5.6.25 et LibXML 2.9. (Les vieux trucs en 2018, je sais, mais c'est Red Hat pour vous.)

J'ai trouvé que la solution beaucoup plus votée suggérée par Alessandro Vendruscolo casse le code HTML en réorganisant les balises. C'est à dire.:

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

devient:

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

Cela vaut pour les deux options suggérées par vous: LIBXML_HTML_NOIMPLIED et LIBXML_HTML_NODEFDTD.

La solution suggérée par Alex va à mi-chemin pour la résoudre, mais cela ne fonctionne pas si <body> a plus d'un nœud enfant.

La solution qui fonctionne pour moi est la suivante:

Tout d'abord, pour charger le DOMDocument, j'utilise:

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

Pour enregistrer le document après avoir massé le DOMDocument, j'utilise:

// 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);

Je suis le premier à convenir qu'il ne s'agit pas d'une solution très élégante, mais cela fonctionne.

0
Free Radical

Moi peut-être trop tard. Mais peut-être que quelqu'un (comme moi) a toujours ce problème.
Donc, rien de ce qui précède n'a fonctionné pour moi. Etant donné que $ dom-> loadHTML ferme également les balises ouvertes, non seulement ajoutez des balises html et body.
Ainsi, ajouter un élément <div> ne fonctionne pas pour moi, car j’ai parfois comme 3-4 div non-fermées dans le fichier html.
Ma solution:

1.) Ajoutez un marqueur pour couper, puis chargez le morceau html

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

2.) faites ce que vous voulez avec le document
3.) Sauvegarder le HTML

$new_html_piece = $dom->saveHTML();

4.) avant de le retourner, supprimez les balises <p> </ p> du marqueur, étrangement, elles n'apparaissent que sur [MARK] mais pas sur [/ MARK] ...!?

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

5.) tout enlever avant et après marqueur

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

6.) le retourner

return $new_html_piece;

Ce serait beaucoup plus facile si LIBXML_HTML_NOIMPLIED fonctionnait pour moi. Cela devrait être fait, mais ce n'est pas le cas. PHP 5.4.17, version libxml 2.7.8. 
Je trouve vraiment étrange, j’utilise l’analyseur HTML DOM puis, pour corriger ce "truc", je dois utiliser regex ... L’important était de ne pas utiliser regex;)

0
Joe