Come salvare l'HTML di DOMDocument senza wrapper HTML?


116

Sono la funzione di seguito, sto lottando per produrre il DOMDocument senza che aggiunga i wrapper XML, HTML, body e p tag prima dell'output del contenuto. La soluzione suggerita:

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

Funziona solo quando il contenuto non ha elementi a livello di blocco al suo interno. Tuttavia, quando lo fa, come nell'esempio seguente con l'elemento h1, l'output risultante da saveXML viene troncato a ...

<p> Se ti piace </p>

Sono stato indicato questo post come possibile soluzione alternativa, ma non riesco a capire come implementarlo in questa soluzione (vedi i tentativi commentati di seguito).

Eventuali suggerimenti?

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

Risposte:


217

Tutte queste risposte ora sono sbagliate , perché a partire da PHP 5.4 e Libxml 2.6 loadHTMLora ha un $optionparametro che istruisce Libxml su come dovrebbe analizzare il contenuto.

Pertanto, se carichiamo l'HTML con queste opzioni

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

quando si fa saveHTML()ci sarà no doctype, no <html>e no <body>.

LIBXML_HTML_NOIMPLIEDdisattiva l'aggiunta automatica di elementi html / body impliciti LIBXML_HTML_NODEFDTDimpedisce l'aggiunta di un doctype predefinito quando non viene trovato.

La documentazione completa sui parametri di Libxml è disponibile qui

(Nota che i loadHTMLdocumenti dicono che Libxml 2.6 è necessario, ma LIBXML_HTML_NODEFDTDè disponibile solo in Libxml 2.7.8 ed LIBXML_HTML_NOIMPLIEDè disponibile in Libxml 2.7.7)


10
Funziona come un fascino. Dovrebbe essere la risposta accettata. Ho appena aggiunto una bandiera e tutti i miei mal di testa sono spariti ;-)
Just Plain High

8
Questo non funziona con PHP 5.4 e Libxml 2.9. loadHTML non accetta alcuna opzione :(
Acyra

11
Nota che questo non è del tutto perfetto. Vedi stackoverflow.com/questions/29493678/…
Josh Levinson

4
Scusa, ma questa non sembra essere affatto una buona soluzione (almeno non in pratica). In realtà non dovrebbe essere la risposta accettata. Oltre ai problemi citati, c'è anche un problema di codifica brutto con DOMDocumentche colpisce anche il codice in questa risposta. Afaik, DOMDocumentinterpreta sempre i dati di input come latin-1 a meno che l'input non specifichi un diverso set di caratteri . In altre parole: il <meta charset="…">tag sembra essere necessario per i dati di input che non sono latin-1. In caso contrario, l'output verrà interrotto per esempio per i caratteri multibyte UTF-8.
mermshaus

1
LIBXML_HTML_NOIMPLIED rovina anche il codice HTML rimuovendo le tabulazioni, i rientri e le interruzioni di riga
Zoltán Süle

72

Basta rimuovere i nodi direttamente dopo aver caricato il documento con loadHTML ():

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

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

questa è la risposta più pulita per me.
KnF

39
va notato che questo funziona se <body> ha un solo nodo figlio.
Yann Milin

Ha funzionato alla grande. Grazie! Molto più pulito e più veloce dell'altra risposta preg.
Ligemer

Grazie per questo! Ho appena aggiunto un altro taglio in basso per gestire i nodi vuoti.
redaxmedia

2
Il codice per rimuovere <!DOCTYPE funziona. La seconda riga si interrompe se <body>ha più di una nota figlio.
Radicale libero

21

Usa saveXML()invece e passa il documentElement come argomento ad esso.

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

http://php.net/domdocument.savexml


Va meglio, ma sto ancora ottenendo <html><body> <p> che racchiude i contenuti.
Scott B


2
Va notato che saveXML () salverà XHTML, non HTML.
alexantd

@Scott: è davvero strano. Mostra cosa stai cercando di fare proprio lì nella sezione degli esempi. Sei sicuro di non avere quell'HTML nel tuo DOM? Che cos'è esattamente l'HTML nel tuo DOMDocument? Potrebbe essere necessario accedere a un nodo figlio.
Jonah

@ Giona non è strano. Quando lo fai, loadHTMLlibxml usa il modulo parser HTML e questo inserirà lo scheletro HTML mancante. Di conseguenza, $dom->documentElementsarà l'elemento HTML radice. Ho corretto il tuo codice di esempio. Ora dovrebbe fare ciò che Scott chiede.
Gordon

19

Il problema con la risposta principale è che LIBXML_HTML_NOIMPLIEDè instabile .

Può riordinare gli elementi (in particolare, spostando il tag di chiusura dell'elemento superiore in fondo al documento), aggiungere ptag casuali e forse una varietà di altri problemi [1] . Potrebbe rimuovere i tag htmle bodyper te, ma a costo di un comportamento instabile. In produzione, questa è una bandiera rossa. In breve:

Non usareLIBXML_HTML_NOIMPLIED . Invece, usasubstr .


Pensaci. Le lunghezze <html><body>e </body></html>sono fisse e ad entrambe le estremità del documento: le loro dimensioni non cambiano mai e nemmeno le loro posizioni. Questo ci permette di utilizzare substrper tagliarli via:

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

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

( QUESTA NON È TUTTAVIA LA SOLUZIONE FINALE! Vedi sotto per la risposta completa , continua a leggere per il contesto)

Tagliamo 12via l'inizio del documento perché <html><body>= 12 caratteri ( <<>>+html+body= 4 + 4 + 4), e andiamo indietro e tagliamo 15 alla fine perché \n</body></html>= 15 caratteri ( \n+//+<<>>+body+html= 1 + 2 + 4 + 4 + 4)

Notare che uso ancora LIBXML_HTML_NODEFDTDomettere !DOCTYPEda essere incluso. Innanzitutto, questo semplifica la substrrimozione dei tag HTML / BODY. Secondo, non rimuoviamo il doctype con substrperché non sappiamo se " default doctype" sarà sempre qualcosa di una lunghezza fissa. Ma, cosa più importante, LIBXML_HTML_NODEFDTDimpedisce al parser DOM di applicare un doctype non HTML5 al documento, il che almeno impedisce al parser di trattare gli elementi che non riconosce come testo sciolto.

Sappiamo per LIBXML_HTML_NODEFDTDcerto che i tag HTML / BODY hanno lunghezze e posizioni fisse e sappiamo che costanti come non vengono mai rimosse senza un qualche tipo di avviso di deprecazione, quindi il metodo sopra dovrebbe essere implementato in futuro, MA ...


... l'unico avvertimento è che l'implementazione DOM potrebbe cambiare il modo in cui i tag HTML / BODY vengono inseriti all'interno del documento, ad esempio rimuovendo la nuova riga alla fine del documento, aggiungendo spazi tra i tag o aggiungendo nuove righe.

Questo può essere risolto cercando le posizioni dei tag di apertura e chiusura bodye usando quegli offset come per le nostre lunghezze da tagliare. Usiamo strpose strrposper trovare gli offset rispettivamente dalla parte anteriore e posteriore:

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

In chiusura, una ripetizione della risposta finale a prova di futuro :

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

Nessun doctype, nessun tag html, nessun tag body. Possiamo solo sperare che il parser DOM riceva presto una nuova mano di vernice e possiamo eliminare più direttamente questi tag indesiderati.


Ottima risposta, un piccolo commento, perché no $html = $dom -> saveHTML();invece di $dom -> saveHTML();ripetutamente?
Steven

15

Un bel trucco è usare loadXMLe poi saveHTML. I tag htmle bodyvengono inseriti nello loadstage, non nello savestage.

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

NB questo è un po 'complicato e dovresti usare la risposta di Jonah se riesci a farlo funzionare.


4
Tuttavia, questo fallirà per HTML non valido.
Gordon

1
@Gordon Esattamente il motivo per cui metto il disclaimer in fondo!
solomeday

1
Quando provo questo, e echo $ dom-> saveHTML (), restituisce solo una stringa vuota. Come se loadXML ($ content) fosse vuoto. Quando faccio lo stesso con $ dom-> loadHTML ($ content), quindi echo $ dom-> saveXML () ottengo il contenuto come previsto.
Scott B

Usare loadXML quando si desidera caricare HTMl è thumb. Soprattutto perché LoadXML non sa come gestire l'HTML.
botenvouwer

15

utilizzare DOMDocumentFragment

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

3
La risposta più pulita per pre php5.4.
Nick Johnson

Questo funziona per me, sia più vecchio che più recente della versione Libxml 2.7.7. Perché questo dovrebbe essere solo per pre php5.4?
RobbertT

Questo dovrebbe avere più voti. Ottima opzione per le versioni di libxml che non supportano LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD. Grazie!
Marty Mulligan

13

È il 2017 e per questa domanda del 2011 non mi piacciono le risposte. Molte espressioni regolari, grandi classi, loadXML ecc ...

Soluzione semplice che risolve i problemi noti:

$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, semplice, solido, veloce. Questo codice funzionerà per quanto riguarda i tag HTML e la codifica come:

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

Se qualcuno trova un errore, per favore dillo, lo userò io stesso.

Modifica , Altre opzioni valide che funzionano senza errori (molto simili a quelle già fornite):

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

Potresti aggiungere corpo da solo per evitare qualsiasi cosa strana sul pelo.

Opzione Thirt:

 $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
Dovresti migliorare la tua risposta evitando il più costoso mb_convert_encodinge aggiungendo <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>e modificando di substrconseguenza. A proposito, la tua è la soluzione più elegante qui. Upvoted.
Hlsg

10

Sono un po 'in ritardo nel club ma non volevo non condividere un metodo che ho scoperto. Prima di tutto ho le versioni giuste per loadHTML () per accettare queste belle opzioni, ma LIBXML_HTML_NOIMPLIEDnon ha funzionato sul mio sistema. Anche gli utenti segnalano problemi con il parser (ad esempio qui e qui ).

La soluzione che ho creato in realtà è piuttosto semplice.

L'HTML da caricare viene inserito in un <div>elemento in modo che abbia un contenitore contenente tutti i nodi da caricare.

Quindi questo elemento contenitore viene rimosso dal documento (ma il DOMElement di esso esiste ancora).

Quindi tutti i figli diretti dal documento vengono rimossi. Ciò include qualsiasi aggiunto <html>, <head>e <body>tag (in modo efficace LIBXML_HTML_NOIMPLIEDopzione), nonché la <!DOCTYPE html ... loose.dtd">dichiarazione (di fatto LIBXML_HTML_NODEFDTD).

Quindi tutti i figli diretti del contenitore vengono aggiunti di nuovo al documento e può essere emesso.

$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 funziona come al solito, basta fare attenzione che ora ci siano più elementi del documento, quindi non un singolo nodo radice:

$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 ~ precise + 2 (cli) (costruito: 21 dicembre 2014 20:28:53)

non ha funzionato per me con sorgenti HTML più complesse. Ha anche rimosso una determinata parte dell'HTML.
Zoltán Süle

4

Nessuna delle altre soluzioni al momento della stesura di questo articolo (giugno 2012) era in grado di soddisfare completamente le mie esigenze, quindi ne ho scritta una che gestisce i seguenti casi:

  • Accetta contenuto di testo normale che non ha tag, così come contenuto HTML.
  • Non aggiungere nessun tag (tra cui <doctype>, <xml>, <html>, <body>, e <p>tag)
  • Lascia tutto <p>da solo avvolto .
  • Lascia solo il testo vuoto.

Quindi ecco una soluzione che risolve questi problemi:

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

Ho anche scritto alcuni test che sarebbero vissuti nella stessa 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
}

Puoi verificare che funzioni da solo. DomDocumentWorkaround::testAll()restituisce questo:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

1
HTML = / = XML, dovresti usare il caricatore HTML per HTML.
hakre

4

Ok, ho trovato una soluzione più elegante, ma è solo noiosa:

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

Va bene, si spera che questo non ometta nulla e aiuti qualcuno?


2
Non gestisce il caso in cui loadHTML carica una stringa senza markup
copndz

3

Usa questa funzione

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

13
Potrebbero esserci alcuni lettori che si sono imbattuti in questo post tramite questo post , hanno deciso di non utilizzare regex per analizzare il loro HTML e utilizzare invece un parser DOM, e finire per aver potenzialmente bisogno di una risposta regex per ottenere una soluzione completa ... ironico
Robbie Averill

Non capisco perché nessuno restituisca semplicemente il contenuto di BODY. Non si suppone che quel tag sia sempre presente quando il parser aggiunge l'intero documento header / doctype? L'espressione regolare sopra sarebbe anche più breve.
sergio

@boksiora "fa il lavoro" - allora perché stiamo usando i metodi del parser DOM in primo luogo?
Grazie

@naomik non ho detto di non usare un parser DOM, ci sono ovviamente molti modi diversi per ottenere lo stesso risultato, dipende da te, nel momento in cui ho usato questa funzione ho avuto un problema con il dom php integrato parser, che non stava analizzando correttamente html5.
boksiora

1
Ho dovuto usarlo preg_replaceperché l'uso di metodi basati su DOMDocument per rimuovere i tag html e body non conservava la codifica UTF-8 :(
wizonesolutions

3

Se la soluzione dei flag a cui ha risposto Alessandro Vendruscolo non funziona, puoi provare questo:

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

$bodyTagconterrà il codice HTML completamente elaborato senza tutti quei wraps HTML, ad eccezione del <body>tag, che è la radice del contenuto. Quindi puoi usare una regex o una funzione trim per rimuoverlo dalla stringa finale (dopo saveHTML) o, come nel caso sopra, iterare su tutti i suoi figli, salvando il loro contenuto in una variabile temporanea $finalHtmle restituendolo (quello che credo sia più sicuro).


3

Sto lottando con questo su RHEL7 con PHP 5.6.25 e LibXML 2.9. (Cose vecchie nel 2018, lo so, ma questo è Red Hat per te.)

Ho scoperto che la soluzione molto votata suggerita da Alessandro Vendruscolo rompe l'HTML riorganizzando i tag. Vale a dire:

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

diventa:

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

Questo vale per entrambe le opzioni che ti suggerisce di usare: LIBXML_HTML_NOIMPLIEDe LIBXML_HTML_NODEFDTD.

La soluzione suggerita da Alex va a metà strada per risolverlo, ma non funziona se <body>ha più di un nodo figlio.

La soluzione che funziona per me è la seguente:

Innanzitutto, per caricare il DOMDocument, utilizzo:

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

Per salvare il documento dopo aver massaggiato il DOMDocument, utilizzo:

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

Sono il primo a concordare sul fatto che questa non è una soluzione molto elegante, ma funziona.


2

L'aggiunta del <meta>tag attiverà il comportamento di correzione di DOMDocument. La parte buona è che non è necessario aggiungere affatto quel tag. Se non vuoi usare una codifica di tua scelta, passala come argomento del costruttore.

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

Produzione

<div>Hello World</div>

Grazie a @Bart


2

Anch'io avevo questo requisito e mi è piaciuta la soluzione pubblicata da Alex sopra. Ci sono un paio di problemi, tuttavia: se l' <body>elemento contiene più di un elemento figlio, il documento risultante conterrà solo il primo elemento figlio di <body>, non tutti. Inoltre, avevo bisogno dello stripping per gestire le cose in modo condizionale, solo quando avevi un documento con i titoli HTML. Quindi l'ho perfezionato come segue. Invece di rimuoverlo <body>, l'ho trasformato in un <div>e ho rimosso la dichiarazione XML e <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

Proprio come gli altri membri, per la prima volta mi sono goduto la semplicità e la straordinaria potenza della risposta di @Alessandro Vendruscolo. La capacità di passare semplicemente alcune costanti contrassegnate al costruttore sembrava troppo bella per essere vera. Per me lo è stato. Ho le versioni corrette sia di LibXML che di PHP, tuttavia non importa cosa aggiungerei ancora il tag HTML alla struttura del nodo dell'oggetto Document.

La mia soluzione ha funzionato molto meglio rispetto all'utilizzo del ...

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

Bandiere o ...

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

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

Node Removal, che diventa disordinato senza un ordine strutturato nel DOM. Anche in questo caso i frammenti di codice non hanno modo di predeterminare la struttura DOM.

Ho iniziato questo viaggio desiderando un modo semplice per eseguire l'attraversamento DOM come fa JQuery o almeno in qualche modo che avesse un set di dati strutturati o collegato singolarmente, doppiamente collegato o attraversamento del nodo ad albero. Non mi importava quanto tempo potevo analizzare una stringa come fa l'HTML e ho anche l'incredibile potere delle proprietà della classe di entità del nodo da utilizzare lungo il percorso.

Finora DOMDocument Object mi ha lasciato con la voglia ... Come con molti altri programmatori sembra ... So di aver visto molta frustrazione in questa domanda, quindi da quando FINALMENTE ... (dopo circa 30 ore di tentativi e fallimenti test di tipo) ho trovato un modo per ottenere tutto. Spero che questo aiuti qualcuno ...

Prima di tutto, sono cinico di TUTTO ... lol ...

Sarei passato una vita prima di concordare con qualcuno che una classe di terze parti è comunque necessaria in questo caso d'uso. Ero molto e NON sono un fan dell'utilizzo di qualsiasi struttura di classe di terze parti, tuttavia sono incappato in un ottimo parser. (circa 30 volte su Google prima che cedessi, quindi non sentirti solo se l'hai evitato perché sembrava poco ufficiale in alcun modo ...)

Se stai usando frammenti di codice e hai bisogno del codice pulito e non influenzato dal parser in alcun modo, senza che vengano usati tag aggiuntivi, usa simplePHPParser .

È incredibile e si comporta in modo molto simile a JQuery. Non sono stato spesso impressionato, ma questa classe fa uso di molti buoni strumenti e non ho ancora avuto errori di analisi. Sono un grande fan di poter fare ciò che fa questo corso.

Puoi trovare i suoi file da scaricare qui , le sue istruzioni di avvio qui e la sua API qui . Consiglio vivamente di utilizzare questa classe con i suoi metodi semplici che possono fare .find(".className")allo stesso modo in cui verrebbe utilizzato un metodo di ricerca JQuery o anche metodi familiari come getElementByTagName()o getElementById()...

Quando salvi un albero dei nodi in questa classe, non aggiunge nulla. Puoi semplicemente dire $doc->save();e restituisce l'intero albero a una stringa senza problemi.

In futuro userò questo parser per tutti i progetti con larghezza di banda non limitata.


2

Ho PHP 5.3 e le risposte qui non hanno funzionato per me.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);ha sostituito tutto il documento con solo il primo figlio, avevo molti paragrafi e solo il primo veniva salvato, ma la soluzione mi ha dato un buon punto di partenza per scrivere qualcosa senza regexlasciare qualche commento e sono abbastanza sicuro che possa essere migliorato ma se qualcuno ha lo stesso problema come me può essere un buon punto di partenza.

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

Quindi potremmo usarlo in questo modo:

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

Nota che appendChildaccetta un DOMNodequindi non è necessario creare nuovi elementi, possiamo semplicemente riutilizzare quelli esistenti che implementano DOMNodecome DOMElementquesto possono essere importanti per mantenere il codice "sano" quando si manipolano più documenti HTML / XML


Questo non funzionerà per i frammenti, solo per un singolo elemento figlio che vuoi rendere il primo figlio del documento. Questo è piuttosto limitato ed effettivamente non fa il lavoro del LIBXML_HTML_NOIMPLIEDcome lo fa solo parzialmente. La rimozione del doctype è efficace LIBXML_HTML_NODEFDTD.
hakre

2

Mi sono imbattuto in questo argomento per trovare un modo per rimuovere il wrapper HTML. L'utilizzo LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTDfunziona alla grande, ma ho un problema con utf-8. Dopo molti sforzi ho trovato una soluzione. Lo posto sotto per chiunque abbia lo stesso problema.

Il problema causato a causa di <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Il problema:

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

Soluzione 1:

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

Soluzione 2:

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

1
Trovo carino che tu condivida le tue scoperte, ma la Soluzione 2 è già presente con queste esatte domande qui e la Soluzione 1 è altrove. Anche per il problema della soluzione 1 la risposta data non è chiara. Onoro le tue buone intenzioni, ma tieni presente che può creare molto rumore e impedire agli altri di trovare le soluzioni che stanno cercando, il che immagino sia un po 'l'opposto di ciò che vuoi ottenere con la tua risposta. Stackoverflow funziona meglio se gestisci una domanda alla volta. Solo un suggerimento.
hakre

2

Affronto 3 problemi con la DOMDocumentclasse.

1- Questa classe carica html con codifica ISO e caratteri utf-8 non visualizzati nell'output.

2- Anche se diamo LIBXML_HTML_NOIMPLIEDbandiera per il metodo loadHTML, fino al nostro html di input non contiene un tag principale, non sarà analizzare correttamente.

3- Questa classe considera i tag HTML5 non validi.

Quindi ho sovrascritto questa classe per risolvere questi problemi e ho cambiato alcuni metodi.

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

}

Ora sto usando DOMEditorinvece di DOMDocumente finora ha funzionato bene per me

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

Il tuo punto 1. è risolto usando mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8'); prima di usare loadHTML () e il 2.nd avendo un tag DIV nella tua funzione di supporto, intorno a mb_convert_encoding () che usi per esempio. Ha funzionato abbastanza bene per me. Infatti, se non è presente alcun DIV, aggiunge automaticamente un paragrafo nel mio caso, il che è scomodo poiché di solito hanno un margine applicato (bootstrap ..)
trainoasis

0

Mi sono imbattuto anche in questo problema.

Sfortunatamente, non mi sono sentito a mio agio nell'usare nessuna delle soluzioni fornite in questo thread, quindi sono andato a verificarne una che mi soddisfacesse.

Ecco cosa ho inventato e funziona senza problemi:

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

In sostanza funziona in modo simile alla maggior parte delle soluzioni fornite qui, ma invece di fare il lavoro manuale utilizza xpath selector per selezionare tutti gli elementi all'interno del corpo e concatena il loro codice html.


Come tutte le soluzioni qui, non funziona per tutti i casi: se la stringa caricata non è iniziata con il markup, <p> </p> è stato aggiunto, il tuo codice non funziona, poiché aggiungerà il <p> </p> markup nel contenuto salvato
copndz

Per essere onesti, non l'ho testato con testo grezzo, ma in teoria dovrebbe funzionare. Per il tuo caso specifico potresti dover cambiare xpath in qualcosa di simile descendant-or-self::body/p/*.
Nikola Petkanski

0

il mio server ha php 5.3 e non può aggiornare così quelle opzioni

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

non sono per me.

Per risolvere questo problema dico alla funzione SaveXML di stampare l'elemento Body e quindi sostituire semplicemente "body" con "div"

ecco il mio codice, spero stia aiutando qualcuno:

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

l'utf-8 è per il supporto ebraico.


0

La risposta di Alex è corretta, ma potrebbe causare il seguente errore sui nodi vuoti:

L'argomento 1 passato a DOMNode :: removeChild () deve essere un'istanza di DOMNode

Ecco la mia piccola 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;

Anche l'aggiunta di trim () è una buona idea per rimuovere gli spazi bianchi.


0

Forse è troppo tardi. Ma forse qualcuno (come me) ha ancora questo problema.
Quindi, nessuno dei precedenti ha funzionato per me. Poiché $ dom-> loadHTML chiude anche i tag aperti, non solo aggiunge tag html e body.
Quindi aggiungere un elemento <div> non funziona per me, perché a volte ho 3-4 div non chiusi nel pezzo html.
La mia soluzione:

1.) Aggiungi un pennarello per tagliare, quindi carica il pezzo html

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

2.) fai quello che vuoi con il documento
3.) salva html

$new_html_piece = $dom->saveHTML();

4.) prima di restituirlo, rimuovi i tag <p> </ p> dal marker, stranamente appare solo su [MARK] ma non su [/ MARK] ...!?

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

5.) rimuovere tutto prima e dopo il pennarello

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

6.) restituirlo

return $new_html_piece;

Sarebbe molto più semplice se LIBXML_HTML_NOIMPLIED funzionasse per me. Schould, ma non lo è. PHP 5.4.17, libxml versione 2.7.8.
Trovo davvero strano, uso il parser HTML DOM e quindi, per aggiustare questa "cosa" devo usare regex ... Il punto era, non usare regex;)


Sembra pericoloso quello che fai qui, stackoverflow.com/a/29499718/367456 dovrebbe fare il lavoro per te.
hakre

Sfortunatamente questo ( stackoverflow.com/questions/4879946/… ) non funzionerà per me. Come ho detto: "Quindi aggiungere un elemento <div> non funziona per me, perché a volte ho 3-4 div non chiusi nel pezzo html" Per qualche motivo, il DOMDocument vuole chiudere tutti gli elementi "non chiusi". In alcuni casi, otterrò un fregment all'interno di uno shortcode o di un altro marker, rimuoverò il fregment e voglio manipolare l'altra parte del documento, quando avrò finito, inserirò nuovamente il fregment.
Joe

Dovrebbe essere possibile lasciare fuori l'elemento div e operare sull'elemento body dopo aver caricato il proprio contenuto. L'elemento body dovrebbe essere aggiunto in modo implicito quando carichi un frammento.
hakre

Il mio problema è che il mio fregment contiene un tag non chiuso. Dovrebbe rimanere aperto e DOMDocument chiuderà quegli elementi. Fregment come: < div >< div > ... < /div >. Sto ancora cercando soluzioni.
Joe

Hmm, penso che i tag div abbiano sempre una coppia di chiusura. Forse Tidy può gestirlo, può funzionare anche con i frammenti.
hakre

0

Per chiunque usi Drupal, c'è una funzione integrata per farlo:

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

Codice di riferimento:

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. Usare questa funzione dall'API Drupal funziona bene sul mio sito Drupal 7. Immagino che chi non utilizza Drupal possa semplicemente copiare la funzione nel proprio sito, poiché non c'è nulla di specifico di Drupal in questo.
Radicale libero

0

Puoi usare tidy con show-body-only:

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

Ma ricorda: rimuovi in ​​modo ordinato alcuni tag come le icone Font Awesome: Problemi di rientro dell'HTML (5) con 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);

Vuoi condividere il motivo per cui -1?
Dylan Maxey

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.