Zend_Dom_Query

DOMDocumentを使うのにCSSセレクタが使えるというので、Server Side Dom Scriptingに使ってみた。
処理の流れとしては、

  1. XMLもしくはHTMLの文字列 をZend_Dom_Queryにセットする。
  2. CSSセレクタで検索して結果を取得する。
  3. 結果はZend_Dom_Query_Resultオブジェクト

Zend_Dom_Query_Resultオブジェクトは検索結果のノードリストと検索のベースになったDomDocumentオブジェクトを保持しており、Iteretorで結果リストに対する処理を行うことができる。

検索結果オブジェクトに対する処理については、Zend_Dom_Query_Resultオブジェクト内のDomDocumentオブジェクトには反映される。Zend_Dom_Query_Result::getDocumentはDomDocumentオブジェクトが返る。Zend_Dom_Query::getDocument()は元の文字列なので注意。DOMDocumentオブジェクトは検索の度にnewされるので、クエリと変更を複数回行うといった処理には向かない。

http://framework.zend.com/issues/browse/ZF-3950
ここに、ちょっとその辺のことが書いてある。

Zend_Dom_Queryはリードオンリーな雰囲気。

MVC アプリケーションの機能テストを支援するために作られたものですが、 スクリーンスクレイパーを手早く作成するためにも使うことができます。

http://framework.zend.com/manual/ja/zend.dom.query.html

DOMの経験値低め

これまで、DOMScriptingは避け気味で来ていたので、技術基盤が薄い。勉強の素材としては、DomDocumentを使っているらしいMoodleか、DOMライクなPHPTALがいいかな。

継続処理可能なDOMDocumentにした。

DOMDocumentを継承しているので、普通にDOMScriptingできる。特に難しいことはしていないので問題はないと思う。
ZFに還元したいところなんだけど、Proposalとかを書くのはちょっとしんどいか。
こういう場合、クラス名とか、勝手につけたけど、どういう手順にするのが正しいのか疑問・・・

<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Dom
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Dom_DOMDocument extends DOMDocument
{
    /**
     * Perform a CSS selector query
     *
     * @param  string $query
     * @return Zend_Dom_Query_Result
     */
    public function query($query)
    {
        $xpathQuery = Zend_Dom_Query_Css2Xpath::transform($query);
        return $this->queryXpath($xpathQuery, $query);
    }

    /**
     * Perform an XPath query
     *
     * @param  string $xpathQuery
     * @param  string $query CSS selector query
     * @return Zend_Dom_Query_Result
     */
    public function queryXpath($xpathQuery, $query = null)
    {
        $nodeList   = $this->_getNodeList($xpathQuery);
        return new Zend_Dom_Query_Result($query, $xpathQuery, $this, $nodeList);
    }

    /**
     * Prepare node list
     *
     * @param  DOMDocument $document
     * @param  string|array $xpathQuery
     * @return array
     */
    protected function _getNodeList($xpathQuery)
    {
        $xpath      = new DOMXPath($this);
        $xpathQuery = (string) $xpathQuery;
        if (preg_match_all('|\[contains\((@[a-z0-9_-]+),\s?\' |i', $xpathQuery, $matches)) {
            foreach ($matches[1] as $attribute) {
                $queryString = '//*[' . $attribute . ']';
                $attributeName = substr($attribute, 1);
                $nodes = $xpath->query($queryString);
                foreach ($nodes as $node) {
                    $attr = $node->attributes->getNamedItem($attributeName);
                    $attr->value = ' ' . $attr->value . ' ';
                }
            }
        }
        return $xpath->query($xpathQuery);
    }
}

使い方

<?php
$document = <<<EOB
<html>
<head><title>test</title></head>
<div>
<table>
    <tr>
        <td class="foo">
            <div>
                Lorem ipsum <span class="bar">
                    <a href="/foo/bar" id="one">One</a>
                    <a href="/foo/baz" id="two">Two</a>
                    <a href="/foo/bat" id="three">Three</a>
                    <a href="/foo/bla" id="four">Four</a>
                </span>
            </div>
        </td>
    </tr>
</table>
</div>
</html>

EOB;
require_once('Zend/Dom/DOMDocument.php');
$dom = new Zend_Dom_DOMDocument();
$dom->loadXML($document);

$results = $dom->query('.foo .bar a');
foreach ($results as $result) {
    // $result は DOMElement です
    /**
     * @var DOMElement $result
     */
    //検索結果にtest="hoge"をセットしています。
    $result->setAttribute('test', 'hoge');
}

Zend_Debug::dump($dom->saveXML());
Zend_Debug::dump($dom);