PHPのXPathをトリッキーに使ってHTMLのテキストノードを取得

以前、PHPXPathでテキストノード取得したら、期待した順番通りに取得できなかった。と書きました。
ある要素の子ノード群に、エレメントノードとテキストノードがそれぞれ2つ以上あると起きる現象ではないかと推測します。


直下じゃなければいいのでは?
ということで、ダミータグで囲ってみたら、期待した順番通りに取得できました。

PHPソース

<?php
$html = <<< EOL
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>
<html>
  <head>
    <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
    <title>test</title>
  </head>
  <body>
    <div>
      <a href="hoge.php">hoge</a> | <a href="huga.cgi">huga</a> | <a href="piyo.php">piyp</a>
    </div>
  </body>
</html>
EOL;
$data = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
$dom = new DOMDocument;
@$dom->loadHTML($data); // nameとidの値が被るとエラーになるので、@で抑制
$xpath = new DOMXpath($dom);

echo '<h2>通常</h2>';
$query = '/html/body//text()[string-length(normalize-space()) > 0]';
$text_nodes = $xpath->query($query);
foreach ($text_nodes as $n)
{
  var_dump($n->nodeValue);
}

// テキストノードをダミータグ"<z>"で囲む
$query = '/html/body//text()[string-length(normalize-space()) > 0]';
$text_nodes = $xpath->query($query);
foreach ($text_nodes as $n)
{
  $dummy_tag = $dom->createElement('z');
  $dummy_tag->nodeValue = $n->nodeValue;
  $n->parentNode->replaceChild($dummy_tag, $n);
}

echo '<h2>秘策</h2>';
$query = '/html/body//text()[string-length(normalize-space()) > 0]';
$text_nodes = $xpath->query($query);
foreach ($text_nodes as $n)
{
  var_dump($n->nodeValue);
}

吐かれた結果

通常

string ' | ' (length=3)

string 'hoge' (length=4)

string ' | ' (length=3)

string 'huga' (length=4)

string 'piyp' (length=4)

秘策

string 'hoge' (length=4)

string ' | ' (length=3)

string 'huga' (length=4)

string ' | ' (length=3)

string 'piyp' (length=4)


キタコレ!

ちなみに

tidyで整形して出力すれば、ダミータグは削除できます。

$tidy = tidy_parse_string($dom->saveHTML());
$html = tidy_get_html($tidy);
echo $html->value;


う〜ん冗長