Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】Dom\HTMLDocument::compareDocumentPosition()メソッドの使い方

compareDocumentPositionメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

compareDocumentPositionメソッドは、ドキュメントツリー内の2つのノード間の相対的な位置関係を比較し、その結果を示す整数値を返すメソッドです。

このメソッドは、Dom\HTMLDocumentクラスのインスタンスから呼び出され、引数として比較したい別のノード(Dom\Nodeオブジェクト)を受け取ります。具体的には、メソッドが呼び出されたノードと引数で指定されたノードが、HTMLドキュメントの構造内でどちらが先行するか、後続するか、あるいは一方のノードがもう一方のノードを包含しているかといった関係性を評価します。

戻り値は整数値で、これは複数のビットフラグの組み合わせとして表現されます。これらのビットフラグは、例えばノードが同じドキュメントに属しているか、ドキュメント順序で先行しているか、後続しているか、親であるか、子であるか、といった複数の状態を同時に示します。プログラマーは、戻り値を特定の定数(例としてDOM_DOCUMENT_POSITION_FOLLOWINGなど)と比較することで、ノード間の詳細な位置関係を判別できます。

このメソッドは、複雑なDOM構造において、ノードの順序や階層関係を正確に把握し、それに基づいて処理を分岐させる必要がある場面で非常に有用です。

構文(syntax)

1<?php
2$htmlDocumentInstance = new Dom\HTMLDocument();
3$otherNodeInstance = $htmlDocumentInstance->createElement('div');
4$positionFlags = $htmlDocumentInstance->compareDocumentPosition($otherNodeInstance);
5?>

引数(parameters)

Dom\Node $other

  • Dom\Node $other: 比較対象となる Dom\Node オブジェクト

戻り値(return)

int

このメソッドは、2つのDOMノードの位置関係を示す整数値を返します。返される値はビットフラグとして解釈され、ノードが同じ文書に属しているか、一方のノードがもう一方のノードの祖先であるか、あるいは兄弟ノードであるかなどの情報を示します。

サンプルコード

PHP DomNodeの位置比較

1<?php
2
3// To run this code, ensure you have the 'dom' extension enabled in your PHP installation.
4// Note for PHP 8 beginners: The reference specifies 'Dom\HTMLDocument' and 'Dom\Node'.
5// In a standard PHP 8 environment, these classes are typically found in the global namespace
6// as 'DOMDocument' and 'DOMNode' respectively. If you encounter a 'class not found' error,
7// try replacing `use Dom\HTMLDocument;` with `use DOMDocument;` and `use Dom\Node;` with `use DOMNode;`.
8// This example uses the names provided in the reference for accuracy.
9
10use Dom\HTMLDocument; // As per the provided reference information
11use Dom\Node;         // As per the provided reference information
12
13/**
14 * Demonstrates the use of Dom\HTMLDocument::compareDocumentPosition to analyze
15 * the relative position of nodes within an HTML document.
16 *
17 * This method is valuable in system engineering contexts, especially within projects
18 * managed with Composer, where generated HTML (e.e.g, from tools like phpDocumentor)
19 * might need structural verification or manipulation. It helps determine if one
20 * HTML element precedes, follows, contains, or is contained by another, or if they
21 * are completely disconnected within the document structure.
22 *
23 * @return void
24 */
25function demonstrateNodePositionComparison(): void
26{
27    // 1. Create a new Dom\HTMLDocument instance.
28    // This is the starting point for parsing and manipulating HTML.
29    $document = new HTMLDocument();
30
31    // 2. Load some sample HTML content into the document.
32    // This HTML could represent a simplified snippet of documentation generated by phpDocumentor.
33    $html = <<<HTML
34<!DOCTYPE html>
35<html>
36<head><title>Sample Doc</title></head>
37<body>
38    <div id="main-content">
39        <h1>Welcome to API Documentation</h1>
40        <p>This section details the primary features.</p>
41        <h2>Getting Started</h2>
42    </div>
43    <div id="footer">
44        <p>Copyright 2023</p>
45    </div>
46</body>
47</html>
48HTML;
49    $document->loadHTML($html);
50
51    // 3. Identify specific nodes within the document to compare.
52    // We use common DOM traversal methods like getElementById and getElementsByTagName.
53    /** @var Node|null $mainContentDiv */
54    $mainContentDiv = $document->getElementById('main-content');
55    /** @var Node|null $h1 */
56    $h1             = $document->getElementsByTagName('h1')->item(0);
57    /** @var Node|null $pInsideMain */
58    $pInsideMain    = $document->getElementsByTagName('p')->item(0); // First paragraph, inside main-content
59    /** @var Node|null $h2 */
60    $h2             = $document->getElementsByTagName('h2')->item(0);
61    /** @var Node|null $footerDiv */
62    $footerDiv      = $document->getElementById('footer');
63    /** @var Node|null $pInFooter */
64    $pInFooter      = $document->getElementsByTagName('p')->item(1); // Second paragraph, inside footer
65
66    // Basic error checking if nodes are not found.
67    if (!$mainContentDiv || !$h1 || !$pInsideMain || !$h2 || !$footerDiv || !$pInFooter) {
68        echo "Error: Could not find all required nodes in the document.\n";
69        return;
70    }
71
72    echo "--- Demonstrating Dom\\HTMLDocument::compareDocumentPosition ---\n\n";
73
74    // Helper function to interpret and display the bitmask result of compareDocumentPosition.
75    $interpretPosition = function (int $result, Node $nodeA, Node $nodeB): void {
76        echo sprintf("  Comparing '%s' (Node A) with '%s' (Node B):\n", $nodeA->nodeName, $nodeB->nodeName);
77        echo sprintf("    Raw Result (Bitmask): %d\n", $result);
78
79        // A result of 0 means the nodes are the same.
80        if ($result === 0) {
81            echo "    - Node A and Node B are the same node.\n";
82            return; // No other flags should be set if they are the same
83        }
84
85        // Check for specific flags using bitwise AND operator.
86        // These constants are globally available in the 'dom' extension.
87        if ($result & DOM_DOCUMENT_POSITION_DISCONNECTED) {
88            echo "    - Nodes are disconnected (e.g., from different documents or not yet added).\n";
89        }
90        if ($result & DOM_DOCUMENT_POSITION_PRECEDING) {
91            echo "    - Node B precedes Node A in document order.\n";
92        }
93        if ($result & DOM_DOCUMENT_POSITION_FOLLOWING) {
94            echo "    - Node B follows Node A in document order.\n";
95        }
96        if ($result & DOM_DOCUMENT_POSITION_CONTAINS) {
97            echo "    - Node A contains Node B.\n";
98        }
99        if ($result & DOM_DOCUMENT_POSITION_CONTAINED_BY) {
100            echo "    - Node A is contained by Node B.\n";
101        }
102        if ($result & DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) {
103            echo "    - Implementation-specific position (rarely used for simple comparisons).\n";
104        }
105        echo "\n";
106    };
107
108    // Example 1: Comparing a parent node with its child node.
109    // mainContentDiv (Node A) contains h1 (Node B).
110    $result1 = $mainContentDiv->compareDocumentPosition($h1);
111    $interpretPosition($result1, $mainContentDiv, $h1);
112
113    // Example 2: Comparing a child node with its parent node.
114    // h1 (Node A) is contained by mainContentDiv (Node B).
115    $result2 = $h1->compareDocumentPosition($mainContentDiv);
116    $interpretPosition($result2, $h1, $mainContentDiv);
117
118    // Example 3: Comparing two sibling nodes.
119    // pInsideMain (Node B) follows h1 (Node A).
120    $result3 = $h1->compareDocumentPosition($pInsideMain);
121    $interpretPosition($result3, $h1, $pInsideMain);
122
123    // Example 4: Comparing two sibling nodes in reverse order.
124    // h1 (Node B) precedes pInsideMain (Node A).
125    $result4 = $pInsideMain->compareDocumentPosition($h1);
126    $interpretPosition($result4, $pInsideMain, $h1);
127
128    // Example 5: Comparing nodes from different top-level sections of the body.
129    // footerDiv (Node B) follows mainContentDiv (Node A).
130    $result5 = $mainContentDiv->compareDocumentPosition($footerDiv);
131    $interpretPosition($result5, $mainContentDiv, $footerDiv);
132
133    // Example 6: Comparing a node with another node that is further down but not a sibling or direct child.
134    // pInFooter (Node B) follows h1 (Node A).
135    $result6 = $h1->compareDocumentPosition($pInFooter);
136    $interpretPosition($result6, $h1, $pInFooter);
137
138    // Example 7: Comparing a node against itself.
139    // The result should be 0, indicating they are the same node.
140    $result7 = $h1->compareDocumentPosition($h1);
141    $interpretPosition($result7, $h1, $h1);
142
143    // Example 8: Comparing a node to a newly created node not yet inserted into the document.
144    $disconnectedNode = $document->createElement('span');
145    $result8 = $h1->compareDocumentPosition($disconnectedNode);
146    $interpretPosition($result8, $h1, $disconnectedNode);
147}
148
149// Execute the demonstration function to see the output.
150demonstrateNodePositionComparison();

PHP 8のDom\HTMLDocument::compareDocumentPositionメソッドは、HTMLドキュメント内にある二つのノードが互いにどのような位置関係にあるかを数値で比較する機能を提供します。このメソッドは、引数として比較対象のノードをDom\Node $otherとして受け取ります。戻り値はint型のビットマスクで、この数値から、二つのノードが同じであるか、どちらが先に現れるか、どちらがもう一方を包含しているか、あるいは完全に独立しているかといった複数の状態を同時に読み取ることが可能です。例えば、DOM_DOCUMENT_POSITION_FOLLOWINGDOM_DOCUMENT_POSITION_CONTAINSなどの定数とビット演算子を使うことで、詳細な位置関係を判定できます。

この機能は、システム開発、特にComposerで管理されるプロジェクトやphpDocumentorで生成されるようなHTMLの構造をプログラム的に検証・操作する際に非常に有用です。サンプルコードでは、まずHTMLドキュメントを作成し、main-contenth1pfooterといった特定のノードを抽出しています。そして、親と子、兄弟要素、異なるセクションの要素、さらにはまだドキュメントに挿入されていないノードなど、様々な組み合わせでこれらのノード間の位置関係を比較しています。例えば、親ノードが子ノードを「包含する」関係や、ある要素が別の要素に「後続する」関係、またノードがドキュメントから「切断されている」状態などが、それぞれの比較結果として数値と解釈文で具体的に示されます。これにより、HTML構造の詳細な分析や、その後の動的な変更処理に役立てることができます。

このサンプルコードをPHP 8で実行するには、まずdom拡張が有効になっていることをご確認ください。リファレンスではDom\HTMLDocumentDom\Nodeと記載されていますが、通常のPHP環境ではDOMDocumentDOMNodeとして利用されることが多いため、もしクラスが見つからないエラーが出た場合は、use文やインスタンス生成時のクラス名を調整してください。compareDocumentPositionメソッドの戻り値はビットマスクのため、各定数(DOM_DOCUMENT_POSITION_など)を用いて、ビット論理積で意味を正しく解釈する必要があります。また、getElementByIdなどでノードを取得する際は、要素が見つからない場合にnullが返されることがありますので、必ず操作前に存在チェックを行ってください。

PHP Dom\HTMLDocument::compareDocumentPosition の使用例

1<?php
2
3/**
4 * Dom\HTMLDocument::compareDocumentPosition メソッドの使用例を解説する関数です。
5 *
6 * この関数は、HTML文字列からDOMドキュメントを構築し、異なるノード間の位置関係を
7 * compareDocumentPosition メソッドを使って比較します。
8 * 結果はビットマスクとして返されるため、その解釈方法も示します。
9 * システムエンジニアを目指す方にとって、DOM操作の基礎とノードの相対位置を
10 * 理解するのに役立ちます。
11 *
12 * @param string $htmlString 比較に使用するHTMLコンテンツ文字列。
13 * @return void 出力は直接コンソールに行われます。
14 *
15 * @see https://www.php.net/manual/ja/dom-htmldocument.comparedocumentposition.php
16 *      Dom\HTMLDocument::compareDocumentPosition の公式リファレンス
17 */
18function demonstrateNodeComparison(string $htmlString): void
19{
20    // 新しい HTMLDocument インスタンスを作成
21    $document = new Dom\HTMLDocument();
22    // HTML 文字列を読み込み、DOM ツリーを構築
23    @$document->loadHTML($htmlString); // エラー抑制は実運用では避けるべきですが、例では簡潔さのため
24
25    // 比較対象となるDOMノードを複数取得
26    // getElementById は null を返す可能性があるため、比較前にチェックが必要です。
27    $divNode = $document->getElementById('container');
28    $firstPNode = $document->getElementById('first');
29    $secondSpanNode = $document->getElementById('second');
30    $thirdPNode = $document->getElementById('third');
31
32    /**
33     * compareDocumentPosition から返されるビットマスク結果を人間が読める形式に変換するヘルパー関数です。
34     *
35     * @param int $result compareDocumentPosition の戻り値。
36     * @return string 関係性を説明する文字列。
37     */
38    $interpretResult = function (int $result): string {
39        $messages = [];
40        if ($result === 0) {
41            return '同一ノード、または位置関係のフラグなし (通常は同一ノードの場合)';
42        }
43        if (($result & DOM_DOCUMENT_POSITION_DISCONNECTED) === DOM_DOCUMENT_POSITION_DISCONNECTED) {
44            $messages[] = 'DISCONNECTED (接続されていない)';
45        }
46        if (($result & DOM_DOCUMENT_POSITION_PRECEDING) === DOM_DOCUMENT_POSITION_PRECEDING) {
47            $messages[] = 'PRECEDING (引数ノードよりも前に位置する)';
48        }
49        if (($result & DOM_DOCUMENT_POSITION_FOLLOWING) === DOM_DOCUMENT_POSITION_FOLLOWING) {
50            $messages[] = 'FOLLOWING (引数ノードよりも後に位置する)';
51        }
52        if (($result & DOM_DOCUMENT_POSITION_CONTAINS) === DOM_DOCUMENT_POSITION_CONTAINS) {
53            $messages[] = 'CONTAINS (引数ノードを内包する)';
54        }
55        if (($result & DOM_DOCUMENT_POSITION_CONTAINED_BY) === DOM_DOCUMENT_POSITION_CONTAINED_BY) {
56            $messages[] = 'CONTAINED_BY (引数ノードに内包される)';
57        }
58        // DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC は通常、一般的な比較では考慮されません。
59        // if (($result & DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) === DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) {
60        //     $messages[] = 'IMPLEMENTATION_SPECIFIC';
61        // }
62
63        return implode(' | ', $messages);
64    };
65
66    echo "--- Dom\\HTMLDocument::compareDocumentPosition の使用例 ---\n\n";
67
68    // 例 1: 兄弟ノード間の比較 (firstPNode は secondSpanNode の前に位置する)
69    // 期待値: FOLLOWING
70    if ($firstPNode && $secondSpanNode) {
71        $result = $firstPNode->compareDocumentPosition($secondSpanNode);
72        echo "ノード比較: 'firstPNode' (id='first') と 'secondSpanNode' (id='second')\n";
73        echo "  戻り値 (ビットマスク): " . $result . "\n";
74        echo "  解釈: " . $interpretResult($result) . "\n\n";
75    }
76
77    // 例 2: 兄弟ノード間の逆比較 (secondSpanNode は firstPNode の後に位置する)
78    // 期待値: PRECEDING
79    if ($secondSpanNode && $firstPNode) {
80        $result = $secondSpanNode->compareDocumentPosition($firstPNode);
81        echo "ノード比較: 'secondSpanNode' (id='second') と 'firstPNode' (id='first')\n";
82        echo "  戻り値 (ビットマスク): " . $result . "\n";
83        echo "  解釈: " . $interpretResult($result) . "\n\n";
84    }
85
86    // 例 3: 子ノードから親ノードへの比較 (firstPNode は divNode に内包される)
87    // 期待値: CONTAINED_BY | FOLLOWING
88    if ($firstPNode && $divNode) {
89        $result = $firstPNode->compareDocumentPosition($divNode);
90        echo "ノード比較: 'firstPNode' (id='first') と 'divNode' (id='container')\n";
91        echo "  戻り値 (ビットマスク): " . $result . "\n";
92        echo "  解釈: " . $interpretResult($result) . "\n\n";
93    }
94
95    // 例 4: 親ノードから子ノードへの比較 (divNode は firstPNode を内包する)
96    // 期待値: CONTAINS | PRECEDING
97    if ($divNode && $firstPNode) {
98        $result = $divNode->compareDocumentPosition($firstPNode);
99        echo "ノード比較: 'divNode' (id='container') と 'firstPNode' (id='first')\n";
100        echo "  戻り値 (ビットマスク): " . $result . "\n";
101        echo "  解釈: " . $interpretResult($result) . "\n\n";
102    }
103
104    // 例 5: 異なる親を持つノード間の比較 (firstPNode と thirdPNode は接続されていないが、firstPNode は前に位置する)
105    // 期待値: DISCONNECTED | FOLLOWING
106    if ($firstPNode && $thirdPNode) {
107        $result = $firstPNode->compareDocumentPosition($thirdPNode);
108        echo "ノード比較: 'firstPNode' (id='first') と 'thirdPNode' (id='third')\n";
109        echo "  戻り値 (ビットマスク): " . $result . "\n";
110        echo "  解釈: " . $interpretResult($result) . "\n\n";
111    }
112
113    // 例 6: ノードとそれ自身の比較 (同一ノード)
114    // 期待値: 0
115    if ($firstPNode) {
116        $result = $firstPNode->compareDocumentPosition($firstPNode);
117        echo "ノード比較: 'firstPNode' (id='first') とそれ自身\n";
118        echo "  戻り値 (ビットマスク): " . $result . "\n";
119        echo "  解釈: " . $interpretResult($result) . "\n\n";
120    }
121}
122
123// デモンストレーション用のHTMLコンテンツ
124$htmlContent = <<<HTML
125<!DOCTYPE html>
126<html>
127<head>
128    <title>DOM比較テスト</title>
129</head>
130<body>
131    <div id="container">
132        <p id="first">これは最初の段落です。</p>
133        <span id="second">これはスパン要素です。</span>
134    </div>
135    <p id="third">これは三番目の段落で、divの外にあります。</p>
136</body>
137</html>
138HTML;
139
140// デモンストレーション関数を実行
141demonstrateNodeComparison($htmlContent);
142
143?>

「Dom\HTMLDocument::compareDocumentPosition」メソッドは、現在のノードと引数で指定した別のノードとの文書上の相対的な位置関係を比較し、その結果を数値(ビットマスク)で返す機能です。引数には比較対象となる「Dom\Node」オブジェクトを指定します。戻り値の整数値は、ノード間の複数の関係性を表す定数(例: DOM_DOCUMENT_POSITION_FOLLOWING, DOM_DOCUMENT_POSITION_CONTAINSなど)を組み合わせたビットマスクとして提供され、これによってノードが前方にあるか、後方にあるか、内包しているか、内包されているかといった詳細な情報を同時に判別できます。

このサンプルコードでは、HTML文字列からDOMドキュメントを生成し、そこから特定のIDを持つ複数のノードを取得しています。そして、「compareDocumentPosition」メソッドを利用して、兄弟ノード、親子ノード、さらには異なる親を持つノード同士など、様々な組み合わせでの位置関係を具体的な例として示しています。メソッドから返されるビットマスクは、コード内に定義されたヘルパー関数によって「PRECEDING(前方)」「CONTAINS(内包している)」といった分かりやすい関係性を示す文字列に変換され、比較結果が解釈しやすく表示されます。システムエンジニアを目指す方にとって、ウェブコンテンツの構造をプログラムで扱うDOM操作の基礎と、ノード間の正確な位置関係を理解するための良い手助けとなるでしょう。

compareDocumentPositionメソッドの戻り値は、ビットマスクと呼ばれる特殊な数値で、複数の位置関係が組み合わされています。このため、専用の定数とビット論理AND演算子(&)を使い、一つずつ状態を判定する必要があります。単純な数値比較では意図しない結果になるため注意してください。また、getElementByIdなどでノードを取得する際は、指定IDの要素が存在しないとnullが返る可能性があります。メソッドを呼び出す前には、必ずノードの存在を確認する習慣をつけましょう。サンプルコードのエラー抑制演算子@は、エラー原因の特定を妨げるため、実運用では使用を避けるべきです。

関連コンテンツ

関連IT用語

関連プログラミング言語