10

<p>タグ内にラップする必要があるすべてのルートレベルの #text ノード (または div の親を持つノード) を見つけたいと思います。次のテキストには、3 つ (または 2 つだけ) の最終ルート<p>タグが必要です。

<div>
    This text should be wrapped in a p tag.
</div>

This also should be wrapped.

<b>And</b> this.

アイデアは、HTML 表示用にテキスト ブロックが段落にグループ化されるように、テキストを適切にフォーマットすることです。ただし、私が取り組んでいる次のxpathは、テキストノードの選択に失敗しているようです。

    <?php

$html = '<div>
    This text should be wrapped in a p tag.
</div>

This also should be wrapped.

<b>And</b> this.';

libxml_use_internal_errors(TRUE);

$dom = DOMDocument::loadHTML($html);

$xp = new DOMXPath($dom);

$xpath = '//text()[not(parent::p) and normalize-space()]';

foreach($xp->query($xpath) as $node) {
    $element = $dom->createElement('p');
    $node->parentNode->replaceChild($element, $node);
    $element->appendChild($node);
}

print $dom->saveHTML();
4

4 に答える 4

8

わかりましたので、答えとして私のコメントを言い換えさせてください。すべてのテキスト ノードに一致させたい場合は、単純//divに XPath 式からその部分を削除する必要があります。したがって、次のようになります。

//text()[not(parent::p) and normalize-space()]
于 2013-03-30T09:55:21.317 に答える
2

あなたのシナリオには多くのエッジケースがあり、言葉一番上に追加されています。クラシックをやりたいと思っていると思いますが、今回は親(または他のブロック要素)内でも同様に、ダブルブレークで新しい段落を開始します。<div>

ほとんどの作業は HTML パーサーに任せますが、それでもテキスト検索と置換 (xpath の隣) を使用します。したがって、今後表示されるのは少しハックですが、かなり安定していると思います。

まず、上記の div の最上位または子であるすべてのテキスト ノードを選択します。

(.|./div)/text()

この xpath は、 にロードされたときに HTML フラグメントのルート タグを表すタグであるアンカー要素に相対的です。<body>DOMDocument

div の子の場合、開始段落を最初に挿入します。

次に、いずれにせよ、新しい段落を開始するシーケンスが出現するたびにブレークマーク(ここではコメントの形式で)を挿入します("\n\n"これは空白の正規化のためであるはずです。間違っている可能性があり、それが適用されない場合、これを透過的に機能させるには、事前に空白の正規化を行う必要があります)。

/* @var $result DOMText[] */
$result = $xp->query('(.|./div)/text()', $anchor);

foreach ($result as $i => $node)
{
    if ($node->parentNode->tagName == 'div')
    {
        $insertBreakMarkBefore($node, true);
    }

    while (FALSE !== $pos = strpos($node->data, $paragraphSequence))
    {
        $node = $node->splitText($pos + $paragraphSequenceLength);
        $insertBreakMarkBefore($node);
    }
}

<p>これらの挿入されたブレーク マークは、HTMLタグに置き換えられるだけです。HTML パーサーはそれらを適切な<p>...</p>ペアに変換するので、そのアルゴリズムを書く手間を省くことができます (これは興味深いかもしれませんが)。これは基本的に、他の回答で概説したように機能しますが、リンクが見つかりません。

  1. DOM ツリーの変更後、再度内部 HTML を取得します<body>
  2. セット マークを次のように置き換えます"<p>"(ここでは、クラスにもマークを付けて、これを表示します)
  3. <p>...</p>HTML フラグメントをパーサーに再度ロードして、適切なペアで DOM を再作成します。
  4. DOMDocumentパーサーから HTML を再度取得します。

これらは、コード内の手順の概要を示しています (関数定義の一部を少しスキップします)。

$needle  = sprintf('%1$s<!--%2$s-->%1$s', $paragraphSequence, $paragraphComment);
$replace = sprintf("\n<p class=\"%s\">\n", $paragraphComment);
$html    = strtr($innerHTML($anchor), array($needle . $needle => $replace, $needle => $replace));

echo "HTML afterwards:\n", $innerHTML($loadHTMLFragment($html));

これが示すように、2 つのシーケンスが 1 つのシーケンスに置き換えられます。おそらく最後の 1 つも削除する必要があります (該当する場合は、ここで空白を削除することもできます)。

最終的な HTML 出力:

<div>
<p class="break">

    This text should be wrapped in a p tag.
</p>
</div>
<p class="break">
This also should be wrapped.
</p>
<p class="break">
<b>And</b> this.</p>

適切な出力フォーマットを作成するためのポストプロダクションも役立ちます。実際には、アルゴリズムを微調整するのに役立つので、それを行う価値があると思います(完全なデモ-見ただけで、空白の正規化はおそらくそこには適用されないため、注意して使用してください)。

于 2013-08-11T21:21:57.380 に答える
1

必要に応じて、純粋な JavaScript で実行できます。

var content = document.evaluate(
                                      '//text()', 
                                      document, 
                                      null, 
                                      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, 
                                      null );

for ( var i=0 ; i < content .snapshotLength; i++ ){
  console.log( content .snapshotItem(i).textContent );
}
于 2013-08-07T10:04:34.243 に答える