DOMDocument'ın HTML'sini HTML sarmalayıcı olmadan nasıl kaydedebilirim?


116

Aşağıdaki fonksiyon benim, DOMDocument'i içeriğin çıktısından önce XML, HTML, gövde ve p etiket sarmalayıcılarını eklemeden çıktı almakta zorlanıyorum . Önerilen düzeltme:

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

Yalnızca içeriğin içinde blok seviyesi öğeleri bulunmadığında çalışır. Ancak, aşağıdaki örnekte olduğu gibi h1 öğesi ile yaptığı gibi, saveXML'den elde edilen çıktı ... şeklinde kesilir.

<p> İsterseniz </p>

Bu gönderiye olası bir geçici çözüm olarak işaret edildim, ancak bunu bu çözüme nasıl uygulayacağımı anlayamıyorum (aşağıda açıklanan girişimlere bakın).

Baska öneri?

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

Yanıtlar:


217

Tüm bu yanıtlar artık yanlış , çünkü PHP 5.4 ve Libxml 2.6'da loadHTMLartık $optionLibxml'e içeriği nasıl ayrıştırması gerektiği konusunda talimat veren bir parametre var.

Bu nedenle, HTML'yi bu seçeneklerle yüklersek

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

bunu yaparken saveHTML()hayır doctype, hayır <html>ve hayır olacak <body>.

LIBXML_HTML_NOIMPLIEDzımni html / body öğelerinin otomatik olarak eklenmesini kapatır, LIBXML_HTML_NODEFDTDbulunamadığında varsayılan bir belge türünün eklenmesini engeller.

Libxml parametreleri hakkında tam dokümantasyon burada

( loadHTMLDokümanların Libxml 2.6'nın gerekli olduğunu söylediğini, ancak LIBXML_HTML_NODEFDTDyalnızca Libxml 2.7.8'de ve LIBXML_HTML_NOIMPLIEDLibxml 2.7.7'de mevcut olduğunu unutmayın)


10
Bu bir cazibe gibi çalışıyor. Kabul edilen cevap olmalı. Az önce bir bayrak ekledim ve tüm baş ağrılarım geçti ;-)
Just Plain High

8
Bu, PHP 5.4 ve Libxml 2.9 ile çalışmaz. loadHTML hiçbir seçeneği kabul etmiyor :(
Acyra

11
Bunun tam olarak mükemmel olmadığını unutmayın. Stackoverflow.com/questions/29493678/… sayfasına
Josh Levinson

4
Üzgünüz, ancak bu hiç de iyi bir çözüm gibi görünmüyor (en azından pratikte değil). Gerçekten kabul edilen cevap olmamalı. Bahsedilen sorunların yanı sıra, bu yanıttaki kodu da etkileyen kötü bir kodlama sorunu var DOMDocument. Afaik, girdi farklı bir karakter kümesi belirtmedikçeDOMDocument girdi verilerini her zaman latin-1 olarak yorumlar . Başka bir deyişle: <meta charset="…">Latin-1 olmayan giriş verileri için etiket gerekli görünüyor. Aksi takdirde çıktı, örneğin UTF-8 çok baytlı karakterler için kesilecektir.
mermshaus

1
LIBXML_HTML_NOIMPLIED ayrıca sekmeleri, girintileri ve satır sonlarını kaldırarak HTML kodunu karıştırıyor
Zoltán Süle

72

LoadHTML () ile belgeyi yükledikten sonra doğrudan düğümleri kaldırın:

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

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

bu bana daha açık cevap.
KnF

39
<body> yalnızca bir çocuk düğüme sahipse bunun işe yarayacağına dikkat edilmelidir.
Yann Milin

Harika çalıştı. Teşekkür ederim! Diğer preg cevabından çok daha temiz ve hızlı.
Ligemer

Bunun için teşekkür ederim! Boş düğümleri işlemek için alt kısma başka bir ekran görüntüsü ekledim.
redaxmedia

2
Kaldırılacak kod <!DOCTYPE çalışır. <body>Birden fazla alt not varsa ikinci satır kesilir .
Free Radical

21

saveXML()Bunun yerine kullanın ve documentElement öğesini argüman olarak iletin.

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

http://php.net/domdocument.savexml


Bu daha iyi, ama yine de <html><body> <p> içeriğini paketlemeye alıyorum.
Scott B


2
SaveXML () 'nin HTML'yi değil XHTML'yi kaydedeceği unutulmamalıdır.
alexantd

@Scott: Bu gerçekten tuhaf. Örnekler bölümünde ne yapmaya çalıştığınızı gösterir. DOM'nuzda bu HTML’nin olmadığından emin misiniz? DOMDocument'inizde tam olarak hangi HTML var? Bir alt düğüme erişmemiz gerekebilir.
Jonah

@Jonah garip değil. Bunu yaptığınızda loadHTMLlibxml, HTML ayrıştırıcı modülünü kullanır ve bu, eksik HTML iskeletini ekler. Sonuç $dom->documentElementolarak, kök HTML öğesi olacaktır. Örnek kodunuzu düzelttim. Şimdi Scott'ın istediğini yapmalı.
Gordon

19

En iyi cevapla ilgili sorun, bunun LIBXML_HTML_NOIMPLIEDistikrarsız olmasıdır .

Öğeleri yeniden sıralayabilir (özellikle, üst öğenin kapanış etiketini belgenin altına taşıyabilir), rastgele petiketler ekleyebilir ve belki de çeşitli başka sorunlar [1] . Sizin için htmlve bodyetiketlerini kaldırabilir , ancak bu kararsız davranışlar pahasına olabilir. Üretimde bu bir kırmızı bayraktır. Kısacası:

KullanmayınLIBXML_HTML_NOIMPLIED . Bunun yerine kullanınsubstr .


Bunu düşün. Uzunlukları <html><body>ve </body></html>sabittir ve belgenin her iki uçta - kendi boyutları hiç değişmez ve ne konumlarını yapmak. Bu, substronları kesip atmamızı sağlar:

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

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

( ANCAK BU SON ÇÖZÜM DEĞİLDİR! Tam yanıt için aşağıya bakın , bağlam için okumaya devam edin)

12Belgenin başlangıcını kesiyoruz çünkü <html><body>= 12 karakter ( <<>>+html+body= 4 + 4 + 4) ve geriye gidip sondan 15'i kesiyoruz çünkü \n</body></html>= 15 karakter ( \n+//+<<>>+body+html= 1 + 2 + 4 + 4 + 4)

Hala kullandıkları Bildirimi LIBXML_HTML_NODEFDTDomit !DOCTYPEdahil edilmesini. İlk olarak bu substr, HTML / BODY etiketlerinin kaldırılmasını basitleştirir . İkincisi, substr' default doctype' ifadesinin her zaman sabit uzunlukta bir şey olup olmayacağını bilmediğimiz için doctype'ı kaldırmayız . Ancak en önemlisi, LIBXML_HTML_NODEFDTDDOM ayrıştırıcısının belgeye HTML5 olmayan bir belge türü uygulamasını durdurur - bu da en azından ayrıştırıcının tanımadığı öğeleri gevşek metin olarak işlemesini engeller.

HTML / BODY etiketlerinin sabit uzunluklarda ve konumlarda olduğunu biliyoruz ve benzer sabitlerin bir LIBXML_HTML_NODEFDTDtür kullanımdan kaldırma bildirimi olmadan asla kaldırılmadığını biliyoruz , bu nedenle yukarıdaki yöntem geleceğe iyi bir şekilde dönmelidir, AMA ...


... sadece ikaz DOM uygulama olmasıdır olabilir örneğin belgenin sonundaki yeni satır kaldırarak, etiketler arasındaki boşluk ekleyerek veya yeni satır ekleme - belge içinde yerleştirilir HTML / BODY etiketleri şeklini değiştirmek.

Bu, açma ve kapama etiketlerinin konumlarını arayarak bodyve bu ofsetleri, kısaltılacak uzunluklarımız için olduğu gibi kullanarak düzeltilebilir. Sırasıyla önden ve arkadan ofsetleri bulmak için strposve kullanırız strrpos:

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

Kapanışta, nihai, geleceğe yönelik cevabın tekrarı :

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

Belge türü yok, html etiketi yok, gövde etiketi yok. Sadece DOM ayrıştırıcısının yakında yeni bir kat boya alacağını umabiliriz ve bu istenmeyen etiketleri daha doğrudan ortadan kaldırabiliriz.


Harika yanıt, küçük bir yorum, neden tekrar tekrar $html = $dom -> saveHTML();yerine olmasın $dom -> saveHTML();?
Steven

15

Temiz bir numara kullanmaktır loadXMLve sonra saveHTML. htmlVe bodyetiketler sokulan loadaşamasında değil, saveaşama.

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

Not: Bu biraz karmaşık ve işe yarayabilirse Jonah'ın cevabını kullanmalısınız.


4
Ancak bu geçersiz HTML için başarısız olacaktır.
Gordon

1
@Gordon Tam olarak neden feragatnameyi en alta koydum!
lonesomeday

1
Bunu denediğimde ve echo $ dom-> saveHTML (), sadece boş bir dizge döndürüyor. LoadXML ($ içerik) boşmuş gibi. Aynı şeyi $ dom-> loadHTML ($ içerik) ile yaptığımda, ardından echo $ dom-> saveXML () içeriği beklendiği gibi alıyorum.
Scott B

HTMl'yi yüklemek istendiğinde loadXML kullanmak çok önemlidir. Özellikle de LoadXML, HTML'yi nasıl kullanacağını bilmediği için.
botenvouwer

15

DOMDocumentFragment kullanın

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

3
Php5.4 için en temiz cevap.
Nick Johnson

Bu, Libxml 2.7.7 sürümünden hem eski hem de daha yeni benim için çalışıyor. Bu neden sadece php5.4 için olsun?
RobbertT

Bunun daha fazla oy alması gerekir. LIBXML_HTML_NOIMPLIED'i desteklemeyen libxml sürümleri için mükemmel seçenek | LIBXML_HTML_NODEFDTD. Teşekkürler!
Marty Mulligan

13

Yıl 2017 ve bu 2011 Sorusu için cevapların hiçbirini beğenmedim. Çok sayıda regex, büyük sınıflar, loadXML vb.

Bilinen sorunları çözen kolay çözüm:

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

Kolay, Basit, Sağlam, Hızlı. Bu kod, aşağıdaki gibi HTML etiketleri ve kodlamayla ilgili olarak çalışacaktır:

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

Herhangi biri bir hata bulursa, lütfen söyleyin, bunu kendim kullanacağım.

Düzenle , Hatasız çalışan diğer geçerli seçenekler (daha önce verilenlere çok benzer):

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

Kürkte herhangi bir garip şeyi önlemek için kendiniz vücut ekleyebilirsiniz.

Otuz seçenek:

 $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
Cevabınızı daha pahalı olanlardan kaçınarak mb_convert_encodingve bunun yerine buna göre ekleyip <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>değiştirerek geliştirmelisiniz substr. Btw, buradaki en şık çözüm sizinki. Upvoted.
Hlsg

10

Ben biraz geç kulübe üyeyim ama istemiyordu değil ben yaklaşık öğrendim bir yöntem paylaşır. Öncelikle bu güzel seçenekleri kabul etmek için loadHTML () için doğru sürümlere sahibim, ancak LIBXML_HTML_NOIMPLIEDsistemimde çalışmadı. Ayrıca kullanıcılar ayrıştırıcıyla ilgili sorunları bildirirler (örneğin burada ve burada ).

Oluşturduğum çözüm aslında oldukça basit.

Yüklenecek HTML, yüklenecek <div>tüm düğümleri içeren bir kaba sahip olacak şekilde bir öğeye yerleştirilir .

Daha sonra bu kap öğesi belgeden kaldırılır (ancak bunun DOMElement'ı hala mevcuttur).

Ardından, belgedeki tüm doğrudan alt öğeler kaldırılır. Bu, herhangi bir ilave içerir <html>, <head>ve <body>etiketleri (etkili bir LIBXML_HTML_NOIMPLIEDseçenek) yanı sıra <!DOCTYPE html ... loose.dtd">beyan (etkili LIBXML_HTML_NODEFDTD).

Ardından, kabın tüm doğrudan alt öğeleri belgeye yeniden eklenir ve çıktı alınabilir.

$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 her zamanki gibi çalışır, şimdi birden fazla belge öğesi olmasına dikkat edin, yani tek bir kök düğümü değil:

$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 ~ kesin + 2 (cli) (inşa: 21 Aralık 2014 20:28:53)

benim için daha karmaşık HTML kaynağıyla çalışmadı. Ayrıca HTML'nin belirli bir bölümünü de kaldırdı.
Zoltán Süle

4

Bu yazının yazıldığı sırada (Haziran 2012) diğer çözümlerin hiçbiri ihtiyaçlarımı tam olarak karşılayamadı, bu yüzden aşağıdaki durumları ele alan bir çözüm yazdım:

  • HTML içeriğinin yanı sıra etiketi olmayan düz metin içeriğini de kabul eder.
  • Hiçbir etiket eklemek yapmaz (dahil <doctype>, <xml>, <html>, <body>, ve <p>etiketleri)
  • Her şeyi <p>tek başına sarar.
  • Boş metni tek başına bırakır.

İşte bu sorunları gideren bir çözüm:

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

Aynı sınıfta yaşayacak bazı testler de yazdım:

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
}

Kendiniz için çalışıp çalışmadığını kontrol edebilirsiniz. DomDocumentWorkaround::testAll()şunu döndürür:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

1
HTML = / = XML, HTML için HTML yükleyiciyi kullanmalısınız.
hakre

4

Tamam, daha zarif bir çözüm buldum ama bu çok sıkıcı:

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

Pekala, umarım bu hiçbir şeyi atlamaz ve birine yardımcı olmaz?


2
LoadHTML, işaretleme olmadan bir dizeyi
yüklediğinde

3

Bu işlevi kullanın

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

13
Aracılığıyla bu yazı rastladım bazı okuyucular olabilir bu yazı onların HTML ayrıştırmak ve bunun yerine bir DOM ayrıştırıcı kullanın ve potansiyel olarak eksiksiz bir çözüm ... ironik ulaşmak için bir normal ifade cevabını ihtiyacı sonuna kadar kullanılması regex karar verdik
Robbie Averill

Noboy'un neden sadece BODY içeriğini döndürdüğünü anlamıyorum. Ayrıştırıcı tüm belge başlığını / belge türünü eklediğinde bu etiketin her zaman mevcut olduğu varsayılmıyor mu? Yukarıdaki normal ifade daha da kısa olacaktır.
sergio

@boksiora "işi yapar" - öyleyse neden ilk olarak DOM ayrıştırıcı yöntemlerini kullanıyoruz?
Teşekkür ederim

@naomik DOM ayrıştırıcı kullanmamamı söylemedim, elbette aynı sonucu elde etmenin birçok farklı yolu var, bu size kalmış, bu işlevi kullandığım sırada yerleşik php dom ile ilgili bir sorun yaşadım html5'i doğru ayrıştırmayan ayrıştırıcı.
boksiora

1
preg_replaceHtml ve body etiketlerini kaldırmak için DOMDocument tabanlı yöntemler kullanmak UTF-8 kodlamasını
korumadığı

3

Bayraklar çözümü tarafından yanıtlanırsa Alessandro Vendruscolo işe yaramazsa, şunu deneyebilirsiniz:

$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<body>içeriğinizin kökü olan etiketi dışında tüm bu HTML sarmalamaları olmadan işlenmiş tam HTML kodunuzu içerecektir . Ardından, onu son dizeden (sonra saveHTML) kaldırmak için bir normal ifade veya bir kırpma işlevi kullanabilir veya yukarıdaki durumda olduğu gibi, tüm çocukları üzerinde yineleyerek içeriğini geçici bir değişkene kaydedebilir $finalHtmlve geri döndürebilirsiniz ( daha güvenli).


3

HTML sarmalayıcısını kaldırmanın bir yolunu bulmak için bu konuyla karşılaştım. Kullanmak LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTDharika çalışıyor, ancak utf-8 ile ilgili bir sorunum var. Çok uğraştıktan sonra bir çözüm buldum. Aynı sorunu olan herkes için feryat ediyorum.

Neden olduğu sorun <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Sorun:

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

1.Çözüm:

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

2.Çözüm:

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

1
Bulgularınızı paylaşmanızı güzel buluyorum, ancak Çözüm 2 zaten tam olarak bu sorularla burada ve Çözüm 1 başka bir yerde. Ayrıca Çözüm 1 Problemi için verilen cevap belirsizdir. İyi niyetinizi onurlandırıyorum, ancak lütfen bunun çok fazla gürültü yaratabileceğini ve başkalarının aradıkları çözümleri bulmalarını engelleyebileceğini unutmayın ki bu sanırım cevabınızla ulaşmak istediğiniz şeyin tam tersi. Stackoverflow, her seferinde bir soruyla ilgilenirseniz en iyi sonucu verir. Sadece bir ipucu.
hakre

3

PHP 5.6.25 ve LibXML 2.9 çalıştıran RHEL7'de bununla mücadele ediyorum. (2018'deki eski şeyler biliyorum, ama bu senin için Red Hat.)

Alessandro Vendruscolo tarafından önerilen çok beğenilen çözümün etiketleri yeniden düzenleyerek HTML'yi kırdığını buldum . yani:

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

dönüşür:

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

Bu, kullanmanızı önerdiği her iki seçenek için de geçerlidir: LIBXML_HTML_NOIMPLIEDve LIBXML_HTML_NODEFDTD.

Alex'in önerdiği çözüm , çözmenin yarısına gider, ancak <body>birden fazla çocuk düğümü varsa işe yaramaz .

Benim için işe yarayan çözüm şu:

İlk olarak, DOMDocument'i yüklemek için şunu kullanıyorum:

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

DOMDocument'e masaj yaptıktan sonra belgeyi kaydetmek için şunu kullanıyorum:

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

Bunun çok zarif bir çözüm olmadığı konusunda ilk hemfikirim - ama işe yarıyor.


2

<meta>Etiketin eklenmesi,DOMDocument . İşin iyi yanı, bu etiketi eklemenize gerek olmamasıdır. Seçtiğiniz bir kodlamayı kullanmak istemiyorsanız, bunu yapıcı argümanı olarak iletin.

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

Çıktı

<div>Hello World</div>

@ Bart'a teşekkürler


2

Ben de bu gereksinime sahiptim ve yukarıda Alex tarafından yayınlanan çözümü beğendim. Bununla birlikte, birkaç sorun vardır - <body>öğe birden fazla alt öğe içeriyorsa, ortaya çıkan belge yalnızca öğesinin ilk alt öğesini içerecektir <body>, hepsini değil. Ayrıca, işleri koşullu olarak halletmek için soymaya ihtiyacım vardı - yalnızca HTML başlıklarını içeren belgeniz olduğunda. Bu yüzden aşağıdaki gibi rafine ettim. Kaldırmak yerine <body>, onu a'ya dönüştürdüm <div>ve XML bildirimini ve <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

Diğer üyeler gibi ben de @Alessandro Vendruscolo cevabının sadeliğini ve müthiş gücünü ilk kez keşfettim. Yapıcıya bazı işaretli sabitleri basitçe geçirme yeteneği gerçek olamayacak kadar iyi görünüyordu. Benim için öyleydi. Hem LibXML hem de PHP'nin doğru sürümlerine sahibim ancak yine de HTML etiketini Document nesnesinin düğüm yapısına ekler.

Benim çözümüm, kullanmaktan çok daha iyi çalıştı ...

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

Bayraklar veya ...

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

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

DOM'da yapılandırılmış bir sıra olmadan karmaşık hale gelen Düğüm Kaldırma. Yine kod parçalarının DOM yapısını önceden belirleme yolu yoktur.

Bu yolculuğa, JQuery'nin yaptığı gibi DOM geçişini yapmanın basit bir yolunu isteyerek başladım ya da en azından tek başına bağlanmış, çift bağlantılı veya ağaç düğüm geçişi olan yapılandırılmış bir veri kümesine sahip bir şekilde. Bir dizeyi HTML'nin yaptığı gibi ne kadar uzun süre ayrıştırabildiğim ve aynı zamanda yol boyunca kullanmak için düğüm varlık sınıfı özelliklerinin inanılmaz gücüne sahip olduğum için umursamadım.

Şimdiye kadar DOMDocument Object beni istemeye bıraktı ... Diğer birçok programcıda olduğu gibi ... Sonunda bu soruda çok fazla hayal kırıklığı gördüğümü biliyorum, bu yüzden SONUNDA .... (kabaca 30 saatlik deneme ve başarısızlıktan sonra tip testi) Hepsini elde etmenin bir yolunu buldum. Umarım bu birine yardımcı olur ...

Öncelikle, HER ŞEY hakkında alaylıyım ... lol ...

Bu kullanım durumunda yine de bir üçüncü taraf sınıfına ihtiyaç duyulduğu konusunda herhangi biriyle anlaşmadan önce bir ömür geçirirdim. Herhangi bir üçüncü şahıs sınıf yapısını kullanmanın hayranı değildim ve değilim, ancak harika bir ayrıştırıcıyla karşılaştım. (Ben teslim olmadan önce Google'da yaklaşık 30 kez, bu yüzden kaçındıysanız, hiçbir şekilde gayri resmi göründüğü için kendinizi yalnız hissetmeyin ...)

Kod parçalarını kullanıyorsanız ve ek etiketler kullanılmadan, temiz ve ayrıştırıcıdan herhangi bir şekilde etkilenmeyen kodlara ihtiyaç duyuyorsanız, simplePHPParser'ı kullanın . .

Şaşırtıcı ve çok JQuery gibi davranıyor. Sık sık etkilenmedim ama bu sınıf birçok iyi aracı kullanıyor ve şimdiye kadar hiçbir ayrıştırma hatası almadım. Bu sınıfın yaptığını yapabilmenin büyük bir hayranıyım.

Dosyalarını buradan indirebilirsiniz , başlangıç ​​talimatlarını burada ve API'sini burada bulabilirsiniz . Bu sınıfı, .find(".className")bir JQuery bulma yönteminin kullanılacağı şekilde veya hatta getElementByTagName()veya getElementById()... gibi tanıdık yöntemlerle aynı şekilde yapabilen basit yöntemleriyle kullanmanızı şiddetle tavsiye ederim .

Bu sınıftaki bir düğüm ağacını kaydettiğinizde, hiçbir şey eklemez. Basitçe söyleyebilirsiniz $doc->save();ve herhangi bir karışıklık olmadan tüm ağacı bir dizeye çıkarır.

Şimdi bu ayrıştırıcıyı gelecekte tüm, sınırlı olmayan bant genişliği projeleri için kullanacağım.


2

PHP 5.3 kullanıyorum ve buradaki cevaplar benim için işe yaramadı.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);tüm belgeyi yalnızca ilk çocukla değiştirdim, birçok paragrafım vardı ve yalnızca ilki kaydediliyordu, ancak çözüm bana regexbazı yorumlar bırakmadan bir şeyler yazmak için iyi bir başlangıç ​​noktası sağladı ve bunun geliştirilebileceğinden oldukça eminim ama eğer birisi benimle aynı sorunu yaşıyor, bu iyi bir başlangıç ​​noktası olabilir.

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

O zaman bunu şu şekilde kullanabiliriz:

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

A'yı appendChildkabul ettiğine dikkat edin, DOMNodeböylece yeni öğeler oluşturmamıza gerek kalmaz, birden çok HTML / XML belgesini işlerken kodu "mantıklı" tutmak için bu DOMNodegibi uygulayan mevcut olanları yeniden DOMElementkullanabiliriz.


Bu, parçalar için çalışmaz, yalnızca belgenin ilk alt öğesini yapmak istediğiniz tek bir alt öğe için çalışmaz. Bu oldukça sınırlıdır ve LIBXML_HTML_NOIMPLIEDsadece kısmen yaptığı gibi işi etkili bir şekilde yapmaz. Belge türünün kaldırılması etkilidir LIBXML_HTML_NODEFDTD.
hakre

2

3 problemle karşılaşıyorum DOMDocument .

1- Bu sınıf, html'yi ISO kodlaması ile yükler ve çıktıda görünmeyen utf-8 karakterleri.

2- Versek bileLIBXML_HTML_NOIMPLIED loadHTML yöntemine

3- Bu sınıf, HTML5 etiketlerini geçersiz kabul eder.

Bu yüzden bu problemleri çözmek için bu sınıfı geçersiz kıldım ve bazı yöntemleri değiştirdim.

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

}

Şimdi DOMEditoryerine kullanıyorum DOMDocumentve şimdiye kadar benim için iyi çalıştı

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

1. noktanız mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8') kullanılarak çözülür; loadHTML () ve 2.'yi kullanmadan önce, örneğin mb_convert_encoding () civarında, yardımcı işlevinizde bir DIV etiketi bulundurarak. Benim için yeterince iyi çalıştı. Gerçekten de DIV yoksa, benim
durumuma

0

Ben de bu konuya geldim.

Ne yazık ki, bu iş parçacığında sağlanan çözümlerden hiçbirini rahatça kullanmadım, bu yüzden beni tatmin edecek olanı kontrol etmeye gittim.

İşte uydurduğum şey ve sorunsuz çalışıyor:

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

Temelde, burada sağlanan çözümlerin çoğuna benzer şekilde çalışır, ancak el emeği yerine vücuttaki tüm öğeleri seçmek için xpath seçici kullanır ve html kodunu birleştirir.


Buradaki tüm çözümler gibi, her durumda işe yaramaz: Yüklenen dize işaretlemeyle başlamadıysa, <p> </p> eklendiyse, o zaman kodunuz çalışmaz çünkü <p> Kaydedilen içerikte </p> işaretleme
copndz

Dürüst olmak gerekirse, onu ham metinle test etmedim, ancak teoride çalışmalı. Özel durumunuz için xpath'i benzeri bir şeye değiştirmeniz gerekebilir descendant-or-self::body/p/*.
Nikola Petkanski

0

sunucum php 5.3 aldı ve yükseltilemiyor, bu yüzden bu seçenekler

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

benim için değil.

Bunu çözmek için SaveXML Function'a Body öğesini yazdırmasını ve ardından "body" öğesini "div" ile değiştirmesini söylüyorum.

İşte benim kodum, umarım birine yardımcı olur:

<? 
$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, İbranice destek içindir.


0

Alex'in cevabı doğru, ancak boş düğümlerde aşağıdaki hataya neden olabilir:

DOMNode :: removeChild () 'e iletilen bağımsız değişken 1, DOMNode'un bir örneği olmalıdır

İşte benim küçük modum:

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

Döşeme () eklemek de beyaz boşlukları kaldırmak için iyi bir fikirdir.


0

Belki çok geç kaldım Ama belki birisi (benim gibi) hala bu sorunu yaşıyor.
Yani, yukarıdakilerin hiçbiri benim için işe yaramadı. $ Dom-> loadHTML aynı zamanda açık etiketleri de kapattığından, yalnızca html ve gövde etiketleri eklemekle kalmaz.
Yani bir <div> öğesi eklemek benim için çalışmıyor, çünkü bazen html parçasında 3-4 kapatılmamış div gibi var.
Benim çözümüm:

1.) Kesmek için işaretçi ekleyin, ardından html parçasını yükleyin

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

2.) belge ile istediğinizi yapın
3.) html kaydedin

$new_html_piece = $dom->saveHTML();

4.) iade etmeden önce <p> </ p> etiketlerini işaretleyiciden kaldırın, tuhaf bir şekilde yalnızca [MARK] üzerinde görünüyor ama [/ MARK] üzerinde görünmüyor ...!?

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

5.) işaretleyiciden önceki ve sonraki her şeyi kaldırın

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

6.) iade et

return $new_html_piece;

LIBXML_HTML_NOIMPLIED benim için çalışsaydı çok daha kolay olurdu. Olabilir, ama değil. PHP 5.4.17, libxml Sürüm 2.7.8.
Gerçekten garip buluyorum, HTML DOM ayrıştırıcısını kullanıyorum ve sonra bu "şeyi" düzeltmek için regex kullanmam gerekiyor ... Bütün mesele regex kullanmak değil;)


Burada yaptığınız şey tehlikeli görünüyor, stackoverflow.com/a/29499718/367456 işi sizin için yapmalı.
hakre

Maalesef bu ( stackoverflow.com/questions/4879946/… ) benim için çalışmayacak. Dediğim gibi: "Bu nedenle, bir <div> öğesi eklemek benim için çalışmıyor, çünkü bazen html parçasında 3-4 kapatılmamış div gibi var" Nedense, DOMDocument tüm "kapalı" öğeleri kapatmak istiyor. Belki bir kısa kodda veya başka bir işaretleyicide bir fregment alırım, fregmenti kaldırırım ve belgenin diğer parçasını değiştirmek istiyorum, işim bittiğinde, fregmenti geri ekleyeceğim.
Joe

Bunun yerine kendi içeriğinizi yükledikten sonra div öğesini dışarıda bırakıp gövde öğesi üzerinde işlem yapmanız mümkün olmalıdır. Bir parça yüklediğinizde gövde öğesi örtük olarak eklenmelidir.
hakre

Benim sorunum, fregment containt kapatılmamış etiketim. Kapatılmamalı ve DOMDocument bu öğeleri kapatacaktır. Gibi Fregment: < div >< div > ... < /div >. Hala çözüm arıyorum.
Joe

Hmm, div etiketlerinin her zaman bir kapanış çifti olduğunu düşünüyorum. Belki Tidy bunun üstesinden gelebilir, parçalarla da çalışabilir.
hakre

0

Drupal kullanan herkes için bunu yapmak için yerleşik bir işlev vardır:

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

Referans kodu:

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. Drupal API'deki bu işlevi kullanarak Drupal 7 sitemde iyi çalışıyor. Sanırım Drupal kullanmayanlar işlevi kendi sitelerine kopyalayabilirler - çünkü bu konuda Drupal'a özgü hiçbir şey yok.
Free Radical

0

Düzenli özelliğini yalnızca şov gövdesi ile kullanabilirsiniz:

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

Ancak şunu unutmayın: Düzenli Yazı Tipi Başar simgeleri gibi bazı etiketleri kaldırın: PHP ile HTML (5) Girintileme Sorunları


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

Neden -1'i paylaşmak ister misiniz?
Dylan Maxey

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.