HTMLラッパーなしでDOMDocumentのHTMLを保存する方法は?


116

私は以下の関数です。コンテンツの出力前にXML、HTML、本文、およびpタグラッパーを追加せずにDOMDocumentを出力するのに苦労しています。推奨される修正:

$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 以降、コンテンツの解析方法をLibxmlに指示するパラメーターがPHP 5.4およびLibxml 2.6に含まれるようになっため 、これらの回答はすべて間違っloadHTMLてい$optionます。

したがって、これらのオプションを使用してHTMLをロードすると

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

やったときにsaveHTML()何も存在しませんdoctype、ない<html>、と何を<body>

LIBXML_HTML_NOIMPLIED暗黙のhtml / body要素の自動追加をオフにして LIBXML_HTML_NODEFDTD、デフォルトのdoctypeが見つからないときに追加されないようにします。

Libxmlパラメータに関する完全なドキュメントはこちら

loadHTMLドキュメントではLibxml 2.6が必要であると述べていLIBXML_HTML_NODEFDTDますが、Libxml 2.7.8でのみ利用LIBXML_HTML_NOIMPLIED可能で、Libxml 2.7.7で利用可能です)


10
これは魅力のように機能します。受け入れられる答えでなければなりません。フラグを1つ追加しただけで、頭痛の種はすべてなくなりました;-)
Just Plain High

8
これは、PHP 5.4およびLibxml 2.9では機能しません。loadHTMLはオプションを受け入れません:(
Acyra

11
これは完全ではないことに注意してください。stackoverflow.com/questions/29493678/…を
Josh Levinson

4
申し訳ありませんが、これは(少なくとも実際には)良い解決策ではないようです。それは本当に受け入れられるべき答えではありません。前述の問題に加えて、もあります厄介なエンコーディングの問題を持つDOMDocumentことも、この答えのコードに影響を与えます。Afaikは、入力が別のcharsetを指定しない限りDOMDocument常に入力データをlatin-1として解釈します。つまり、タグは、latin-1 以外の入力データに必要なようです。そうしないと、出力はUTF-8マルチバイト文字などで壊れます。<meta charset="…">
mermshaus 16

1
LIBXML_HTML_NOIMPLIEDはまた、タブ、インデントや改行を除去することにより、HTMLコードを台無しに
ゾルタンスーレーパゴダ

72

loadHTML()でドキュメントを読み込んだ直後にノードを削除するだけです:

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

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

これは私に対するより明確な答えです。
KnF 2013年

39
これは、<body>に子ノードが1つしかない場合に機能することに注意してください。
Yann Milin

よく働きました。ありがとうございました!他のpregの回答よりもはるかにクリーンで高速です。
Ligemer 14

これありがとう!空のノードを処理するために下部に別の切り取りを追加しました。
redaxmedia 2014年

2
削除するコードは<!DOCTYPE 機能します。<body>子ノートが複数ある場合、2行目は改行されます。
フリーラジカル

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>を取得しています。
スコットB


2
saveXML()はHTMLではなくXHTMLを保存することに注意してください。
alexantd

@スコット:それは本当に奇妙です。これは、例のセクションで、まさにここで実行しようとしていることを示しています。DOMにそのHTMLがないことを確認しますか?DOMDocumentに含まれるHTMLは正確ですか?子ノードにアクセスする必要があるかもしれません。
ジョナ

@ジョナそれは奇妙ではありません。あなたが行うとloadHTMLのlibxmlの用途をHTMLパーサモジュールとそれが欠落しているHTMLスケルトンを挿入します。その結果、$dom->documentElementルートHTML要素になります。サンプルコードを修正しました。これで、スコットが求めていることを実行するはずです。
ゴードン

19

上の答えの問題は、それLIBXML_HTML_NOIMPLIEDが不安定であることです

要素の順序を変更したり(特に、一番上の要素の終了タグをドキュメントの一番下に移動したり)、ランダムpタグを追加したり、その他のさまざまな問題を追加したりできます[1]htmlbodyタグが削除される可能性がありますが、動作が不安定になります。本番環境では、これは危険信号です。要するに:

使用しないでください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文字(= 4 + 4 + 4)である12ため、ドキュメントの先頭から切り取り、= 15文字(= 1 + 2 + 4 + 4 + 4)であるため、後方に移動し、末尾から15を切り取ります<html><body><<>>+html+body\n</body></html>\n+//+<<>>+body+html

まだ含まれてLIBXML_HTML_NODEFDTDいる!DOCTYPEから省略を使用していることに注意してください。まず、これsubstrによりHTML / BODYタグの削除が簡単になります。次に、substrdefault doctype」が常に固定長になるかどうかがわからないため、Doctypeを削除しません。ただし、最も重要なのLIBXML_HTML_NODEFDTDは、DOMパーサーがHTML5以外のdoctypeをドキュメントに適用しないようにすることです。これにより、パーサーは、ルーズテキストとして認識しない要素を処理できなくなります。

私たちは、HTML / BODYタグは固定長さと位置であるという事実を知っている、と私たちは、定数が好きなことを知ってLIBXML_HTML_NODEFDTD、上記の方法を今後も展開する必要がありますので、廃止通知のいくつかの種類せずに削除されることはありません、しかし ...


...唯一の注意点は、DOM実装 HTML / BODYタグをドキュメント内に配置する方法を変更する可能性があることです。たとえば、ドキュメントの最後にある改行を削除したり、タグ間にスペースを追加したり、改行を追加したりします。

これは、の開始タグと終了タグの位置を検索し、bodyそれらのオフセットを使用して長さをトリミングすることで修正できます。とを使用strposstrrposて、それぞれ前面と背面からのオフセットを見つけます。

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

doctype、htmlタグ、bodyタグはありません。私たちは、DOMパーサーがすぐに新しいペイントを受け取ることを望み、これらの不要なタグをより直接的に排除できます。


すばらしい回答、1つの小さなコメント、繰り返しでは$html = $dom -> saveHTML();なくなぜ$dom -> saveHTML();ですか?
Steven、

15

きちんとしたトリックはloadXML、次に使用することsaveHTMLです。htmlそしてbodyタグがで挿入されているload段階ではなく、save段階。

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

これは少しハックであり、動作させることができる場合は、ジョナの答えを使用する必要があることに注意してください。


4
ただし、無効なHTMLの場合は失敗します。
Gordon、

1
@Gordonまさに私が免責事項を一番下に置いた理由!
lonesomeday

1
これを試して$ dom-> saveHTML()をエコーすると、空の文字列が返されます。loadXML($ content)が空であるかのように。$ dom-> loadHTML($ content)で同じことを行うと、$ dom-> saveXML()をエコーし​​ます。期待どおりにコンテンツを取得します。
スコットB

HTMlをロードしようとするときにloadXMLを使用するのは簡単です。特に、LoadXMLはHTMLの処理方法を知らないためです。
botenvouwer 2013年

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 14

これは、バージョンLibxml 2.7.7よりも古いバージョンでも新しいバージョンでも機能します。なぜこれはphp5.4より前のバージョンのみに該当するのですか?
RobbertT 2015

これはもっと投票すべきです。LIBXML_HTML_NOIMPLIEDをサポートしないlibxmlのバージョンに最適なオプション| LIBXML_HTML_NODEFDTD。ありがとう!
Marty Mulligan

13

それは2017年であり、この2011年の質問については私は答えが好きではありません。たくさんの正規表現、大きなクラス、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 );

自分でボディを追加して、変身を奇妙に防ぐことができます。

Thirtオプション:

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());

3
あなたはより高価なものを避け、mb_convert_encoding代わりにそれに応じて追加<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>および変更することにより、回答を改善する必要がありますsubstr。ところで、あなたのものはここで最もエレガントなソリューションです。賛成。
Hlsg 2018年

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は通常どおり機能しますが、ただ1つのルートノードではなく、複数のドキュメント要素があることに注意してください。

$xpath = new DOMXPath($doc);
foreach ($xpath->query('/p') as $element)
{   #                   ^- note the single slash "/"
    # ... each of the two <p> element

  • PHP 5.4.36-1 + deb.sury.org〜precise + 2(cli)(ビルド:2014年12月21日20:28:53)

より複雑なHTMLソースを使用している場合は機能しませんでした。また、HTMLの特定の部分を削除しました。
ゾルタンスーレーパゴダ

4

この記事の執筆時点(2012年6月)で他のソリューションはどれも私のニーズを完全に満たすことができなかったため、次のケースを処理するソリューションを作成しました。

  • タグのないプレーンテキストコンテンツと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ローダーを使用する必要があります。
2015

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
経由してこの記事に出くわしてきた一部の読者があるかもしれませんがこの投稿、自分のHTMLを解析し、代わりにDOMパーサを使用して、潜在的に完全なソリューション...皮肉を達成するために正規表現の答えを必要としてしまうために使用正規表現しないことを決定しました
ロビーアベリル14

noboyがBODYのコンテンツを返すだけの理由がわかりません。パーサーがドキュメントヘッダー/ Doctype全体を追加するときに、そのタグが常に存在すると想定されていませんか?上記の正規表現はさらに短くなります。
セルジオ2015

@boksiora「それでうまくいく」-では、そもそもなぜDOMパーサーメソッドを使用するのでしょうか。
2015

@naomik DOMパーサーを使用しないとは言っていませんが、同じ結果を得るにはさまざまな方法があります。あなた次第です。この関数を使用したとき、組み込みのphp domに問題がありましたhtml5を正しく解析していなかったパーサー。
boksiora

1
preg_replacehtmlおよびbodyタグを削除するDOMDocumentベースの方法を使用すると、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<body>コンテンツのルートであるタグを除いて、すべてのHTMLラップなしで完全に処理されたHTMLコードが含まれます。次に、正規表現またはトリム関数を使用して、それを最終文字列から削除し(後saveHTML)、または上記の場合と同様に、すべての子を反復処理してコンテンツを一時変数に保存し、$finalHtmlそれを返します(私が信じていること)より安全)。


3

PHP 5.6.25およびLibXML 2.9を実行しているRHEL7でこれに苦労しています。(2018年の古いものは知っていますが、それはRed Hatの皆さんです)。

Alessandro Vendruscoloによって提案された非常に支持されている解決策は、タグを再配置することによってHTMLを壊すことがわかりました。すなわち:

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

になる:

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

これは、彼はあなたが使用して示唆して両方のオプションのために行く:LIBXML_HTML_NOIMPLIEDLIBXML_HTML_NODEFDTD

アレックスによって提案された解決策はそれを解決するための半分の道を歩きますが、それ<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>、に変換<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の両方の正しいバージョンを持っていますが、それでもDocumentオブジェクトのノード構造に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構造を事前に決定する方法がありません。

私はこの旅を始めました。JQueryがDOMトラバーサルを行う簡単な方法、または少なくとも何らかの方法で構造化データセットが単一リンク、二重リンク、またはツリー化されたノードトラバーサルを行う方法を求めていました。HTMLのように文字列を解析でき、ノードエンティティクラスのプロパティがその途中で使用できるという驚異的な能力がある限り、私は気にしませんでした。

これまでのところ、DOMDocumentオブジェクトは私が欲しがっています...他の多くのプログラマーと同じように...私はこの質問で多くの不満を感じたので、私はついに...(約30時間の試行と失敗の後)タイプテスト)私はそれをすべて得る方法を見つけました。これが誰かに役立つことを願っています...

まず最初に、私はすべてのことを皮肉にしています...笑...

このユースケースではサードパーティのクラスが必要であることに誰もが同意するまで、私は一生かけていたでしょう。私はサードパーティのクラス構造を使用するのが大好きで、ファンではありませんでしたが、素晴らしいパーサーに出くわしました。(私が与える前にグーグルで約30回ですので、それが何らかの形で非公式のラメに見えたのでそれを避けたとしても孤独を感じないでください...)

コードフラグメントを使用していて、余分なタグを使用せずにコードがクリーンでパーサーの影響を受けない場合は、simplePHPParserを使用します

これは驚くべきことであり、JQueryのように機能します。感心することはあまりありませんが、このクラスは多くの優れたツールを使用しており、現時点で解析エラーは発生していません。私はこのクラスでできることをすることができるという大ファンです。

ダウンロードするファイルはこちら、起動手順はこちら、APIはこちらにあります。このクラスを.find(".className")、JQueryのfindメソッドを使用するのと同じ方法で実行できる単純なメソッド、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部分的にしか実行しないため、実質的にのジョブを実行しません。doctypeの削除は効果的LIBXML_HTML_NODEFDTDです。
2016

2

私はこのトピックに出くわし、HTMLラッパーを削除する方法を見つけました。使用する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は、一度に1つの質問を処理する場合に最適に機能します。ほんのヒントです。
2016

2

私は3つの問題に直面しています DOMDocumentクラスでます。

1-このクラスは、ISOエンコードと出力に表示されないutf-8文字を含むhtmlをロードします。

2-与えてもEvenLIBXML_HTML_NOIMPLIED、当社の入力HTMLはルートタグが含まれていないまで、それは正しく解析されません、loadHtmlメソッドに

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.ndを使用する前に、たとえば使用するmb_convert_encoding()の周りのヘルパー関数でDIVタグを使用します。私にとっては十分にうまくいきました。実際、DIVが存在しない場合は、段落が自動的に追加されます。私の段落には、通常、マージンが適用されているので不便です(ブートストラップ..)
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 2013年

公平を期すために、私は生のテキストでテストしていませんが、理論的には動作するはずです。特定のケースでは、xpathをのようなものに変更する必要があるかもしれませんdescendant-or-self::body/p/*
Nikola Petkanski 2013年

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

アレックスの答えは正しいですが、空のノードで次のエラーが発生する可能性があります。

DOMNode :: removeChild()に渡される引数1は、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;

空白を削除するには、trim()を追加することもお勧めします。


0

遅すぎるかもしれません。しかし、多分誰か(私のような)はまだこの問題を抱えています。
したがって、上記のどれも私にとってはうまくいきませんでした。$ dom-> loadHTMLはオープンタグも閉じるため、htmlタグとbodyタグを追加するだけではありません。
したがって、<div>要素を追加しても機能しません。HTMLのピースに3〜4個の閉じられていないdivが含まれることがあるからです。
私の解決策:

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。
私は本当に奇妙だと思います。HTMLDOMパーサーを使用し、この「もの」を修正するために正規表現を使用する必要があります...要点は、正規表現を使用しないことでした;)


ここであなたがすることは危険に見えます、stackoverflow.com / a / 29499718/367456があなたのために仕事をするはずです。
2016

残念ながら、これ(stackoverflow.com/questions/4879946/…)は私には機能しません。私が言ったように、「HTMLピースに閉じられていないdivが3〜4個ある場合があるため、<div>要素を追加しても機能しません。」何らかの理由で、DOMDocumentはすべての「閉じていない」要素を閉じたいと考えています。場合によっては、ショートコードまたはその他のマーカー内にフレグメントを取得し、そのフレグメントを削除して、ドキュメントの他の部分を操作したいと思います。それが終わったら、フレグメントを元に戻します。
Joe

代わりに独自のコンテンツを読み込んだ後、div要素を省略してbody要素を操作できるようにする必要があります。body要素は、フラグメントを読み込むときに暗黙的に追加する必要があります。
hakre 2017年

私の問題は、私のfregmentに閉じられていないタグが含まれていることです。それは閉じられないままであり、DOMDocumentはそれらの要素を閉じます。Fregmentのように:< div >< div > ... < /div >。私はまだ解決策を探しています。
Joe

うーん、divタグには常に終了ペアがあると思います。おそらくTidyはそれを処理でき、フラグメントも処理できます。
2017年

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

賛成。Drupal APIからこの関数を使用すると、私のDrupal 7サイトでは問題なく機能します。Drupalを使用していないユーザーは、関数を自分のサイトにコピーできます。これについてはDrupal固有のものは何もないためです。
フリーラジカル

0

tidyはshow-body-onlyで使用できます。

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

ただし、注意:Font Awesomeアイコンなどのタグを整頓してください:PHPでHTML(5)をインデントする際の問題


-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を共有するか。
ディランマクセイ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.