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

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

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

作成日: 更新日:

基本的な使い方

compareDocumentPositionメソッドは、現在のDOMCharacterDataノードと、引数で指定された別のノードとのドキュメント内での位置関係を比較し、その結果をビットマスクとして返すメソッドです。このメソッドは、DOMツリー構造において、あるノードが別のノードより前にあるか、後にあるか、あるいは親子関係(祖先や子孫)にあるかといった複雑な位置関係を正確に判定するために使用されます。戻り値は整数で、複数の状態を同時に表現するビットマスクとなっています。例えば、定数DOMNode::DOCUMENT_POSITION_FOLLOWINGは指定したノードが後方にあることを示し、DOMNode::DOCUMENT_POSITION_CONTAINED_BYは指定したノードが祖先であることを示します。これらの定義済み定数と戻り値をビット単位のAND演算子(&)で比較することで、特定の位置関係を満たしているかどうかを判定できます。この機能により、DOM操作において、ノード間の相対的な位置に基づいた精密な処理を実装することが可能になります。

構文(syntax)

1$document = new DOMDocument();
2$document->loadXML('<root>text<!--comment--></root>');
3
4$nodeA = $document->documentElement->firstChild;
5$nodeB = $document->documentElement->lastChild;
6
7$comparisonResult = $nodeA->compareDocumentPosition($nodeB);

引数(parameters)

DOMNode $other

  • DOMNode $other: 比較対象となる別のDOMNodeオブジェクト

戻り値(return)

int

このメソッドは、2つのDOMノード間の文書内での相対的な位置関係を示す整数を返します。返される整数は、ビットマスクとして解釈され、ノードの包含関係や兄弟順序などの情報を示します。

サンプルコード

PHP 8 DOMノード位置比較

1<?php
2
3/**
4 * This file demonstrates the use of DOMCharacterData::compareDocumentPosition in PHP 8.
5 *
6 * This example is designed for system engineer beginners. It shows how to
7 * compare the relative position of two DOM nodes within a document.
8 *
9 * While this specific functionality does not require Composer (PHP's dependency manager),
10 * it is a standard tool for managing dependencies in modern PHP projects.
11 * PHPDoc comments, as used here, are essential for tools like phpDocumentor
12 * to generate API documentation.
13 */
14
15/**
16 * Compares the document position of a DOMCharacterData node with another DOMNode
17 * and prints an interpretation of the result.
18 *
19 * This function helps to understand how `DOMCharacterData::compareDocumentPosition`
20 * works by determining if a node precedes, follows, contains, or is contained by another.
21 *
22 * @param DOMCharacterData $node1 The reference node, which must be an instance of DOMCharacterData
23 *                                (e.g., DOMText, DOMComment). This is the node on which the method is called.
24 * @param DOMNode          $node2 The node to compare against (the 'other' node).
25 * @param string           $description A brief description of the comparison scenario for clarity.
26 * @return void
27 */
28function demonstrateCompareDocumentPosition(DOMCharacterData $node1, DOMNode $node2, string $description): void
29{
30    echo "--- {$description} ---\n";
31    echo "Comparing reference node '{$node1->nodeName}' (value: '" . substr($node1->nodeValue, 0, 30) . "...')\n" .
32         "with other node '{$node2->nodeName}' (value: '" . substr($node2->nodeValue, 0, 30) . "...')\n";
33
34    // Call the compareDocumentPosition method on the DOMCharacterData instance.
35    $position = $node1->compareDocumentPosition($node2);
36
37    echo "Raw result (bitmask): {$position}\n";
38    echo "Interpretation:\n";
39
40    // Interpret the bitmask result using DOM_DOCUMENT_POSITION_* constants.
41    // A result of 0 means the nodes are identical.
42    if ($position === 0) {
43        echo "  - Both nodes are the same.\n";
44    } else {
45        if ($position & DOM_DOCUMENT_POSITION_DISCONNECTED) {
46            echo "  - The nodes are disconnected (not in the same document or not in the same subtree accessible from a common ancestor).\n";
47        }
48        if ($position & DOM_DOCUMENT_POSITION_PRECEDING) {
49            echo "  - The reference node ('{$node1->nodeName}') precedes the other node ('{$node2->nodeName}') in document order.\n";
50        }
51        if ($position & DOM_DOCUMENT_POSITION_FOLLOWING) {
52            echo "  - The reference node ('{$node1->nodeName}') follows the other node ('{$node2->nodeName}') in document order.\n";
53        }
54        if ($position & DOM_DOCUMENT_POSITION_CONTAINS) {
55            // Note: A DOMCharacterData node (like DOMText or DOMComment) cannot contain other nodes.
56            // This flag would typically be set if the *calling* node was an element that contained $node2.
57            echo "  - The reference node ('{$node1->nodeName}') contains the other node ('{$node2->nodeName}') as an ancestor.\n";
58        }
59        if ($position & DOM_DOCUMENT_POSITION_CONTAINED_BY) {
60            echo "  - The reference node ('{$node1->nodeName}') is contained by the other node ('{$node2->nodeName}') as a descendant.\n";
61        }
62        if ($position & DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) {
63            echo "  - The comparison involves an implementation-specific position, which might indicate vendor-specific behavior.\n";
64        }
65    }
66    echo "\n";
67}
68
69// 1. Create a DOMDocument instance and load some HTML content.
70$dom = new DOMDocument('1.0', 'UTF-8');
71// Suppress warnings for potentially malformed HTML in simple examples.
72libxml_use_internal_errors(true);
73$dom->loadHTML('
74<!DOCTYPE html>
75<html>
76<head><title>DOM Position Test</title></head>
77<body>
78    <div id="container">
79        <h1>Welcome</h1>
80        <p id="first-para">This is the <span>first</span> paragraph.</p>
81        <!-- A simple comment for testing -->
82        <span id="second-span">And this is the second element.</span>
83    </div>
84    <div id="footer">
85        <p>Copyright &copy; 2023</p>
86    </div>
87</body>
88</html>
89');
90libxml_clear_errors(); // Clear any suppressed errors.
91
92// 2. Identify and retrieve various DOM nodes for comparison.
93
94// Get the text node from the first paragraph. This is a DOMText instance, which extends DOMCharacterData.
95$firstParagraph = $dom->getElementById('first-para');
96$pTextNode = null;
97if ($firstParagraph) {
98    // We are looking for the text node "This is the "
99    foreach ($firstParagraph->childNodes as $child) {
100        if ($child instanceof DOMText && trim($child->nodeValue) !== '') {
101            $pTextNode = $child;
102            break;
103        }
104    }
105}
106if ($pTextNode === null) {
107    echo "Error: Could not find the expected text node in the first paragraph.\n";
108    exit(1);
109}
110
111// Get the text node from the span inside the first paragraph.
112$spanInParagraph = $firstParagraph->getElementsByTagName('span')->item(0);
113$spanTextNode = null;
114if ($spanInParagraph) {
115    foreach ($spanInParagraph->childNodes as $child) {
116        if ($child instanceof DOMText && trim($child->nodeValue) !== '') {
117            $spanTextNode = $child; // "first"
118            break;
119        }
120    }
121}
122if ($spanTextNode === null) {
123    echo "Error: Could not find the expected text node in the inner span.\n";
124    exit(1);
125}
126
127// Get the entire 'container' div element. This is a DOMElement instance, which extends DOMNode.
128$containerElement = $dom->getElementById('container');
129if ($containerElement === null) {
130    echo "Error: Could not find the 'container' element.\n";
131    exit(1);
132}
133
134// Get the text node from the 'second-span' element.
135$secondSpanElement = $dom->getElementById('second-span');
136$secondSpanTextNode = null;
137if ($secondSpanElement) {
138    foreach ($secondSpanElement->childNodes as $child) {
139        if ($child instanceof DOMText && trim($child->nodeValue) !== '') {
140            $secondSpanTextNode = $child; // "And this is the second element."
141            break;
142        }
143    }
144}
145if ($secondSpanTextNode === null) {
146    echo "Error: Could not find the expected text node in the second span.\n";
147    exit(1);
148}
149
150// Get a comment node. This is a DOMComment instance, which also extends DOMCharacterData.
151$commentNode = null;
152foreach ($dom->getElementsByTagName('body')->item(0)->childNodes as $child) {
153    if ($child instanceof DOMComment) {
154        $commentNode = $child;
155        break;
156    }
157}
158if ($commentNode === null) {
159    echo "Error: Could not find a comment node.\n";
160    exit(1);
161}
162
163// 3. Demonstrate various comparison scenarios using the 'demonstrateCompareDocumentPosition' function.
164
165// Scenario 1: Node is contained by an ancestor element.
166demonstrateCompareDocumentPosition(
167    $pTextNode,
168    $containerElement,
169    '1. Text node ($pTextNode) is contained by its ancestor ($containerElement)'
170);
171
172// Scenario 2: Inner text node contained by its immediate parent element.
173demonstrateCompareDocumentPosition(
174    $spanTextNode,
175    $spanInParagraph,
176    '2. Inner text node ($spanTextNode) contained by its immediate parent ($spanInParagraph)'
177);
178
179// Scenario 3: A node precedes another node in document order.
180demonstrateCompareDocumentPosition(
181    $pTextNode,
182    $secondSpanTextNode,
183    '3. First paragraph text node ($pTextNode) precedes second span text node ($secondSpanTextNode)'
184);
185
186// Scenario 4: A node follows another node in document order.
187demonstrateCompareDocumentPosition(
188    $secondSpanTextNode,
189    $pTextNode,
190    '4. Second span text node ($secondSpanTextNode) follows first paragraph text node ($pTextNode)'
191);
192
193// Scenario 5: Comparison involving a comment node.
194demonstrateCompareDocumentPosition(
195    $commentNode,
196    $secondSpanTextNode,
197    '5. Comment node ($commentNode) precedes second span text node ($secondSpanTextNode)'
198);
199
200// Scenario 6: Disconnected nodes (a node not part of the current DOM tree).
201$disconnectedTextNode = $dom->createTextNode('This node is not connected to the document.');
202demonstrateCompareDocumentPosition(
203    $pTextNode,
204    $disconnectedTextNode,
205    '6. Connected node ($pTextNode) with a newly created, disconnected node ($disconnectedTextNode)'
206);
207
208// Scenario 7: Comparing a node with itself (should result in 0).
209demonstrateCompareDocumentPosition(
210    $pTextNode,
211    $pTextNode,
212    '7. Comparing a node with itself ($pTextNode)'
213);
214
215?>

このPHPコードは、DOMCharacterDataクラスが提供するcompareDocumentPositionメソッドの利用方法を、システムエンジニアを目指す初心者向けに解説しています。このメソッドは、XMLやHTMLなどのDOMドキュメント内で、2つのDOMノードが互いに対してどのような相対的な位置関係にあるかを数値として比較し、その結果を返します。

compareDocumentPositionメソッドは、呼び出し元のDOMCharacterDataインスタンス(例えば、テキストノードを表すDOMTextやコメントノードを表すDOMCommentなど)と、引数として渡される比較対象のDOMNode $otherの位置関係を調べます。戻り値は整数型で、ノードが文書内で先行するか、後続するか、含まれるか、あるいは互いに切断されているかといった複数の状態を示すビットマスクです。このビットマスクは、DOM_DOCUMENT_POSITION_PRECEDINGDOM_DOCUMENT_POSITION_FOLLOWINGといったPHPの標準定数とビット演算子&を用いて解釈します。もし戻り値が0であれば、2つのノードが完全に同一であることを意味します。

サンプルコードでは、まずHTMLドキュメントを作成し、そこから様々な種類のノードを取得しています。その後、これらのノードの組み合わせに対してcompareDocumentPositionメソッドを適用し、返されたビットマスクの値を詳細に解釈して、人間が理解しやすい形で出力しています。これにより、ノードが先行する場合、後続する場合、あるいは文書内で切断されている場合など、多様なシナリオにおけるメソッドの具体的な挙動を学習できます。なお、現代のPHP開発ではComposerによる依存管理やphpDocumentorによるドキュメント生成が一般的ですが、このメソッド自体はPHPの標準機能として提供されています。

DOMCharacterData::compareDocumentPositionメソッドの戻り値は、複数の状態を同時に示すビットマスクです。単なる数値比較ではなく、DOM_DOCUMENT_POSITION_*定数とビット演算子&を用いて、それぞれの状態を個別に判断する必要があります。このメソッドを呼び出すDOMCharacterData(DOMTextやDOMCommentなど)は通常子ノードを持たないため、自身が他のノードを含んでいることを示すDOM_DOCUMENT_POSITION_CONTAINSフラグが立つことは稀です。比較対象のノードは、getElementByIdなどで取得後、必ずnullチェックを行い、存在を確認してから使用してください。また、比較するノードが同じDOMツリーに属していない場合は、DOM_DOCUMENT_POSITION_DISCONNECTEDフラグが立ちます。PHP開発では、Composerで依存関係を管理し、phpDocumentorを用いたPHPDocコメントでコードの可読性と保守性を高めることが推奨されます。

PHP DOMノード位置関係比較とデコード

1<?php
2
3/**
4 * DOMNode::compareDocumentPosition メソッドの戻り値であるビットマスクを、
5 * 人間が読める形式の文字列に変換します。
6 *
7 * この関数は、phpdocumentor のオプションを意識し、適切なPHPDocコメントで記述されています。
8 *
9 * @param int $positionFlags compareDocumentPosition の戻り値(ビットマスク)
10 * @return string ノードの位置関係を表す文字列
11 * @link https://www.php.net/manual/ja/domnode.comparedocumentposition.php PHP Manual (compareDocumentPosition)
12 * @link https://www.w3.org/TR/dom/#dom-node-comparedocumentposition W3C DOM 仕様
13 */
14function decodeDocumentPosition(int $positionFlags): string
15{
16    $result = [];
17
18    // DOM_DOCUMENT_POSITION_SAME_NODE (0x20)
19    // 比較対象のノードが同じノードである場合、このフラグが単独で返されます。
20    if ($positionFlags === DOM_DOCUMENT_POSITION_SAME_NODE) {
21        return '同じノード';
22    }
23
24    // 他のフラグは組み合わされて返される可能性があります。
25    // ビット演算子 '&' を使用して、各フラグが存在するかどうかをチェックします。
26
27    // DOM_DOCUMENT_POSITION_DISCONNECTED (0x01)
28    if (($positionFlags & DOM_DOCUMENT_POSITION_DISCONNECTED) === DOM_DOCUMENT_POSITION_DISCONNECTED) {
29        $result[] = '切断されている (異なるツリーに存在するか、位置関係が不明)';
30    }
31
32    // DOM_DOCUMENT_POSITION_PRECEDING (0x02)
33    if (($positionFlags & DOM_DOCUMENT_POSITION_PRECEDING) === DOM_DOCUMENT_POSITION_PRECEDING) {
34        $result[] = '現在のノードより前に位置する';
35    }
36
37    // DOM_DOCUMENT_POSITION_FOLLOWING (0x04)
38    if (($positionFlags & DOM_DOCUMENT_POSITION_FOLLOWING) === DOM_DOCUMENT_POSITION_FOLLOWING) {
39        $result[] = '現在のノードより後に位置する';
40    }
41
42    // DOM_DOCUMENT_POSITION_CONTAINS (0x08)
43    if (($positionFlags & DOM_DOCUMENT_POSITION_CONTAINS) === DOM_DOCUMENT_POSITION_CONTAINS) {
44        $result[] = '現在のノードが比較対象ノードを含んでいる';
45    }
46
47    // DOM_DOCUMENT_POSITION_CONTAINED_BY (0x10)
48    if (($positionFlags & DOM_DOCUMENT_POSITION_CONTAINED_BY) === DOM_DOCUMENT_POSITION_CONTAINED_BY) {
49        $result[] = '現在のノードが比較対象ノードに含められている';
50    }
51
52    // 何も検出されない場合(通常、DOMの実装では何らかのフラグを返すはずですが、念のため)
53    if (empty($result)) {
54        return '不明な位置関係';
55    }
56
57    return implode(' & ', $result);
58}
59
60/**
61 * DOMCharacterData::compareDocumentPosition メソッドの使用例を示します。
62 *
63 * この関数は、システムエンジニアを目指す初心者が、
64 * PHPのDOM操作とノードの位置関係の比較について理解できるよう、
65 * 具体的な例とPHPDocコメントの適切な利用を示します。
66 *
67 * @return void
68 */
69function demonstrateCompareDocumentPosition(): void
70{
71    // HTMLドキュメントの作成
72    $dom = new DOMDocument();
73    // HTMLをロードします。@ をつけてエラーを抑制していますが、
74    // 実際のアプリケーションではエラーハンドリングを適切に行うべきです。
75    @$dom->loadHTML('
76        <!DOCTYPE html>
77        <html>
78        <body>
79            <p id="p1">Hello <span>World</span>!</p>
80            <div id="div1">Another <p>paragraph</p> here.</div>
81            <!-- これはコメントノードです -->
82            <p id="p2">End text.</p>
83        </body>
84        </html>
85    ');
86
87    // DOMXPath を使用して、ドキュメント内の特定のノードを取得します。
88    // これにより、様々な種類のノード(要素、テキスト、コメント)を効率的に見つけられます。
89    $xpath = new DOMXPath($dom);
90
91    // テキストノードを取得します。DOMText クラスは DOMCharacterData を継承しています。
92    // <p id="p1">Hello <span>World</span>!</p> の "Hello " の部分です。
93    $textNodeHello = $xpath->query('//p[@id="p1"]/text()[1]')->item(0);
94
95    // <span> 要素ノードを取得します。
96    $spanNode = $xpath->query('//span')->item(0);
97
98    // <p id="p1"> 要素ノードを取得します。
99    $pNode1 = $xpath->query('//p[@id="p1"]')->item(0);
100
101    // <div id="div1"> 内の <p> 要素ノードを取得します。
102    $pNode2InDiv = $xpath->query('//div[@id="div1"]/p')->item(0);
103
104    // <body> 要素ノードを取得します。
105    $bodyNode = $xpath->query('//body')->item(0);
106
107    // コメントノードを取得します。DOMComment クラスも DOMCharacterData を継承しています。
108    $commentNode = $xpath->query('//comment()')->item(0);
109
110    // 最後の <p> 要素のテキストノードを取得します。
111    $textNodeTail = $xpath->query('//p[@id="p2"]/text()[1]')->item(0);
112
113    // 必要なノードがすべて取得できたかを確認します。
114    if (
115        !$textNodeHello || !$spanNode || !$pNode1 ||
116        !$pNode2InDiv || !$bodyNode || !$commentNode ||
117        !$textNodeTail
118    ) {
119        echo "エラー: 必要なノードの一部が見つかりませんでした。スクリプトを終了します。\n";
120        return;
121    }
122
123    echo "--- DOMCharacterData::compareDocumentPosition の使用例 ---\n\n";
124
125    /**
126     * @var DOMCharacterData $baseNode DOMText は DOMCharacterData を継承しており、
127     *                                   DOMCharacterData も DOMNode を継承しているため、
128     *                                   DOMNode::compareDocumentPosition メソッドを呼び出すことができます。
129     */
130    $baseNode = $textNodeHello; // 比較の基準となるノードを "Hello " テキストノードに設定
131
132    echo "基準ノード: \"" . $baseNode->nodeValue . "\" (ノードタイプ: " . $baseNode->nodeName . ")\n\n";
133
134    // 1. 自身との比較
135    echo "1. 基準ノード自身との比較 (baseNode vs baseNode):\n";
136    $position = $baseNode->compareDocumentPosition($baseNode);
137    echo "   比較対象: \"{$baseNode->nodeValue}\"\n";
138    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
139
140    // 2. 親要素との比較
141    echo "2. 親要素との比較 (baseNode vs pNode1):\n";
142    $position = $baseNode->compareDocumentPosition($pNode1);
143    echo "   比較対象: <{$pNode1->nodeName}> (id=\"{$pNode1->getAttribute('id')}\")\n";
144    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
145
146    // 3. 後の子孫要素との比較 (baseNode は <p id="p1"> の最初の子、<span> は <p id="p1"> の子孫)
147    echo "3. 後の子孫要素 (<span>) との比較 (baseNode vs spanNode):\n";
148    $position = $baseNode->compareDocumentPosition($spanNode);
149    echo "   比較対象: <{$spanNode->nodeName}>\n";
150    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
151
152    // 4. 異なるツリーブランチのノードとの比較 (baseNode とは異なる <div id="div1"> の中の <p>)
153    echo "4. 異なるツリーブランチのノードとの比較 (baseNode vs pNode2InDiv):\n";
154    $position = $baseNode->compareDocumentPosition($pNode2InDiv);
155    echo "   比較対象: <{$pNode2InDiv->nodeName}>\n";
156    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
157
158    // 5. 別の DOMCharacterData (コメントノード) との比較
159    echo "5. 別の DOMCharacterData (コメントノード) との比較 (baseNode vs commentNode):\n";
160    $position = $baseNode->compareDocumentPosition($commentNode);
161    echo "   比較対象: \"{$commentNode->nodeValue}\"\n"; // コメントの内容を表示
162    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
163
164    // 6. 祖先ノードとの比較 (<body> は <p id="p1"> の親、<p id="p1"> は baseNode の親)
165    echo "6. 祖先ノード (<body>) との比較 (baseNode vs bodyNode):\n";
166    $position = $baseNode->compareDocumentPosition($bodyNode);
167    echo "   比較対象: <{$bodyNode->nodeName}>\n";
168    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
169
170    // 7. baseNode より後に位置するテキストノードとの比較 (テキストノード vs テキストノード)
171    echo "7. 後続のテキストノードとの比較 (baseNode vs textNodeTail):\n";
172    $position = $baseNode->compareDocumentPosition($textNodeTail);
173    echo "   比較対象: \"{$textNodeTail->nodeValue}\"\n";
174    echo "   結果: " . decodeDocumentPosition($position) . " (0x" . dechex($position) . ")\n\n";
175}
176
177// サンプルコードの実行
178demonstrateCompareDocumentPosition();

PHP 8のDOMCharacterData::compareDocumentPositionメソッドは、HTMLやXMLドキュメントのDOMツリーにおいて、二つのノード間の相対的な位置関係を比較します。このメソッドは、基準となるノード(メソッドを呼び出すオブジェクト)と、引数$otherとして渡される別のDOMNodeオブジェクトの位置関係を調べます。

戻り値はint型のビットマスクであり、これは複数の位置情報が組み合わされた数値です。例えば、比較対象のノードが同じノードであるか、基準ノードが比較対象ノードを含んでいるか、逆に比較対象ノードが基準ノードに含められているか、ドキュメント上で前後に位置するか、あるいは全く異なるツリーに属しているかといった関係が、この一つの数値で示されます。

サンプルコードでは、このビットマスクの戻り値を人間が読める文字列に変換するdecodeDocumentPosition関数を定義し、メソッドの理解を助けています。さらに、demonstrateCompareDocumentPosition関数内で、テキストノードや要素ノード、コメントノードといった様々なノードタイプを取得し、それぞれを比較する具体的な使用例が示されています。これにより、システムエンジニアを目指す初心者が、DOM操作におけるノード間の複雑な位置関係をプログラムでどのように判断し、利用できるかを実践的に学ぶことができます。

このサンプルコードは、PHPのDOMノードの位置関係を比較するcompareDocumentPositionメソッドの利用例です。特に注意すべき点は、戻り値がビットマスクという特殊な整数値であることです。これは、複数の位置情報が1つの数値にまとめられているため、decodeDocumentPosition関数の例のようにビット演算子&を使って個々のフラグを抽出する必要があります。DOM_DOCUMENT_POSITION_SAME_NODEは、比較対象が同じノードの場合に他のフラグとは組み合わされず単独で返される点も重要です。DOM操作では、@によるエラー抑制は避け、loadHTMLなどのメソッドでは適切なエラーハンドリングを実装してください。コードを理解しやすくするためのPHPDocコメントの書き方も参考にしてください。

関連コンテンツ

関連IT用語

関連プログラミング言語