วิธี saveHTML ของ DOMDocument โดยไม่ใช้ HTML wrapper


116

ฉันเป็นฟังก์ชั่นด้านล่างฉันกำลังดิ้นรนเพื่อส่งออก DOMDocument โดยที่ไม่ต้องต่อท้าย XML, HTML, bodyและp tag ก่อนผลลัพธ์ของเนื้อหา การแก้ไขที่แนะนำ:

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

ใช้งานได้เฉพาะเมื่อเนื้อหาไม่มีองค์ประกอบระดับบล็อกอยู่ภายใน อย่างไรก็ตามเมื่อเป็นเช่นนั้นดังตัวอย่างด้านล่างพร้อมกับองค์ประกอบ h1 ผลลัพธ์ที่ได้จาก saveXML จะถูกตัดทอนเป็น ...

<p> ถ้าคุณชอบ </p>

ฉันถูกชี้ไปที่โพสต์นี้ว่าเป็นวิธีแก้ปัญหาที่เป็นไปได้ แต่ฉันไม่เข้าใจวิธีการนำไปใช้ในโซลูชันนี้ (ดูความคิดเห็นในความพยายามด้านล่าง)

ข้อเสนอแนะใด ๆ ?

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

คำตอบ:


217

ตอนนี้คำตอบทั้งหมดนี้ไม่ถูกต้องเนื่องจาก PHP 5.4 และ Libxml 2.6 loadHTMLมี$optionพารามิเตอร์ที่สั่งให้ Libxml ทราบว่าควรแยกวิเคราะห์เนื้อหาอย่างไร

ดังนั้นหากเราโหลด HTML ด้วยตัวเลือกเหล่านี้

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

เมื่อทำsaveHTML()จะไม่มีdoctypeไม่มีและไม่มี<html><body>

LIBXML_HTML_NOIMPLIEDปิดการเพิ่มองค์ประกอบ html / body โดยนัยโดยอัตโนมัติ LIBXML_HTML_NODEFDTDป้องกันไม่ให้มีการเพิ่มประเภทหลักเริ่มต้นเมื่อไม่พบ

เอกสารฉบับเต็มเกี่ยวกับพารามิเตอร์ libxml คือที่นี่

(โปรดทราบว่าloadHTMLเอกสารบอกว่าจำเป็นต้องใช้ Libxml 2.6 แต่LIBXML_HTML_NODEFDTDมีให้บริการใน Libxml 2.7.8 เท่านั้นและLIBXML_HTML_NOIMPLIEDมีให้บริการใน Libxml 2.7.7)


10
การทำงานนี้เหมือนมีเสน่ห์ ควรเป็นคำตอบที่ได้รับการยอมรับ ฉันเพิ่งเพิ่มธงหนึ่งรายการและอาการปวดหัวของฉันก็หายไป ;-)
Just Plain High

8
สิ่งนี้ใช้ไม่ได้กับ PHP 5.4 และ Libxml 2.9 loadHTML ไม่ยอมรับตัวเลือกใด ๆ :(
Acyra

11
โปรดทราบว่านี่ยังไม่สมบูรณ์แบบ ดูstackoverflow.com/questions/29493678/…
Josh Levinson

4
ขออภัยดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่ดีเลย (อย่างน้อยก็ไม่ใช่ในทางปฏิบัติ) ไม่ควรเป็นคำตอบที่ยอมรับได้จริงๆ นอกจากนี้ปัญหาดังกล่าว, นอกจากนี้ยังมีปัญหาการเข้ารหัสที่น่ารังเกียจด้วยDOMDocumentที่ยังมีผลต่อรหัสในคำตอบนี้ afaik, DOMDocumentเสมอตีความข้อมูลเข้าเป็น Latin-1 เว้นแต่การป้อนข้อมูลที่ระบุ charset กล่าวอีกนัยหนึ่ง: <meta charset="…">ดูเหมือนว่าแท็กจะจำเป็นสำหรับข้อมูลอินพุตที่ไม่ใช่ภาษาละติน -1 มิฉะนั้นเอาต์พุตจะเสียสำหรับอักขระแบบมัลติไบต์เช่น UTF-8
mermshaus

1
LIBXML_HTML_NOIMPLIED ยังทำให้โค้ด HTML สับสนโดยการลบแท็บเยื้องและตัวแบ่งบรรทัด
ZoltánSüle

72

เพียงลบโหนดออกโดยตรงหลังจากโหลดเอกสารด้วย loadHTML ():

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

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

นี่คือคำตอบที่สะอาดกว่าสำหรับฉัน
KnF

39
ควรสังเกตว่าวิธีนี้ใช้ได้ถ้า <body> มีโหนดลูกเพียงโหนดเดียว
Yann Milin

ทำงานได้ดีมาก ขอบคุณ! สะอาดและเร็วกว่าคำตอบ preg อื่น ๆ มาก
Ligemer

ขอบคุณสำหรับสิ่งนี้! ฉันเพิ่งเพิ่มสนิปอีกอันที่ด้านล่างเพื่อจัดการกับโหนดว่าง
redaxmedia

2
รหัสที่จะลบใช้<!DOCTYPE งานได้ บรรทัดที่สองจะแตกหาก<body>มีโน้ตย่อยมากกว่าหนึ่งตัว
Free Radical

21

ใช้saveXML()แทนและส่ง documentElement เป็นอาร์กิวเมนต์ไป

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

http://php.net/domdocument.savexml


ดีกว่า แต่ฉันยังคงได้รับ <html><body> <p> สรุปเนื้อหา
Scott B


2
ควรสังเกตว่า saveXML () จะบันทึก XHTML ไม่ใช่ HTML
alexantd

@Scott: แปลกจริงๆ จะแสดงสิ่งที่คุณกำลังพยายามทำอยู่ในส่วนตัวอย่าง คุณแน่ใจหรือว่าคุณไม่มี HTML นั้นใน DOM ของคุณ? HTML ใน DOMDocument ของคุณคืออะไร อาจเป็นไปได้ว่าเราจำเป็นต้องเข้าถึงโหนดลูก
โยนาห์

@ จอนห์ก็ไม่แปลก เมื่อคุณทำloadHTMLlibxml ใช้โมดูลตัวแยกวิเคราะห์ HTML และจะแทรกโครงกระดูก HTML ที่หายไป ดังนั้น$dom->documentElementจะเป็นองค์ประกอบ HTML ราก ฉันได้แก้ไขโค้ดตัวอย่างของคุณแล้ว ตอนนี้ควรทำตามที่สก็อตต์ขอ
Gordon

19

ปัญหาที่มีคำตอบด้านบนLIBXML_HTML_NOIMPLIEDคือไม่เสถียรไม่เสถียร

มันสามารถเรียงลำดับองค์ประกอบ (โดยเฉพาะการเคลื่อนย้ายแท็กปิดองค์ประกอบชั้นนำของไปที่ด้านล่างของเอกสาร), เพิ่มการสุ่มpแท็กและบางทีอาจจะเป็นความหลากหลายของปัญหาอื่น ๆ[1] อาจลบhtmlและbodyแท็กให้คุณได้ แต่ต้องเสียค่าใช้จ่ายจากพฤติกรรมที่ไม่เสถียร ในการผลิตนั่นคือธงสีแดง ในระยะสั้น:

อย่าใช้LIBXML_HTML_NOIMPLIED . ให้ใช้substrไฟล์.


ลองคิดดูสิ ความยาว<html><body>และ</body></html>คงที่และที่ปลายทั้งสองด้านของเอกสาร - ขนาดไม่เคยเปลี่ยนแปลงและไม่ได้กำหนดตำแหน่ง สิ่งนี้ช่วยให้เราสามารถใช้substrเพื่อตัดมันออกไป:

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

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

( นี่ไม่ใช่วิธีแก้ปัญหาขั้นสุดท้ายอย่างไรก็ตามดูคำตอบที่สมบูรณ์ด้านล่างอ่านต่อไปเพื่อดูบริบท)

เราตัด12ออกจากจุดเริ่มต้นของเอกสารเนื่องจาก<html><body>= 12 ตัวอักษร ( <<>>+html+body= 4 + 4 + 4) และเราย้อนกลับและตัดส่วนท้ายออก 15 ตัวเนื่องจาก\n</body></html>= 15 ตัวอักษร ( \n+//+<<>>+body+html= 1 + 2 + 4 + 4 + 4)

สังเกตว่าฉันยังคงใช้LIBXML_HTML_NODEFDTDละเว้น!DOCTYPEจากการรวม ขั้นแรกสิ่งนี้ช่วยลดความยุ่งยากในsubstrการลบแท็ก HTML / BODY ประการที่สองเราจะไม่ลบประเภทหลักด้วยsubstrเพราะเราไม่รู้ว่า " default doctype" จะเป็นค่าความยาวคงที่หรือไม่ แต่ที่สำคัญที่สุดคือLIBXML_HTML_NODEFDTDหยุดตัวแยกวิเคราะห์ DOM จากการใช้หลักประเภทที่ไม่ใช่ HTML5 กับเอกสารซึ่งอย่างน้อยก็ป้องกันไม่ให้ตัวแยกวิเคราะห์จัดการกับองค์ประกอบที่ไม่รู้จักว่าเป็นข้อความหลวม

เราทราบดีว่าแท็ก HTML / BODY มีความยาวและตำแหน่งคงที่และเราทราบดีว่าค่าคงที่LIBXML_HTML_NODEFDTDจะไม่ถูกลบออกโดยไม่มีการแจ้งเตือนการเลิกใช้งานบางประเภทดังนั้นวิธีการข้างต้นน่าจะนำไปใช้ได้ดีในอนาคตแต่ ...


... ข้อแม้เดียวคือการใช้งาน DOM สามารถเปลี่ยนวิธีการวางแท็ก HTML / BODY ภายในเอกสารได้ตัวอย่างเช่นการลบบรรทัดใหม่ท้ายเอกสารการเพิ่มช่องว่างระหว่างแท็กหรือการเพิ่มบรรทัดใหม่

ซึ่งสามารถแก้ไขได้โดยค้นหาตำแหน่งของแท็กเปิดและปิดสำหรับbodyและใช้ค่าชดเชยเหล่านั้นสำหรับความยาวของเราในการตัดออก เราใช้strposและ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);

ในการปิดท้ายให้ทำซ้ำคำตอบสุดท้ายที่พิสูจน์ได้ในอนาคต :

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

ไม่มีประเภทหลักไม่มีแท็ก html ไม่มีแท็กเนื้อหา เราหวังได้เพียงว่าโปรแกรมแยกวิเคราะห์ DOM จะได้รับสีใหม่ในเร็ว ๆ นี้และเราสามารถกำจัดแท็กที่ไม่ต้องการเหล่านี้ได้โดยตรงมากขึ้น


คำตอบที่ดีความคิดเห็นเล็ก ๆ ทำไมไม่$html = $dom -> saveHTML();แทนที่จะ$dom -> saveHTML();ซ้ำ ๆ
สตีเวน

15

เคล็ดลับที่เรียบร้อยคือการใช้แล้วloadXML และแท็กจะถูกแทรกที่เวทีไม่ได้เป็นขั้นตอนsaveHTMLhtmlbodyloadsave

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

โปรดทราบว่านี่เป็นการแฮ็กเล็กน้อยและคุณควรใช้คำตอบของโจนาห์หากคุณสามารถใช้งานได้


4
สิ่งนี้จะล้มเหลวสำหรับ HTML ที่ไม่ถูกต้องแม้ว่า
Gordon

1
@Gordon ว่าทำไมฉันใส่คำปฏิเสธที่ด้านล่าง!
lonesomeday

1
เมื่อฉันลองสิ่งนี้และ echo $ dom-> saveHTML () มันจะส่งคืนสตริงว่าง ราวกับว่า loadXML ($ content) ว่างเปล่า เมื่อฉันทำเช่นเดียวกันกับ $ dom-> loadHTML ($ content) แล้ว echo $ dom-> saveXML () ฉันจะได้เนื้อหาตามที่คาดไว้
Scott B

การใช้ loadXML เมื่อต้องการโหลด HTMl คือหัวแม่มือ โดยเฉพาะอย่างยิ่งเนื่องจาก LoadXML ไม่ทราบวิธีจัดการ HTML
botenvouwer

15

ใช้ DOMDocumentFragment

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

3
คำตอบที่สะอาดที่สุดสำหรับพรี php5.4
Nick Johnson

สิ่งนี้ใช้ได้กับฉันทั้งรุ่นเก่าและใหม่กว่า Libxml 2.7.7 เหตุใดจึงต้องเป็นรุ่นก่อน php5.4 แต่เพียงผู้เดียว
RobbertT

เรื่องนี้ควรมีคะแนนเสียงมากกว่านี้ ตัวเลือกที่ยอดเยี่ยมสำหรับ libxml เวอร์ชันที่ไม่รองรับ LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ขอบคุณ!
Marty Mulligan

13

มันเป็นปี 2017 และสำหรับคำถามปี 2011 นี้ฉันไม่ชอบคำตอบใด ๆ regex มากมายคลาสใหญ่ loadXML และอื่น ๆ ...

วิธีง่ายๆที่ช่วยแก้ปัญหาที่ทราบ:

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

ง่ายง่ายมั่นคงรวดเร็ว รหัสนี้จะทำงานเกี่ยวกับแท็ก HTML และการเข้ารหัสเช่น:

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

หากใครพบข้อผิดพลาดโปรดบอกฉันจะใช้สิ่งนี้เอง

แก้ไขตัวเลือกที่ถูกต้องอื่น ๆ ที่ทำงานได้โดยไม่มีข้อผิดพลาด (คล้ายกับที่ให้ไว้แล้ว):

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

คุณสามารถเพิ่มร่างกายตัวเองเพื่อป้องกันสิ่งแปลก ๆ บนขน

ตัวเลือกที่สาม:

 $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
คุณควรปรับปรุงคำตอบของคุณโดยหลีกเลี่ยงราคาแพงกว่าmb_convert_encodingและเพิ่ม<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>และแก้ไขsubstrตามนั้นแทน Btw คุณเป็นทางออกที่ดีที่สุดที่นี่ upvoted
Hlsg

10

ฉันเข้าคลับช้าไปหน่อย แต่ไม่อยากจะไม่แบ่งปันวิธีการที่ฉันค้นพบ ก่อนอื่นฉันมีเวอร์ชันที่เหมาะสมสำหรับ loadHTML () เพื่อยอมรับตัวเลือกที่ดีเหล่านี้ แต่ใช้LIBXML_HTML_NOIMPLIEDไม่ได้กับระบบของฉัน นอกจากนี้ผู้ใช้รายงานปัญหาเกี่ยวกับตัวแยกวิเคราะห์ (ตัวอย่างเช่นที่นี่และที่นี่ )

วิธีแก้ปัญหาที่ฉันสร้างขึ้นนั้นค่อนข้างง่าย

HTML ที่จะโหลดจะอยู่ในไฟล์ <div>องค์ประกอบดังนั้นจึงมีคอนเทนเนอร์ที่มีโหนดทั้งหมดที่จะโหลด

จากนั้นองค์ประกอบคอนเทนเนอร์นี้จะถูกลบออกจากเอกสาร (แต่DOMElementของมันยังคงมีอยู่)

จากนั้นเด็กโดยตรงทั้งหมดจากเอกสารจะถูกลบออก ซึ่งรวมถึงการเพิ่มใด ๆ<html>, <head>และ<body>แท็ก (อย่างมีประสิทธิภาพLIBXML_HTML_NOIMPLIEDตัวเลือก) เช่นเดียวกับ<!DOCTYPE html ... loose.dtd">การประกาศ (อย่างมีประสิทธิภาพLIBXML_HTML_NODEFDTD)

จากนั้นลูกโดยตรงทั้งหมดของคอนเทนเนอร์จะถูกเพิ่มลงในเอกสารอีกครั้งและสามารถส่งออกได้

$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 ทำงานได้ตามปกติเพียงดูแลว่าตอนนี้มีองค์ประกอบเอกสารหลายรายการดังนั้นไม่ใช่โหนดรูทเดียว:

$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 ~ แม่นยำ + 2 (cli) (สร้าง: 21 ธันวาคม 2557 20:28:53 น.)

มันไม่ได้ผลสำหรับฉันกับซอร์ส HTML ที่ซับซ้อนกว่านี้ นอกจากนี้ยังลบบางส่วนของ HTML
ZoltánSüle

4

ไม่มีวิธีแก้ปัญหาอื่นใดในขณะที่เขียนนี้ (มิถุนายน 2555) ที่สามารถตอบสนองความต้องการของฉันได้อย่างสมบูรณ์ดังนั้นฉันจึงเขียนวิธีแก้ไขปัญหาต่อไปนี้:

  • ยอมรับเนื้อหาข้อความธรรมดาที่ไม่มีแท็กเช่นเดียวกับเนื้อหา HTML
  • ไม่ได้ผนวกแท็กใด ๆ (รวมทั้ง<doctype>, <xml>, <html>, <body>และ<p>แท็ก)
  • ทิ้งสิ่งที่ห่อไว้ <p>เพียงอย่างเดียว
  • ปล่อยให้ข้อความว่างเปล่าอยู่คนเดียว

ดังนั้นนี่คือวิธีแก้ปัญหาที่แก้ไขปัญหาเหล่านั้น:

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

ฉันยังเขียนการทดสอบบางอย่างซึ่งจะอยู่ในชั้นเรียนเดียวกัน:

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
}

คุณสามารถตรวจสอบได้ว่าเหมาะกับตัวคุณเอง DomDocumentWorkaround::testAll()ส่งคืนสิ่งนี้:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

1
HTML = / = XML คุณควรใช้ตัวโหลด HTML สำหรับ HTML
hakre

4

โอเคฉันพบวิธีแก้ปัญหาที่หรูหรากว่านี้ แต่มันน่าเบื่อ:

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

เอาล่ะหวังว่านี่จะไม่ละเว้นอะไรและช่วยใครได้บ้าง?


2
ไม่จัดการกรณีที่ loadHTML โหลดสตริงโดยไม่มีมาร์กอัป
copndz

3

ใช้ฟังก์ชันนี้

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

13
อาจมีผู้อ่านบางคนที่สะดุดในโพสต์นี้ผ่านโพสต์นี้ตัดสินใจที่จะไม่ใช้ regex เพื่อแยกวิเคราะห์ HTML และใช้ DOM parser แทนและอาจต้องใช้คำตอบ regex เพื่อให้ได้โซลูชันที่สมบูรณ์ ... แดกดัน
Robbie Averill

ฉันไม่เข้าใจว่าทำไมโนบอยถึงคืนเนื้อหาของ BODY ไม่ควรให้แท็กนั้นปรากฏอยู่เสมอเมื่อตัวแยกวิเคราะห์เพิ่มส่วนหัว / ประเภทเอกสารทั้งหมดหรือไม่ regex ด้านบนจะสั้นกว่าด้วยซ้ำ
เซร์คิโอ

@boksiora "มันได้ผล" - แล้วทำไมเราถึงใช้วิธีการแยกวิเคราะห์ DOM ตั้งแต่แรก?
ขอบคุณ

@naomik ฉันไม่ได้บอกว่าจะไม่ใช้ตัวแยกวิเคราะห์ DOM แน่นอนว่ามีหลายวิธีในการบรรลุผลลัพธ์เดียวกันขึ้นอยู่กับคุณในเวลาที่ฉันใช้ฟังก์ชันนี้ฉันมีปัญหากับ php dom ในตัว parser ซึ่งไม่ได้แยกวิเคราะห์ html5 อย่างถูกต้อง
boksiora

1
ฉันต้องใช้preg_replaceเพราะการใช้วิธีการที่ใช้ DOMDocument ในการลบ html และแท็กเนื้อหาไม่ได้รักษาการเข้ารหัส UTF-8 :(
wizonesolutions

3

หากโซลูชันการตั้งค่าสถานะที่ตอบโดยAlessandro Vendruscoloไม่ได้ผลคุณสามารถลองสิ่งนี้:

$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จะมีโค้ด HTML ที่ผ่านการประมวลผลทั้งหมดของคุณโดยไม่มีการห่อ HTML ทั้งหมดยกเว้น<body>แท็กซึ่งเป็นรากของเนื้อหาของคุณ จากนั้นคุณสามารถใช้ regex หรือฟังก์ชั่นตัดแต่งเพื่อลบออกจากสตริงสุดท้าย (หลังsaveHTML) หรือเช่นเดียวกับในกรณีด้านบนให้ทำซ้ำทับ childen ทั้งหมดบันทึกเนื้อหาลงในตัวแปรชั่วคราว$finalHtmlและส่งคืน (สิ่งที่ฉันเชื่อว่าเป็น ปลอดภัยมากขึ้น)


3

ฉันเจอหัวข้อนี้เพื่อหาวิธีลบ HTML wrapper การใช้LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTDงานได้ดี แต่ฉันมีปัญหากับ utf-8 หลังจากใช้ความพยายามอย่างมากฉันก็พบวิธีแก้ปัญหา ฉันโพสต์มันร้องสำหรับใครก็ตามที่มีปัญหาเดียวกัน

ปัญหาเกิดจาก <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

ปัญหา:

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

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

แนวทางที่ 2:

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

1
ฉันรู้สึกดีที่คุณแบ่งปันสิ่งที่คุณค้นพบ แต่โซลูชัน 2 มีอยู่แล้วพร้อมกับคำถามที่แน่นอนนี้ที่นี่และโซลูชัน 1 อยู่ที่อื่น นอกจากนี้สำหรับปัญหาของโซลูชันที่ 1 คำตอบที่ได้รับยังไม่ชัดเจน ฉันเคารพในความตั้งใจที่ดีของคุณ แต่โปรดทราบว่ามันสามารถสร้างเสียงรบกวนได้มากและขัดขวางผู้อื่นในการค้นหาวิธีแก้ปัญหาที่พวกเขากำลังมองหาซึ่งฉันเดาว่าตรงกันข้ามกับสิ่งที่คุณต้องการบรรลุกับคำตอบของคุณ Stackoverflow จะทำงานได้ดีที่สุดหากคุณจัดการทีละคำถาม เพียงแค่คำใบ้
hakre

3

ฉันกำลังดิ้นรนกับสิ่งนี้ใน RHEL7 ที่ใช้ PHP 5.6.25 และ LibXML 2.9 (ของเก่าในปี 2018 ฉันรู้ แต่นั่นคือ Red Hat สำหรับคุณ)

ฉันพบว่าโซลูชันที่ได้รับการโหวตเพิ่มขึ้นมากที่แนะนำโดยAlessandro Vendruscoloทำลาย HTML โดยการจัดเรียงแท็กใหม่ เช่น:

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

กลายเป็น:

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

นี้ไปสำหรับทั้งสองตัวเลือกที่เขาแนะนำให้คุณใช้: และLIBXML_HTML_NOIMPLIEDLIBXML_HTML_NODEFDTD

วิธีแก้ปัญหาที่Alexแนะนำไปครึ่งทางในการแก้ปัญหา แต่จะไม่ได้ผลหาก<body>มีโหนดลูกมากกว่าหนึ่งโหนด

วิธีแก้ปัญหาที่เหมาะกับฉันคือการต่อไปนี้:

ก่อนอื่นในการโหลด DOMDocument ฉันใช้:

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

ในการบันทึกเอกสารหลังจากการนวด DOMDocument ฉันใช้:

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

ฉันเป็นคนแรกที่ยอมรับว่านี่ไม่ใช่วิธีแก้ปัญหาที่หรูหรามากนัก แต่ก็ใช้ได้ผล


2

การเพิ่มแท็กจะเรียกการแก้ไขพฤติกรรมของ<meta> DOMDocumentส่วนที่ดีคือคุณไม่จำเป็นต้องเพิ่มแท็กนั้นเลย หากคุณไม่ต้องการใช้การเข้ารหัสที่คุณเลือกเพียงแค่ส่งมันเป็นอาร์กิวเมนต์ตัวสร้าง

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

เอาท์พุต

<div>Hello World</div>

ขอบคุณข้อมูล@Bart


2

ฉันมีข้อกำหนดนี้เช่นกันและชอบโซลูชันที่โพสต์โดย Alex ด้านบน แม้ว่าจะมีปัญหาอยู่สองสามข้อ - หาก<body>องค์ประกอบนั้นมีองค์ประกอบลูกมากกว่าหนึ่งองค์ประกอบเอกสารผลลัพธ์จะมีเฉพาะองค์ประกอบลูกแรกของปัญหา<body>เท่านั้นไม่ใช่ทั้งหมด นอกจากนี้ฉันต้องการการลอกเพื่อจัดการสิ่งต่างๆตามเงื่อนไข - เฉพาะเมื่อคุณมีเอกสารที่มีส่วนหัว HTML เท่านั้น ผมจึงกลั่นมันออกมาดังนี้ แทนที่จะลบ<body>ฉันเปลี่ยนเป็น a <div>และถอดการประกาศ XML และ<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

เช่นเดียวกับสมาชิกคนอื่น ๆ ฉันได้สัมผัสกับความเรียบง่ายและพลังอันยอดเยี่ยมของคำตอบของ @Alessandro Vendruscolo เป็นครั้งแรก ความสามารถในการส่งผ่านค่าคงที่ที่ถูกตั้งค่าสถานะบางอย่างไปยังตัวสร้างดูเหมือนจะดีเกินจริง สำหรับฉันมันเป็น ฉันมีเวอร์ชันที่ถูกต้องของทั้ง LibXML และ PHP อย่างไรก็ตามไม่ว่าจะเพิ่มแท็ก HTML ในโครงสร้างโหนดของวัตถุเอกสารก็ตาม

วิธีแก้ปัญหาของฉันได้ผลดีกว่าการใช้ ...

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

แฟล็กหรือ ....

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

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

การกำจัดโหนดซึ่งยุ่งเหยิงโดยไม่มีลำดับโครงสร้างใน DOM ส่วนโค้ดอีกครั้งไม่มีวิธีกำหนดโครงสร้าง DOM ไว้ล่วงหน้า

ฉันเริ่มต้นการเดินทางครั้งนี้โดยต้องการวิธีง่ายๆในการสำรวจ DOM ว่า JQuery ทำอย่างไรหรืออย่างน้อยก็ในบางรูปแบบที่มีชุดข้อมูลที่มีโครงสร้างไม่ว่าจะเชื่อมโยงเดี่ยวเชื่อมโยงแบบทวีคูณหรือการส่งผ่านโหนดแบบทรี ฉันไม่สนใจว่าตราบใดที่ฉันสามารถแยกวิเคราะห์สตริงด้วยวิธีที่ HTML ทำและยังมีพลังที่น่าทึ่งของคุณสมบัติคลาสเอนทิตีโหนดที่จะใช้ไปพร้อมกัน

จนถึงตอนนี้ DOMDocument Object ทำให้ฉันต้องการ ... เช่นเดียวกับโปรแกรมเมอร์คนอื่น ๆ ดูเหมือนว่า ... ฉันรู้ว่าฉันเห็นความไม่พอใจมากมายในคำถามนี้ตั้งแต่ในที่สุดฉันก็เลย .... (หลังจากลองแล้วล้มเหลวประมาณ 30 ชั่วโมง พิมพ์การทดสอบ) ฉันพบวิธีที่จะได้รับมันทั้งหมด หวังว่านี่จะช่วยใครสักคน ...

ก่อนอื่นฉันเหยียดหยามทุกอย่าง ...ฮ่า ๆ ...

ฉันจะไปตลอดชีวิตก่อนที่จะตกลงกับใครก็ตามว่าจำเป็นต้องมีคลาสของบุคคลที่สามอยู่แล้วในกรณีการใช้งานนี้ ฉันเป็นอย่างมากและไม่ได้เป็นแฟนของการใช้โครงสร้างคลาสของบุคคลที่สามใด ๆ แต่ฉันก็สะดุดกับโปรแกรมแยกวิเคราะห์ที่ยอดเยี่ยม (ประมาณ 30 ครั้งใน Google ก่อนที่ฉันจะให้ดังนั้นอย่ารู้สึกโดดเดี่ยวถ้าคุณหลีกเลี่ยงเพราะมันดูไม่เป็นทางการ แต่อย่างใด ... )

หากคุณกำลังใช้เศษรหัสและจำเป็นต้องมีรหัสที่สะอาดและได้รับผลกระทบโดยการแยกวิเคราะห์ในทางใด ๆ โดยไม่ต้องแท็กพิเศษถูกนำมาใช้แล้วใช้simplePHPParser

มันน่าทึ่งและทำหน้าที่เหมือน JQuery มาก ฉันไม่ค่อยประทับใจบ่อยนัก แต่คลาสนี้ใช้เครื่องมือดีๆมากมายและฉันยังไม่มีข้อผิดพลาดในการแยกวิเคราะห์เลย ฉันเป็นแฟนตัวยงที่สามารถทำสิ่งที่คลาสนี้ทำ

คุณสามารถค้นหาไฟล์ดาวน์โหลดที่นี่คำแนะนำการเริ่มต้นที่นี่และของ API ที่นี่ ฉันขอแนะนำให้ใช้คลาสนี้ด้วยวิธีการง่ายๆที่สามารถทำเช่น.find(".className")เดียวกับวิธีการค้นหา JQuery หรือแม้แต่วิธีการที่คุ้นเคยเช่นgetElementByTagName()หรือgetElementById()...

เมื่อคุณบันทึกโหนดทรีในคลาสนี้จะไม่เพิ่มอะไรเลย คุณสามารถพูดง่ายๆ$doc->save();และส่งเอาต์พุตต้นไม้ทั้งหมดเป็นสตริงโดยไม่ต้องยุ่งยาก

ตอนนี้ฉันจะใช้โปรแกรมแยกวิเคราะห์นี้สำหรับโปรเจ็กต์ทั้งหมดที่ไม่มีการ จำกัด แบนด์วิดท์ในอนาคต


2

ฉันมี PHP 5.3 และคำตอบที่นี่ใช้ไม่ได้กับฉัน

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);แทนที่เอกสารทั้งหมดด้วยลูกคนแรกเท่านั้นฉันมีหลายย่อหน้าและมีเพียงคนแรกเท่านั้นที่ได้รับการบันทึก แต่วิธีแก้ปัญหาทำให้ฉันมีจุดเริ่มต้นที่ดีในการเขียนบางสิ่งโดยที่regexฉันไม่ได้แสดงความคิดเห็นและฉันค่อนข้างแน่ใจว่าสิ่งนี้สามารถปรับปรุงได้ แต่ถ้า ใครบางคนมีปัญหาเช่นเดียวกับฉันอาจเป็นจุดเริ่มต้นที่ดี

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

จากนั้นเราสามารถใช้มันได้ดังนี้:

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

โปรดทราบว่าappendChildยอมรับDOMNodeดังนั้นเราจึงไม่จำเป็นต้องสร้างองค์ประกอบใหม่เราสามารถนำองค์ประกอบที่มีอยู่กลับมาใช้ใหม่ได้DOMNodeเช่นDOMElementนี้อาจเป็นสิ่งสำคัญที่จะต้องรักษาโค้ดให้ "มีเหตุผล" เมื่อจัดการกับเอกสาร HTML / XML หลายรายการ


สิ่งนี้จะใช้ไม่ได้กับส่วนย่อยเฉพาะสำหรับองค์ประกอบลูกเดียวที่คุณต้องการทำให้เป็นลูกคนแรกของเอกสาร สิ่งนี้ค่อนข้าง จำกัด และไม่ได้ทำงานอย่างมีประสิทธิภาพLIBXML_HTML_NOIMPLIEDเหมือนที่ทำเพียงบางส่วน LIBXML_HTML_NODEFDTDถอดประเภทเอกสารที่เป็นได้อย่างมีประสิทธิภาพ
hakre

2

ฉันประสบปัญหา 3 อย่างกับ DOMDocumentชั้นเรียน

1- คลาสนี้โหลด html ด้วยการเข้ารหัส ISO และอักขระ utf-8 ไม่แสดงในเอาต์พุต

2- แม้ว่าเราจะให้ ‍‍‍LIBXML_HTML_NOIMPLIEDธงวิธีloadHtml จน html ที่ป้อนข้อมูลของเราไม่ได้มีแท็กรากมันจะไม่แยกอย่างถูกต้อง

3- คลาสนี้ถือว่าแท็ก HTML5 ไม่ถูกต้อง

ดังนั้นฉันจึงแทนที่คลาสนี้เพื่อแก้ปัญหาเหล่านี้และฉันเปลี่ยนวิธีการบางอย่าง

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

}

ตอนนี้ฉันใช้DOMEditorแทนDOMDocumentและมันก็ใช้ได้ดีสำหรับฉันจนถึงตอนนี้

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

จุดที่ 1 ของคุณได้รับการแก้ไขโดยใช้ mb_convert_encoding ($ string, 'HTML-ENTITIES', 'UTF-8'); ก่อนที่จะใช้ loadHTML () และข้อ 2 โดยมีแท็ก DIV รอบ ๆ ฟังก์ชันตัวช่วยของคุณรอบ ๆ mb_convert_encoding () ที่คุณใช้เป็นตัวอย่าง ทำงานให้ฉันดีพอ แน่นอนถ้าไม่มี DIV อยู่มันจะเพิ่มย่อหน้าโดยอัตโนมัติในกรณีของฉันซึ่งไม่สะดวกเนื่องจากโดยปกติแล้วจะมีการใช้ระยะขอบ (bootstrap .. )
trainoasis

0

ฉันมาถึงปัญหานี้เช่นกัน

น่าเสียดายที่ฉันไม่รู้สึกสบายใจในการใช้โซลูชันใด ๆ ที่มีให้ในชุดข้อความนี้ดังนั้นฉันจึงไปตรวจสอบวิธีที่จะทำให้ฉันพอใจ

นี่คือสิ่งที่ฉันสร้างขึ้นและใช้งานได้โดยไม่มีปัญหา:

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

ในสาระสำคัญมันทำงานในลักษณะเดียวกันกับโซลูชันส่วนใหญ่ที่ให้ไว้ที่นี่ แต่แทนที่จะใช้แรงงานคนจะใช้ตัวเลือก xpath เพื่อเลือกองค์ประกอบทั้งหมดภายในร่างกายและเชื่อมโค้ด html เข้าด้วยกัน


เช่นเดียวกับวิธีแก้ปัญหาทั้งหมดที่นี่มันใช้ไม่ได้กับทุกกรณี: หากสตริงที่โหลดไม่ได้ขึ้นต้นด้วยมาร์กอัปมีการเพิ่ม <p> </p> แสดงว่าโค้ดของคุณใช้ไม่ได้เนื่องจากจะเพิ่ม <p> </p> มาร์กอัปในเนื้อหาที่บันทึกไว้
copndz

เพื่อความเป็นธรรมฉันไม่ได้ทดสอบด้วยข้อความดิบ แต่ในทางทฤษฎีควรใช้งานได้ สำหรับกรณีของคุณโดยเฉพาะคุณอาจต้องเปลี่ยน XPath descendant-or-self::body/p/*เพื่อสิ่งที่ต้องการ
Nikola Petkanski

0

เซิร์ฟเวอร์ของฉันมี php 5.3 และไม่สามารถอัปเกรดตัวเลือกเหล่านั้นได้

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

ไม่ใช่สำหรับฉัน

ในการแก้ปัญหานี้ฉันบอกให้ฟังก์ชั่น SaveXML เพื่อพิมพ์องค์ประกอบ Body จากนั้นแทนที่ "body" ด้วย "div"

นี่คือรหัสของฉันหวังว่ามันจะช่วยใครสักคน:

<? 
$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 มีไว้สำหรับรองรับภาษาฮิบรู


0

คำตอบของ Alex ถูกต้อง แต่อาจทำให้เกิดข้อผิดพลาดต่อไปนี้บนโหนดว่าง:

อาร์กิวเมนต์ 1 ส่งผ่านไปยัง DOMNode :: removeChild () ต้องเป็นอินสแตนซ์ของ DOMNode

มาแล้ว 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;

การเพิ่มทริม () เป็นความคิดที่ดีในการลบช่องว่าง


0

ฉันอาจจะสายเกินไป แต่อาจมีบางคน (เช่นฉัน) ยังคงมีปัญหานี้
ดังนั้นข้างต้นไม่ได้ผลสำหรับฉัน เนื่องจาก $ dom-> loadHTML ปิดแท็กที่เปิดอยู่เช่นกันไม่เพียง แต่เพิ่ม html และแท็กเนื้อหา
ดังนั้นการเพิ่มองค์ประกอบ <div> ไม่ได้ผลสำหรับฉันเพราะบางครั้งฉันก็มี div ที่ไม่ได้ปิด 3-4 รายการในส่วน html
วิธีแก้ปัญหาของฉัน:

1. ) เพิ่มเครื่องหมายที่จะตัดจากนั้นโหลดชิ้นส่วน html

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

2. ) ทำสิ่งที่คุณต้องการด้วยเอกสาร
3. ) บันทึก html

$new_html_piece = $dom->saveHTML();

4. ) ก่อนที่คุณจะส่งคืนให้ลบแท็ก <p> </ p> ออกจากเครื่องหมายแปลกที่มันปรากฏบน [MARK] เท่านั้น แต่ไม่ปรากฏบน [/ MARK] ... !?

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

5. ) ลบทุกอย่างก่อนและหลังเครื่องหมาย

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

6. ) ส่งคืน

return $new_html_piece;

มันจะง่ายกว่ามากถ้า LIBXML_HTML_NOIMPLIED ทำงานให้ฉัน มันแย่มาก แต่มันไม่ใช่ PHP 5.4.17, libxml เวอร์ชัน 2.7.8
ฉันคิดว่าแปลกจริงๆฉันใช้ตัวแยกวิเคราะห์ HTML DOM จากนั้นเพื่อแก้ไข "สิ่ง" นี้ฉันต้องใช้ regex ... ประเด็นทั้งหมดคือไม่ใช้ regex;)


สิ่งที่คุณทำที่นี่ดูอันตรายstackoverflow.com/a/29499718/367456ควรทำงานให้คุณ
hakre

น่าเสียดายที่สิ่งนี้ ( stackoverflow.com/questions/4879946/… ) ใช้ไม่ได้สำหรับฉัน ดังที่ฉันได้กล่าวไป: "ดังนั้นการเพิ่มองค์ประกอบ <div> จึงไม่ได้ผลสำหรับฉันเพราะบางครั้งฉันก็มี div ที่ไม่ปิด 3-4 รายการในส่วน html" ด้วยเหตุผลบางประการ DOMDocument ต้องการปิดองค์ประกอบที่ "ไม่ปิด" ทั้งหมด ในกรณีนี้ฉันจะได้รับ fregment ภายในรหัสย่อหรือเครื่องหมายอื่น ๆ ลบ fregment และฉันต้องการจัดการกับส่วนอื่น ๆ ของเอกสารเมื่อฉันทำเสร็จแล้วฉันจะแทรก fregment กลับเข้าไป
โจ

ควรเป็นไปได้ที่จะปล่อยองค์ประกอบ div ออกและดำเนินการกับองค์ประกอบของร่างกายหลังจากโหลดเนื้อหาของคุณเองแทน ควรเพิ่มองค์ประกอบของร่างกายโดยปริยายเมื่อคุณโหลดส่วนย่อย
hakre

ปัญหาของฉันคือแท็กที่มีการปิดกั้นพื้นที่ว่างของฉัน ควรไม่ปิดและ DOMDocument จะปิดองค์ประกอบเหล่านั้น Fregment เช่น: < div >< div > ... < /div >. ฉันยังคงมองหาวิธีแก้ปัญหา
โจ

อืมฉันคิดว่าแท็ก div มีคู่ปิดเสมอ บางที Tidy สามารถจัดการกับสิ่งนั้นได้ แต่ก็สามารถทำงานกับชิ้นส่วนได้เช่นกัน
hakre

0

สำหรับใครก็ตามที่ใช้ Drupal จะมีฟังก์ชันในตัวสำหรับทำสิ่งนี้:

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

รหัสสำหรับอ้างอิง:

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 ทำงานได้ดีบนไซต์ Drupal 7 ของฉัน ฉันเดาว่าผู้ที่ไม่ได้ใช้ Drupal สามารถคัดลอกฟังก์ชันลงในไซต์ของตนเองได้เนื่องจากไม่มีอะไรเฉพาะเกี่ยวกับ Drupal
Free Radical

0

คุณสามารถใช้ความเป็นระเบียบเรียบร้อยกับการแสดงเท่านั้น:

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

แต่ remeber: ลบแท็กบางแท็กเช่นไอคอน Font Awesome: ปัญหาในการเยื้อง HTML (5) ด้วย 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);

สนใจที่จะแบ่งปันว่าทำไม -1?
Dylan Maxey

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.