Bagaimana cara menyimpanHTML dari DOMDocument tanpa pembungkus HTML?

116

Saya fungsi di bawah ini, saya berjuang untuk mengeluarkan DOMDocument tanpa menambahkan pembungkus tag XML, HTML, body dan p sebelum output konten. Perbaikan yang disarankan:

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

Hanya berfungsi jika konten tidak memiliki elemen level blok di dalamnya. Namun, jika ya, seperti pada contoh di bawah ini dengan elemen h1, keluaran yang dihasilkan dari saveXML dipotong ke ...

<p> Jika Anda suka </p>

Saya telah diarahkan ke posting ini sebagai solusi yang mungkin, tetapi saya tidak dapat memahami cara menerapkannya ke dalam solusi ini (lihat upaya berkomentar di bawah).

Ada saran?

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

Jawaban:

217

Semua jawaban ini sekarang salah , karena mulai PHP 5.4 dan Libxml 2.6 loadHTMLsekarang memiliki $optionparameter yang menginstruksikan Libxml tentang bagaimana ia harus mengurai konten.

Oleh karena itu, jika kita memuat HTML dengan pilihan tersebut

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

ketika melakukan saveHTML()tidak akan ada doctype, tidak <html>, dan tidak <body>.

LIBXML_HTML_NOIMPLIEDmematikan penambahan otomatis dari elemen html / body tersirat LIBXML_HTML_NODEFDTDmencegah doctype default ditambahkan ketika tidak ditemukan.

Dokumentasi lengkap tentang parameter Libxml adalah di sini

(Perhatikan bahwa loadHTMLdokumen mengatakan bahwa Libxml 2.6 diperlukan, tetapi LIBXML_HTML_NODEFDTDhanya tersedia di Libxml 2.7.8 dan LIBXML_HTML_NOIMPLIEDtersedia di Libxml 2.7.7)

Alessandro Vendruscolo
sumber
10
Ini bekerja seperti pesona. Harus menjadi jawaban yang diterima. Saya baru saja menambahkan satu bendera dan semua sakit kepala saya hilang ;-)
Just Plain High
8
Ini tidak berfungsi dengan PHP 5.4 dan Libxml 2.9. loadHTML tidak menerima opsi apa pun :(
Acyra
11
Perhatikan bahwa ini tidak sepenuhnya sempurna. Lihat stackoverflow.com/questions/29493678/…
Josh Levinson
4
Maaf, tapi ini sepertinya bukan solusi yang baik sama sekali (setidaknya tidak dalam praktiknya). Itu seharusnya tidak menjadi jawaban yang diterima. Selain masalah yang disebutkan, ada juga masalah encoding jahat dengan DOMDocumentyang juga mempengaruhi kode dalam jawaban ini. Afaik, DOMDocumentselalu mengartikan data masukan sebagai latin-1 kecuali jika masukan menentukan rangkaian karakter yang berbeda . Dengan kata lain: <meta charset="…">Tag tampaknya diperlukan untuk memasukkan data yang bukan latin-1. Jika tidak, output akan rusak, misalnya karakter multibyte UTF-8.
mermshaus
1
LIBXML_HTML_NOIMPLIED juga mengacaukan kode HTML dengan menghapus tab, indentasi, dan jeda baris
Zoltán Süle
72

Hapus saja node secara langsung setelah memuat dokumen dengan loadHTML ():

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

# remove <html><body></body></html> 
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
Alex
sumber
ini adalah jawaban yang lebih bersih untuk saya.
KnF
39
harus dicatat bahwa ini berfungsi jika <body> hanya memiliki satu simpul anak.
Yann Milin
Bekerja dengan baik. Terima kasih! Jauh lebih bersih dan lebih cepat dari jawaban preg lainnya.
Ligemer
Terima kasih untuk ini! Saya baru saja menambahkan snip lain di bagian bawah untuk menangani node kosong.
redaxmedia
2
Kode untuk menghapus <!DOCTYPE karya. Baris kedua putus jika <body>memiliki lebih dari satu catatan anak.
Radikal Bebas
21

Gunakan saveXML()sebagai gantinya, dan teruskan documentElement sebagai argumen untuk itu.

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

http://php.net/domdocument.savexml

Yunus
sumber
Itu lebih baik, tapi saya masih <html><body> <p> membungkus isinya.
Scott B
2
Perlu dicatat bahwa saveXML () akan menyimpan XHTML, bukan HTML.
alexantd
@ Scott: itu sangat aneh. Ini menunjukkan apa yang Anda coba lakukan di sana di bagian contoh. Apakah Anda yakin tidak memiliki HTML itu di DOM Anda? Apa tepatnya HTML yang ada di DOMDocument Anda? Bisa jadi kita perlu mengakses simpul anak.
Yunus
@ Jonah itu tidak aneh. Saat Anda melakukannya loadHTMLlibxml menggunakan modul pengurai HTML dan itu akan memasukkan kerangka HTML yang hilang. Akibatnya, $dom->documentElementakan menjadi elemen HTML root. Saya telah memperbaiki kode contoh Anda. Sekarang harus melakukan apa yang diminta Scott.
Gordon
19

Masalah dengan jawaban teratas adalah itu LIBXML_HTML_NOIMPLIEDtidak stabil .

Ia dapat menyusun ulang elemen (terutama, memindahkan tag penutup elemen atas ke bawah dokumen), menambahkan ptag acak , dan mungkin berbagai masalah lainnya [1] . Ini dapat menghapus tag htmldan bodyuntuk Anda, tetapi dengan mengorbankan perilaku yang tidak stabil. Dalam produksi, itu adalah bendera merah. Pendeknya:

Jangan gunakanLIBXML_HTML_NOIMPLIED . Sebagai gantinya, gunakansubstr .


Pikirkan tentang itu. Panjang <html><body>dan </body></html>ditetapkan dan di kedua ujung dokumen - ukurannya tidak pernah berubah, begitu pula posisinya. Ini memungkinkan kita menggunakan substruntuk memotongnya:

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

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

( INI BUKAN SOLUSI TERAKHIR NAMUN! Lihat di bawah untuk jawaban lengkapnya , terus membaca untuk konteksnya)

Kami memotong 12dari awal dokumen karena <html><body>= 12 karakter ( <<>>+html+body= 4 + 4 + 4), dan kami mundur dan memotong 15 dari bagian akhir karena \n</body></html>= 15 karakter (\n+//+<<>>+body+html = 1 + 2 + 4 + 4 + 4)

Perhatikan bahwa saya masih menggunakan LIBXML_HTML_NODEFDTDomit the !DOCTYPEfrom sedang disertakan. Pertama, ini menyederhanakan substrpenghapusan tag HTML / BODY. Kedua, kami tidak menghapus doctype dengan substrkarena kami tidak tahu apakah ' default doctype' akan selalu menjadi sesuatu dengan panjang tetap. Tapi, yang terpenting,LIBXML_HTML_NODEFDTD menghentikan pengurai DOM dari menerapkan jenis dokumen non-HTML5 ke dokumen - yang setidaknya mencegah pengurai memperlakukan elemen yang tidak dikenali sebagai teks lepas.

Kami tahu pasti bahwa tag HTML / BODY memiliki panjang dan posisi yang tetap, dan kami tahu bahwa konstanta seperti LIBXML_HTML_NODEFDTDitu tidak pernah dihapus tanpa pemberitahuan penghentian, jadi metode di atas akan berjalan dengan baik di masa mendatang, TAPI ...


... satu-satunya peringatan adalah bahwa penerapan DOM dapat mengubah cara penempatan tag HTML / BODY di dalam dokumen - misalnya, menghapus baris baru di akhir dokumen, menambahkan spasi di antara tag, atau menambahkan baris baru.

Ini dapat diperbaiki dengan mencari posisi dari tag pembuka dan penutup body, dan menggunakan offset tersebut untuk memangkas panjang kami. Kami menggunakan strposdan strrposuntuk menemukan offset dari depan dan belakang, masing-masing:

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

Sebagai penutup, pengulangan dari jawaban final, bukti masa depan :

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

Tanpa doctype, tanpa tag html, tanpa tag body. Kami hanya berharap pengurai DOM akan segera menerima lapisan cat baru dan kami dapat langsung menghilangkan tag yang tidak diinginkan ini.

Kucing Super
sumber
Jawaban yang bagus, satu komentar kecil, mengapa tidak berulang $html = $dom -> saveHTML();- $dom -> saveHTML();ulang?
Steven
15

Trik yang rapi adalah dengan menggunakan loadXMLdan kemudian saveHTML. The htmldan bodytag dimasukkan di loadpanggung, bukan savepanggung.

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

NB bahwa ini agak hacky dan Anda harus menggunakan jawaban Jonah jika Anda bisa membuatnya berfungsi.

lonesomeday
sumber
4
Ini akan gagal untuk HTML yang tidak valid.
Gordon
1
@Gordon Persis mengapa saya meletakkan disclaimer di bagian bawah!
lonesomeday
1
Ketika saya mencoba ini, dan echo $ dom-> saveHTML (), itu hanya mengembalikan string kosong. Seolah loadXML ($ content) kosong. Ketika saya melakukan hal yang sama dengan $ dom-> loadHTML ($ content), lalu echo $ dom-> saveXML () saya mendapatkan konten seperti yang diharapkan.
Scott B
Menggunakan loadXML saat ingin memuat HTMl adalah jempol. Terutama karena LoadXML tidak tahu bagaimana menangani HTML.
botenvouwer
15

gunakan DOMDocumentFragment

$html = 'what you want';
$doc = new DomDocument();
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html);
$doc->appendChild($fragment);
echo $doc->saveHTML();
jcp
sumber
3
Jawaban terbersih untuk pra php5.4.
Nick Johnson
Ini berfungsi untuk saya, baik yang lebih lama maupun yang lebih baru dari versi Libxml 2.7.7. Mengapa ini hanya untuk pra php5.4?
RobbertT
Ini harus memiliki lebih banyak suara. Pilihan bagus untuk versi libxml yang tidak mendukung LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD. Terima kasih!
Marty Mulligan
13

Ini tahun 2017, dan untuk Pertanyaan 2011 ini, saya tidak suka jawabannya. Banyak regex, kelas besar, loadXML dll ...

Solusi mudah yang memecahkan masalah yang diketahui:

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

Mudah, Sederhana, Solid, Cepat. Kode ini akan berfungsi terkait tag HTML dan pengkodean seperti:

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

Jika ada yang menemukan kesalahan, tolong beritahu, saya akan menggunakan ini sendiri.

Edit , Opsi valid lainnya yang berfungsi tanpa kesalahan (sangat mirip dengan yang sudah diberikan):

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

Anda bisa menambahkan body sendiri untuk mencegah hal-hal aneh pada furure.

Opsi tiga:

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());
Vixxs
sumber
3
Anda harus memperbaiki jawaban Anda dengan menghindari yang lebih mahal mb_convert_encodingdan sebagai gantinya menambahkan <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>dan memodifikasi substrsesuai. Btw, milikmu adalah solusi paling elegan di sini. Suara positif.
Hlsg
10

Saya sedikit terlambat dalam klub, tapi tidak ingin tidak berbagi metode yang saya sudah tahu tentang. Pertama-tama saya punya versi yang tepat untuk loadHTML () untuk menerima opsi bagus ini, tetapi LIBXML_HTML_NOIMPLIEDtidak berfungsi pada sistem saya. Juga pengguna melaporkan masalah dengan parser (misalnya di sini dan di sini ).

Solusi yang saya buat sebenarnya cukup sederhana.

HTML yang akan dimuat diletakkan di file <div> elemen sehingga memiliki wadah yang berisi semua node yang akan dimuat.

Kemudian elemen kontainer ini dihapus dari dokumen (tapi DOMElementnya masih ada).

Kemudian semua turunan langsung dari dokumen tersebut akan dihapus. Ini termasuk setiap tambahan <html>, <head>dan <body>tag ( LIBXML_HTML_NOIMPLIEDopsi efektif ) serta <!DOCTYPE html ... loose.dtd">deklarasi (efektif LIBXML_HTML_NODEFDTD).

Kemudian semua turunan langsung dari penampung ditambahkan ke dokumen lagi dan dapat menjadi keluaran.

$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 bekerja seperti biasa, berhati-hatilah karena sekarang ada beberapa elemen dokumen, jadi bukan satu node root:

$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 ~ presisi + 2 (cli) (dibangun: 21 Des 2014 20:28:53)
hakre
sumber
itu tidak berhasil untuk saya dengan sumber HTML yang lebih kompleks. Itu juga menghapus bagian tertentu dari HTML.
Zoltán Süle
4

Tidak ada solusi lain pada saat penulisan ini (Juni, 2012) yang dapat sepenuhnya memenuhi kebutuhan saya, jadi saya menulis solusi yang menangani kasus-kasus berikut:

  • Menerima konten teks biasa yang tidak memiliki tag, serta konten HTML.
  • Tidak menambahkan tag apapun (termasuk <doctype>, <xml>, <html>, <body>, dan<p> tag)
  • Meninggalkan apa pun yang terbungkus <p> sendiri.
  • Meninggalkan teks kosong saja.

Jadi, inilah solusi yang memperbaiki masalah tersebut:

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

Saya juga menulis beberapa tes yang akan dilakukan di kelas yang sama:

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
}

Anda dapat memeriksa apakah itu berfungsi untuk Anda sendiri. DomDocumentWorkaround::testAll()mengembalikan ini:

    Succeeded
    Succeeded
    Succeeded
    Succeeded
pembajak tanah
sumber
1
HTML = / = XML, Anda harus menggunakan pemuat HTML untuk HTML.
hakre
4

Oke saya menemukan solusi yang lebih elegan, tetapi hanya membosankan:

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

Baiklah, semoga ini tidak menghilangkan apa-apa dan membantu seseorang?

rclai.dll
sumber
2
Tidak menangani kasus saat memuatHTML memuat string tanpa markup
copndz
3

Gunakan fungsi ini

$layout = preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $layout);
boksiora
sumber
13
Mungkin ada beberapa pembaca yang menemukan posting ini melalui posting ini , telah memutuskan untuk tidak menggunakan regex untuk mengurai HTML mereka dan menggunakan pengurai DOM sebagai gantinya, dan akhirnya berpotensi membutuhkan jawaban regex untuk mencapai solusi lengkap ... ironis
Robbie Averill
Saya tidak mengerti mengapa noboy hanya mengembalikan konten BODY. Bukankah tag tersebut harus selalu ada saat pengurai menambahkan seluruh header dokumen / doctype? Regex di atas bahkan lebih pendek.
sergio
@boksiora "it does the job" - lalu mengapa kita menggunakan metode parser DOM sejak awal?
Terima kasih
@naomik saya belum mengatakan untuk tidak menggunakan parser DOM, tentu saja ada banyak cara berbeda untuk mencapai hasil yang sama, terserah Anda, pada saat saya menggunakan fungsi ini saya punya masalah dengan dom php built-in parser, yang tidak mengurai html5 dengan benar.
boksiora
1
Saya harus menggunakan preg_replacekarena menggunakan metode berbasis DOMDocument untuk menghapus tag html dan tubuh tidak mempertahankan pengkodean UTF-8 :(
wizonesolutions
3

Jika solusi bendera yang dijawab oleh Alessandro Vendruscolo tidak berfungsi, Anda dapat mencoba ini:

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

$bodyTagakan berisi kode HTML Anda yang diproses secara lengkap tanpa semua pembungkus HTML tersebut, kecuali untuk <body>tag, yang merupakan root dari konten Anda. Kemudian Anda dapat menggunakan regex atau fungsi trim untuk menghapusnya dari string terakhir (setelah saveHTML) atau, seperti dalam kasus di atas, mengulang semua anak-anaknya, menyimpan konten mereka ke dalam variabel sementara $finalHtmldan mengembalikannya (apa yang saya yakini sebagai lebih aman).

José Ricardo Júnior
sumber
3

Saya berjuang dengan ini di RHEL7 yang menjalankan PHP 5.6.25 dan LibXML 2.9. (Barang lama di tahun 2018, saya tahu, tapi itu Red Hat untuk Anda.)

Saya telah menemukan bahwa banyak solusi yang diberi suara positif yang disarankan oleh Alessandro Vendruscolo merusak HTML dengan mengatur ulang tag. Yaitu:

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

menjadi:

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

Ini berlaku untuk kedua opsi yang dia sarankan untuk Anda gunakan: LIBXML_HTML_NOIMPLIEDdan LIBXML_HTML_NODEFDTD.

Solusi yang disarankan oleh Alex berjalan setengah jalan untuk menyelesaikannya, tetapi tidak berhasil jika<body> memiliki lebih dari satu simpul anak.

Solusi yang berhasil untuk saya adalah berikut ini:

Pertama, untuk memuat DOMDocument, saya menggunakan:

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

Untuk menyimpan dokumen setelah memijat DOMDocument, saya menggunakan:

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

Saya orang pertama yang setuju bahwa ini bukanlah solusi yang sangat elegan - tetapi berhasil.

Radikal bebas
sumber
2

Menambahkan <meta>tag akan memicu perilaku perbaikan dariDOMDocument . Bagian baiknya adalah Anda tidak perlu menambahkan tag itu sama sekali. Jika Anda tidak ingin menggunakan pengkodean pilihan Anda, teruskan saja sebagai argumen konstruktor.

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

Keluaran

<div>Hello World</div>

Terima kasih kepada @Bart

botenvouwer
sumber
2

Saya juga memiliki persyaratan ini, dan menyukai solusi yang diposting oleh Alex di atas. Namun, ada beberapa masalah - jika <body>elemen tersebut berisi lebih dari satu elemen turunan, dokumen yang dihasilkan hanya akan berisi elemen turunan pertama saja <body>, tidak semuanya. Juga, saya membutuhkan stripping untuk menangani hal-hal secara kondisional - hanya ketika Anda memiliki dokumen dengan judul HTML. Jadi saya menyempurnakannya sebagai berikut. Alih-alih menghapus <body>, saya mengubahnya menjadi a <div>, dan menghapus deklarasi XML dan <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);
    }
}
blackcatweb
sumber
2

Sama seperti anggota lain, saya pertama kali menikmati kesederhanaan dan kekuatan luar biasa dari jawaban @Alessandro Vendruscolo. Kemampuan untuk hanya meneruskan beberapa konstanta yang ditandai ke konstruktor tampaknya terlalu bagus untuk menjadi kenyataan. Bagi saya itu. Saya memiliki versi yang benar dari LibXML serta PHP namun tidak peduli apa itu tetap akan menambahkan tag HTML ke struktur simpul dari objek Dokumen.

Solusi saya bekerja jauh lebih baik daripada menggunakan ...

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

Bendera atau ....

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

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

Penghapusan Node, yang menjadi berantakan tanpa urutan terstruktur di DOM. Sekali lagi, fragmen kode tidak memiliki cara untuk menentukan struktur DOM.

Saya memulai perjalanan ini dengan menginginkan cara sederhana untuk melakukan traversal DOM bagaimana JQuery melakukannya atau setidaknya dengan cara tertentu yang memiliki kumpulan data terstruktur baik yang terhubung secara tunggal, terhubung ganda atau traversal simpul pohon. Saya tidak peduli berapa lama saya bisa mengurai string seperti yang dilakukan HTML dan juga memiliki kekuatan luar biasa dari properti kelas entitas node untuk digunakan di sepanjang jalan.

Sejauh ini DOMDocument Object telah membuat saya menginginkan ... Seperti banyak programmer lain, sepertinya ... Saya tahu saya telah melihat banyak frustrasi dalam pertanyaan ini jadi sejak AKHIRNYA .... (setelah kira-kira 30 jam mencoba dan gagal jenis pengujian) Saya telah menemukan cara untuk mendapatkan semuanya. Saya harap ini membantu seseorang ...

Pertama, saya sinis terhadap SEMUANYA ... lol ...

Saya akan pergi seumur hidup sebelum setuju dengan siapa pun bahwa kelas pihak ketiga tetap diperlukan dalam kasus penggunaan ini. Saya sangat banyak dan saya BUKAN penggemar menggunakan struktur kelas pihak ketiga namun saya tersandung ke parser yang hebat. (sekitar 30 kali di Google sebelum saya menyerah jadi jangan merasa sendirian jika Anda menghindarinya karena terlihat payah tidak resmi dengan cara apa pun ...)

Jika Anda menggunakan fragmen kode dan membutuhkan, kode bersih dan tidak terpengaruh oleh parser dengan cara apa pun, tanpa menggunakan tag tambahan, gunakan simplePHPParser .

Luar biasa dan bertindak sangat mirip dengan JQuery. Saya tidak sering terkesan tetapi kelas ini menggunakan banyak alat yang bagus dan saya belum memiliki kesalahan penguraian sampai saat ini. Saya sangat menyukai kemampuan untuk melakukan apa yang dilakukan kelas ini.

Anda dapat menemukan file-nya untuk diunduh di sini , petunjuk permulaannya di sini , dan API-nya di sini . Saya sangat merekomendasikan menggunakan kelas ini dengan metode sederhana yang dapat melakukan cara .find(".className")yang sama seperti metode pencarian JQuery akan digunakan atau bahkan metode yang sudah dikenal seperti getElementByTagName()atau getElementById()...

Ketika Anda menyimpan pohon simpul di kelas ini, itu tidak menambahkan apa-apa. Anda cukup mengatakan $doc->save();dan mengeluarkan seluruh pohon ke string tanpa keributan.

Saya sekarang akan menggunakan parser ini untuk semua, non-capped-bandwidth, project di masa mendatang.

GoreDefex
sumber
2

Saya memiliki PHP 5.3 dan jawaban di sini tidak berhasil untuk saya.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);mengganti semua dokumen dengan hanya anak pertama, saya memiliki banyak paragraf dan hanya yang pertama yang disimpan, tetapi solusi memberi saya titik awal yang baik untuk menulis sesuatu tanpa regexsaya meninggalkan beberapa komentar dan saya cukup yakin ini dapat ditingkatkan tetapi jika seseorang memiliki masalah yang sama dengan saya, ini bisa menjadi titik awal yang baik.

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

Kemudian kita bisa menggunakannya seperti ini:

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

Perhatikan bahwa appendChildmenerima a DOMNodejadi kita tidak perlu membuat elemen baru, kita cukup menggunakan kembali yang sudah ada yang menerapkan DOMNodeseperti DOMElementini penting untuk menjaga kode "waras" saat memanipulasi beberapa dokumen HTML / XML

Bata Abadi
sumber
Ini tidak akan berfungsi untuk fragmen, hanya untuk satu elemen anak yang ingin Anda jadikan anak pertama dokumen. Ini sangat terbatas dan secara efektif tidak melakukan pekerjaan LIBXML_HTML_NOIMPLIEDseperti yang dilakukannya hanya sebagian. Menghapus doctype secara efektif LIBXML_HTML_NODEFDTD.
hakre
2

Saya menemukan topik ini untuk menemukan cara menghapus pembungkus HTML. Menggunakan LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTDberfungsi dengan baik, tetapi saya memiliki masalah dengan utf-8. Setelah banyak usaha saya menemukan solusi. Saya mempostingnya di bawah untuk siapa pun yang memiliki masalah yang sama.

Masalahnya disebabkan karena <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Masalah:

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

Solusi 1:

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

Solusi 2:

$dom->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
utf8_decode($dom->saveHTML($dom->documentElement));
Panagiotis Koursaris
sumber
1
Saya merasa senang Anda membagikan temuan Anda, tetapi Solusi 2 sudah ada dengan pertanyaan tepat ini di sini dan Solusi 1 ada di tempat lain. Juga untuk Soal Solusi 1 jawaban yang diberikan tidak jelas. Saya menghormati niat baik Anda, ntetapi harap diperhatikan bahwa hal itu dapat menimbulkan banyak kebisingan serta menghalangi orang lain untuk menemukan solusi yang mereka cari yang menurut saya agak berlawanan dengan apa yang ingin Anda capai dengan jawaban Anda. Stackoverflow berfungsi paling baik jika Anda menangani satu pertanyaan dalam satu waktu. Hanya petunjuk.
hakre
2

Saya menghadapi 3 masalah dengan DOMDocumentkelas.

1- Kelas ini memuat html dengan encoding ISO dan karakter utf-8 tidak muncul dalam keluaran.

2- Bahkan jika kita memberikan LIBXML_HTML_NOIMPLIEDbendera untuk metode loadHtml, sampai html masukan kami tidak mengandung tag root, itu tidak akan mengurai dengan benar.

3- Kelas ini menganggap tag HTML5 tidak valid.

Jadi saya telah mengganti kelas ini untuk menyelesaikan masalah ini dan saya mengubah beberapa metode.

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

}

Sekarang saya menggunakan DOMEditoralih-alih DOMDocumentdan sejauh ini berhasil dengan baik untuk saya

        $editor = new DOMEditor();
        $editor->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // works like a charm!
        echo $editor->saveHTML();
Tuan Hosseini
sumber
Poin Anda 1. diselesaikan dengan menggunakan mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8'); sebelum menggunakan loadHTML () dan 2.nd dengan memiliki tag DIV di sekitar fungsi helper Anda, sekitar mb_convert_encoding () yang Anda gunakan misalnya. Bekerja untuk saya cukup baik. Memang jika tidak ada DIV, maka secara otomatis menambahkan paragraf dalam kasus saya yang tidak nyaman karena biasanya mereka menerapkan beberapa margin (bootstrap ..)
trainoasis
0

Saya menemukan masalah ini juga.

Sayangnya, saya tidak merasa nyaman menggunakan solusi apa pun yang disediakan di utas ini, jadi saya pergi untuk memeriksa salah satu yang akan memuaskan saya.

Inilah yang saya buat dan berfungsi tanpa masalah:

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

Intinya, ini bekerja dengan cara yang mirip dengan sebagian besar solusi yang disediakan di sini, tetapi alih-alih melakukan kerja manual, ia menggunakan pemilih xpath untuk memilih semua elemen di dalam tubuh dan menggabungkan kode html mereka.

Nikola Petkanski
sumber
Seperti semua solusi di sini, ini tidak berfungsi untuk setiap kasus: jika string yang dimuat tidak dimulai dengan markup, <p> </p> telah ditambahkan, maka kode Anda tidak berfungsi, karena akan menambahkan <p> </p> markup dalam konten yang disimpan
copndz
Agar adil, saya belum mengujinya dengan teks mentah, tetapi secara teori seharusnya berhasil. Untuk kasus khusus Anda, Anda mungkin perlu mengubah xpath menjadi seperti descendant-or-self::body/p/*.
Nikola Petkanski
0

server saya mendapat php 5.3 dan tidak dapat memutakhirkan jadi opsi itu

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

bukan untuk saya.

Untuk mengatasi hal ini saya memberi tahu Fungsi SaveXML untuk mencetak elemen Body dan kemudian hanya mengganti "body" dengan "div"

ini kode saya, semoga bisa membantu seseorang:

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

utf-8 adalah untuk dukungan Ibrani.

Tomer Ofer
sumber
0

Jawaban Alex benar, tetapi mungkin menyebabkan kesalahan berikut pada node kosong:

Argumen 1 yang diteruskan ke DOMNode :: removeChild () harus merupakan turunan dari DOMNode

Ini dia mod kecil saya:

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

Menambahkan trim () juga merupakan ide bagus untuk menghapus spasi.

redaxmedia
sumber
0

Saya mungkin terlambat. Tetapi mungkin seseorang (seperti saya) masih memiliki masalah ini.
Jadi, semua hal di atas tidak berhasil untuk saya. Karena $ dom-> loadHTML juga menutup tag terbuka, tidak hanya menambahkan tag html dan body.
Jadi menambahkan elemen <div> tidak berfungsi untuk saya, karena terkadang saya menyukai 3-4 div yang tidak ditutup di bagian html.
Solusi saya:

1.) Tambahkan spidol untuk dipotong, lalu muat potongan html

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

2.) lakukan apapun yang Anda inginkan dengan dokumen
3.) simpan html

$new_html_piece = $dom->saveHTML();

4.) sebelum dikembalikan, hapus tag <p> </ p> dari marker, anehnya hanya muncul di [MARK] tapi tidak di [/ MARK] ...!?

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

5.) Hapus semua sebelum dan sesudah penanda

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

6.) mengembalikannya

return $new_html_piece;

Akan jauh lebih mudah jika LIBXML_HTML_NOIMPLIED bekerja untuk saya. Itu skema, tapi sebenarnya tidak. PHP 5.4.17, libxml Versi 2.7.8.
Saya merasa sangat aneh, saya menggunakan parser DOM HTML dan kemudian, untuk memperbaiki "hal" ini saya harus menggunakan regex ... Intinya adalah, bukan menggunakan regex;)

Joe
sumber
Tampak berbahaya apa yang Anda lakukan di sini, stackoverflow.com/a/29499718/367456 harus melakukan pekerjaan itu untuk Anda.
hakre
Sayangnya ini ( stackoverflow.com/questions/4879946/… ) tidak akan berhasil untuk saya. Seperti yang saya katakan: "Jadi menambahkan elemen <div> tidak berfungsi untuk saya, karena saya terkadang menyukai 3-4 div yang tidak ditutup di bagian html" Untuk beberapa alasan, DOMDocument ingin menutup semua elemen "tidak tertutup". Dalam kasus mungkin, saya akan mendapatkan fregement dalam shortcode atau marker lain, menghapus fregement dan saya ingin memanipulasi bagian lain dari dokumen tersebut, ketika saya selesai dengan itu, saya akan memasukkan fregement kembali.
Joe
Seharusnya memungkinkan untuk membiarkan elemen div keluar dan beroperasi pada elemen body setelah memuat konten Anda sendiri. Elemen body harus ditambahkan secara implisit saat Anda memuat sebuah fragmen.
hakre
Masalah saya adalah, fregement saya mengandung tag yang tidak ditutup. Ini harus tetap tidak tertutup dan DOMDocument akan menutup elemen tersebut. Fregment seperti: < div >< div > ... < /div >. Saya masih mencari solusi.
Joe
Hmm, menurut saya tag div selalu memiliki pasangan penutup. Mungkin Tidy bisa mengatasinya, juga bisa bekerja dengan fragmen.
hakre
0

Bagi siapa pun yang menggunakan Drupal, ada fungsi bawaan untuk melakukan ini:

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

Kode untuk referensi:

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;
  }
}
leon.nk
sumber
Suara positif. Gunakan fungsi ini dari Drupal API berfungsi dengan baik di situs Drupal 7 saya. Saya rasa mereka yang tidak menggunakan Drupal dapat menyalin fungsi tersebut ke situs mereka sendiri - karena tidak ada yang spesifik untuk Drupal tentang ini.
Radikal Bebas
0

Anda dapat menggunakan tidy dengan show-body-only:

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

Tapi, ingat: bersihkan beberapa tag seperti ikon Font Awesome: Masalah Mengindentasi HTML (5) dengan PHP

Rafa Rodríguez
sumber
-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);
Dylan Maxey
sumber
Peduli untuk berbagi mengapa -1?
Dylan Maxey