【PHP8.x】DOMNotation::compareDocumentPosition()メソッドの使い方
compareDocumentPositionメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
compareDocumentPositionメソッドは、現在のDOMNotationノードと、引数で指定された他のノードとのドキュメント内での相対的な位置関係を比較する処理を実行するメソッドです。このメソッドは、すべてのDOMノードの基底クラスであるDOMNodeクラスから継承されています。引数には、比較対象となるDOMNodeオブジェクトを渡します。メソッドが返却するのは、2つのノードの位置関係を示すビットマスクと呼ばれる整数値です。このビットマスクには、対象ノードが現在のノードより文書内で後にあるか(DOMNode::DOCUMENT_POSITION_FOLLOWING)、前にあるか(DOMNode::DOCUMENT_POSITION_PRECEDING)、あるいは互いに関係ないツリーに属しているか(DOMNode::DOCUMENT_POSITION_DISCONNECTED)といった情報が符号化されています。開発者は、この戻り値と定義済みの定数をビット単位の論理積(&演算子)を用いて比較することで、ノード間の詳細な関係を判定できます。これにより、DOMツリーの構造をプログラムで正確に把握し、ノードの順序や親子関係に基づいた処理を実装する際に利用されます。
構文(syntax)
1<?php 2 3$xml = <<<XML 4<?xml version="1.0"?> 5<!DOCTYPE root [ 6 <!NOTATION notation1 PUBLIC "some-public-id"> 7]> 8<root></root> 9XML; 10 11$doc = new DOMDocument(); 12$doc->loadXML($xml); 13 14// $notation は DOMNotation オブジェクトです 15$notation = $doc->doctype->notations->getNamedItem('notation1'); 16 17// $otherNode は DOMNode オブジェクトです 18$otherNode = $doc->documentElement; 19 20// public function DOMNotation::compareDocumentPosition(DOMNode $other): int 21$position = $notation->compareDocumentPosition($otherNode); 22 23?>
引数(parameters)
DOMNode $other
- DOMNode $other: 比較対象となる別のDOMNodeオブジェクト
戻り値(return)
int
このメソッドは、2つのDOMノード間の相対的な位置関係を示す整数値を返します。
サンプルコード
PHP DOMNode::compareDocumentPosition を使ったノード位置比較
1<?php 2 3/** 4 * DOMNode::compareDocumentPosition() メソッドの使用例を示します。 5 * 6 * このメソッドは、DOMNode を継承するすべてのクラス(DOMElement, DOMText, DOMNotation など)で利用可能です。 7 * 与えられたリファレンス情報では「所属クラス: DOMNotation」とありますが、 8 * PHPのリファレンスでは DOMNode のメソッドとして定義されており、DOMNotation も DOMNode を継承するため、 9 * このメソッドを利用できます。 10 * 11 * 指定された2つのノード間の相対的な位置関係を比較し、結果をビットマスクとして返します。 12 * このサンプルでは、主に DOMElement を用いてその動作をデモンストレーションします。 13 * 14 * @see https://www.php.net/manual/ja/domnode.comparedocumentposition.php PHP公式リファレンス (DOMNode::compareDocumentPosition) 15 */ 16function demonstrateCompareDocumentPosition(): void 17{ 18 // DOMDocument オブジェクトを作成 19 $dom = new DOMDocument('1.0', 'UTF-8'); 20 $dom->formatOutput = true; // 出力整形を有効にする 21 22 // ルート要素を作成し、ドキュメントに追加 23 $root = $dom->createElement('root'); 24 $dom->appendChild($root); 25 26 // 子要素を作成し、ルート要素に追加 27 $child1 = $dom->createElement('child1'); 28 $root->appendChild($child1); 29 30 // さらに深い階層の子要素を作成 31 $grandchild1 = $dom->createElement('grandchild1'); 32 $child1->appendChild($grandchild1); 33 34 // 別の兄弟要素を作成 35 $child2 = $dom->createElement('child2'); 36 $root->appendChild($child2); 37 38 echo "DOMNode::compareDocumentPosition() のデモンストレーション:\n"; 39 echo "---------------------------------------------------------\n"; 40 41 // 1. 同じノードを比較 42 // 結果: 0 (DOM_DOCUMENT_POSITION_SAME_NODE) 43 $result = $root->compareDocumentPosition($root); 44 echo "root vs root: " . getPositionDescription($result) . " (期待値: SAME_NODE)\n"; 45 46 // 2. 親ノードと子ノードを比較 ($root が $child1 を含む) 47 // 結果: CONTAINS (0x08) | FOLLOWING (0x04) = 12 48 $result = $root->compareDocumentPosition($child1); 49 echo "root vs child1: " . getPositionDescription($result) . " (期待値: CONTAINS | FOLLOWING)\n"; 50 51 // 3. 子ノードと親ノードを比較 ($child1 が $root に含まれる) 52 // 結果: CONTAINED_BY (0x10) | PRECEDING (0x02) = 18 53 $result = $child1->compareDocumentPosition($root); 54 echo "child1 vs root: " . getPositionDescription($result) . " (期待値: CONTAINED_BY | PRECEDING)\n"; 55 56 // 4. 兄弟ノードを比較 ($child1 が $child2 より前にある) 57 // 結果: FOLLOWING (0x04) 58 $result = $child1->compareDocumentPosition($child2); 59 echo "child1 vs child2: " . getPositionDescription($result) . " (期待値: FOLLOWING)\n"; 60 61 // 5. 兄弟ノードを比較 ($child2 が $child1 より後にある) 62 // 結果: PRECEDING (0x02) 63 $result = $child2->compareDocumentPosition($child1); 64 echo "child2 vs child1: " . getPositionDescription($result) . " (期待値: PRECEDING)\n"; 65 66 // 6. 異なるドキュメントのノードを比較 67 // 結果: DISCONNECTED (0x01) 68 $otherDom = new DOMDocument(); 69 $otherRoot = $otherDom->createElement('otherRoot'); 70 $otherDom->appendChild($otherRoot); 71 72 $result = $root->compareDocumentPosition($otherRoot); 73 echo "root vs otherRoot (異なるドキュメント): " . getPositionDescription($result) . " (期待値: DISCONNECTED)\n"; 74} 75 76/** 77 * compareDocumentPosition の結果であるビットマスクを、人間が読める文字列に変換します。 78 * 79 * PHPのDOM定数を利用して、ビットフラグを解釈します。 80 * 81 * @param int $position compareDocumentPosition から返されるビットマスク結果。 82 * @return string 位置関係を示す人間が読める形式の文字列。 83 */ 84function getPositionDescription(int $position): string 85{ 86 $descriptions = []; 87 88 // DOM_DOCUMENT_POSITION_SAME_NODE は 0 なので、他のフラグと排他的に処理 89 if ($position === 0) { 90 return 'SAME_NODE'; 91 } 92 93 // 各ビットフラグをチェックし、該当する説明を追加 94 if ($position & DOM_DOCUMENT_POSITION_DISCONNECTED) { 95 $descriptions[] = 'DISCONNECTED'; 96 } 97 if ($position & DOM_DOCUMENT_POSITION_PRECEDING) { 98 $descriptions[] = 'PRECEDING'; 99 } 100 if ($position & DOM_DOCUMENT_POSITION_FOLLOWING) { 101 $descriptions[] = 'FOLLOWING'; 102 } 103 if ($position & DOM_DOCUMENT_POSITION_CONTAINS) { 104 $descriptions[] = 'CONTAINS'; 105 } 106 if ($position & DOM_DOCUMENT_POSITION_CONTAINED_BY) { 107 $descriptions[] = 'CONTAINED_BY'; 108 } 109 if ($position & DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) { 110 $descriptions[] = 'IMPLEMENTATION_SPECIFIC'; 111 } 112 113 return implode(' | ', $descriptions); 114} 115 116// スクリプトの実行 117demonstrateCompareDocumentPosition();
PHPのDOMNode::compareDocumentPosition()メソッドは、二つのDOMノード間の相対的な位置関係を比較するために使用されます。このメソッドは、DOMNodeクラスに属し、DOMNotationを含むDOMNodeを継承するすべてのクラスで利用可能です。引数 $other には比較対象となるDOMNodeオブジェクトを一つ指定し、戻り値として、ノードの位置関係を示す整数値のビットマスクを返します。このビットマスクは、DOM_DOCUMENT_POSITION_SAME_NODE、DOM_DOCUMENT_POSITION_DISCONNECTED、DOM_DOCUMENT_POSITION_PRECEDINGなどの定数の組み合わせで、詳細な位置情報を示します。
サンプルコードでは、DOMDocumentとDOMElementを使ってシンプルなXML構造を作成し、その中のノード同士をcompareDocumentPosition()メソッドで比較する様子を示しています。ルートノードと子ノード、子ノード同士の兄弟関係、さらには異なるドキュメントに属するノードなど、様々なパターンで位置関係を比較しています。getPositionDescription()関数は、メソッドの戻り値であるビットマスクを、人間が読みやすい文字列に変換して表示する補助関数です。これにより、「同じノードであるか」「どちらがどちらを含んでいるか」「どちらがどちらより前または後に位置するか」といったノード間の具体的な関係性を、視覚的に確認できるようになっています。この機能は、DOMツリーを操作する際にノードの相対位置を正確に把握し、プログラムのロジックに活用するのに役立ちます。
DOMNode::compareDocumentPositionメソッドは、リファレンス情報にあるDOMNotationクラスだけでなく、DOMNodeを継承するDOMElementやDOMTextなど、多様なDOMノードで利用できる汎用的な機能です。このメソッドは、引数で渡されたノードとの相対的な位置関係を数値(ビットマスク)として返します。戻り値の数値は複数の状態を示すため、DOM_DOCUMENT_POSITION_SAME_NODEのようなDOM定数とビット演算子(&)を使って個々の位置関係を正しく解釈する必要があります。ノードが同一、親子関係、兄弟関係、あるいは異なるドキュメントに属するかによって結果は異なり、時には複数の位置関係が同時に示される場合もありますので注意が必要です。DOMツリー構造内のノード位置を正確に判断する際に活用できます。
PHP DOMNotation::compareDocumentPosition でノード位置比較
1<?php 2 3/** 4 * このスクリプトは、PHPのDOM拡張機能におけるDOMNotationクラスの 5 * compareDocumentPositionメソッドの使用例を示します。 6 * 7 * DOMNotationはXMLのDTD(Document Type Definition)に記述される記法(NOTATION)を 8 * 表すオブジェクトです。`compareDocumentPosition`メソッドは、 9 * 2つのDOMノード間のドキュメント内での相対的な位置関係を比較します。 10 * 11 * システムエンジニアを目指す初心者の方へ: 12 * PHPのDOM拡張はXMLやHTMLドキュメントをプログラムで操作するための強力なツールです。 13 * `DOMNotation`は比較的特殊なノードタイプですが、XMLのDTDを利用する際には重要になります。 14 * このメソッドは、ノード間の位置関係を知りたい場合に役立ちます。 15 * 16 * phpDocumentorでこのコードのドキュメントを生成する際には、 17 * 例えばプロジェクトルートで `phpdoc -d . -t docs` のようにコマンドを実行します。 18 * このPHPDocコメントが適切に記述されていれば、詳細なAPIドキュメントが生成されます。 19 */ 20 21/** 22 * DOMNotationオブジェクトと他のDOMNodeオブジェクトのドキュメント内での位置関係を比較し、 23 * その結果を分かりやすく表示する関数です。 24 * 25 * DOMNotationはXMLドキュメントの構造(DOCTYPE)の一部であり、 26 * 通常の要素ノード(DOMElementなど)とはドキュメントツリー上で直接的な親子関係を持ちません。 27 * そのため、DOMNotationとDOMElementを比較すると、ほとんどの場合「切断されている(DISCONNECTED)」 28 * という結果になります。しかし、複数のDOMNotationを比較した場合は、DTD内での宣言順序に基づいて 29 * 「前にある(PRECEDING)」や「後ろにある(FOLLOWING)」という情報も示されることがあります。 30 * 31 * @param string $xmlString 比較に使用するXMLドキュメントの文字列。DTDにNOTATION定義が含まれることを想定します。 32 * @return void この関数は結果を標準出力に出力します。 33 */ 34function demonstrateDomNotationComparison(string $xmlString): void 35{ 36 // DOMDocumentオブジェクトを初期化します。 37 $dom = new DOMDocument(); 38 // DTDバリデーションを有効にし、エラー時に警告を発するようにします。 39 $dom->validateOnParse = true; 40 // libxmlのエラーを内部で処理し、PHPのエラーハンドラに渡さないように設定します。 41 libxml_use_internal_errors(true); 42 43 // XML文字列をDOMDocumentにロードします。 44 if (!$dom->loadXML($xmlString)) { 45 echo "エラー: XMLのロードに失敗しました。\n"; 46 foreach (libxml_get_errors() as $error) { 47 echo " Libxmlエラー: " . trim($error->message) . " (Code: " . $error->code . ")\n"; 48 } 49 libxml_clear_errors(); // エラーをクリア 50 return; 51 } 52 53 echo "--- ロードされたXMLドキュメント ---\n"; 54 echo $dom->saveXML(); // 整形されたXMLを出力 55 echo "----------------------------------\n\n"; 56 57 // ドキュメントのDOCTYPEオブジェクトを取得します。 58 /** @var DOMDocumentType|null $doctype */ 59 $doctype = $dom->doctype; 60 61 if (!$doctype) { 62 echo "このXMLドキュメントにはDOCTYPE宣言がありません。DOMNotationを取得できません。\n"; 63 libxml_clear_errors(); 64 return; 65 } 66 67 // DOCTYPEから定義されているNOTATIONのリストを取得します。 68 /** @var DOMNamedNodeMap|null $notations */ 69 $notations = $doctype->notations; 70 71 if (!$notations || $notations->length === 0) { 72 echo "DOCTYPE宣言にNOTATIONが定義されていません。比較対象がありません。\n"; 73 libxml_clear_errors(); 74 return; 75 } 76 77 echo "--- DOMNotation の取得 ---\n"; 78 /** @var DOMNotation|null $firstNotation ドキュメント内で最初に見つかったNOTATION */ 79 $firstNotation = null; 80 // NOTATIONのリストを反復処理し、最初のDOMNotationオブジェクトを取得します。 81 foreach ($notations as $notationName => $notationNode) { 82 if ($notationNode instanceof DOMNotation) { 83 $firstNotation = $notationNode; 84 echo "最初のNOTATION: '$notationName' (Public ID: '{$notationNode->publicId}', System ID: '{$notationNode->systemId}')\n"; 85 break; // 最初のNOTATIONが見つかったらループを抜けます 86 } 87 } 88 89 if (!$firstNotation) { 90 echo "DOMNotationオブジェクトが見つかりませんでした。\n"; 91 libxml_clear_errors(); 92 return; 93 } 94 95 // ドキュメントツリー内の最初の要素ノードを取得します(例としてルート要素を使用)。 96 /** @var DOMElement|null $firstElement */ 97 $firstElement = $dom->documentElement; 98 99 if (!$firstElement) { 100 echo "ドキュメントにルート要素がありません。比較対象のDOMElementが見つかりません。\n"; 101 libxml_clear_errors(); 102 return; 103 } 104 105 echo "\n--- ドキュメント位置の比較 (DOMNotation vs DOMElement) ---\n"; 106 echo "DOMNotation ('{$firstNotation->nodeName}') と DOMElement ('{$firstElement->nodeName}') を比較します。\n"; 107 108 // `compareDocumentPosition`メソッドを呼び出して、2つのノードの位置関係を比較します。 109 // 結果はビットマスクとして返されます。 110 $position = $firstNotation->compareDocumentPosition($firstElement); 111 112 echo "比較結果 (ビットマスク): " . sprintf("0x%02X", $position) . "\n"; 113 echo "結果の意味:\n"; 114 115 // 各ビットフラグをチェックして、位置関係を解釈します。 116 if ($position & DOM_DOCUMENT_POSITION_DISCONNECTED) { 117 echo " - DOM_DOCUMENT_POSITION_DISCONNECTED (0x01): 2つのノードはドキュメント内で直接的な関係を持たず、切断されています。\n"; 118 echo " (DOMNotationはDTDの一部であり、DOMElementはドキュメントツリー内にあるため、この結果は通常です。)\n"; 119 } 120 if ($position & DOM_DOCUMENT_POSITION_PRECEDING) { 121 echo " - DOM_DOCUMENT_POSITION_PRECEDING (0x02): '$firstNotation->nodeName' が '$firstElement->nodeName' より前にあります。\n"; 122 } 123 if ($position & DOM_DOCUMENT_POSITION_FOLLOWING) { 124 echo " - DOM_DOCUMENT_POSITION_FOLLOWING (0x04): '$firstNotation->nodeName' が '$firstElement->nodeName' より後にあります。\n"; 125 } 126 if ($position & DOM_DOCUMENT_POSITION_CONTAINS) { 127 echo " - DOM_DOCUMENT_POSITION_CONTAINS (0x08): '$firstNotation->nodeName' が '$firstElement->nodeName' を含んでいます。\n"; 128 } 129 if ($position & DOM_DOCUMENT_POSITION_CONTAINED_BY) { 130 echo " - DOM_DOCUMENT_POSITION_CONTAINED_BY (0x10): '$firstNotation->nodeName' が '$firstElement->nodeName' に含まれています。\n"; 131 } 132 if ($position & DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) { 133 echo " - DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (0x20): 実装依存のプライベートな比較結果です。\n"; 134 } 135 echo "\n"; 136 137 // 複数のNOTATIONがある場合、DOMNotation同士の比較も試します。 138 if ($notations->length > 1) { 139 /** @var DOMNotation|null $secondNotation ドキュメント内で二番目に見つかったNOTATION */ 140 $secondNotation = $notations->item(1); // 2番目のNOTATIONを取得 141 142 if ($secondNotation && $firstNotation->nodeName !== $secondNotation->nodeName) { 143 echo "--- ドキュメント位置の比較 (DOMNotation vs 別のDOMNotation) ---\n"; 144 echo "DOMNotation ('{$firstNotation->nodeName}') と 別のDOMNotation ('{$secondNotation->nodeName}') を比較します。\n"; 145 146 // DOMNotation同士を比較した場合、DTD内の定義順序が結果に影響を与えることがあります。 147 $positionNotationToNotation = $firstNotation->compareDocumentPosition($secondNotation); 148 echo "比較結果 (ビットマスク): " . sprintf("0x%02X", $positionNotationToNotation) . "\n"; 149 echo "結果の意味:\n"; 150 151 if ($positionNotationToNotation & DOM_DOCUMENT_POSITION_DISCONNECTED) { 152 echo " - DOM_DOCUMENT_POSITION_DISCONNECTED (0x01): 切断されています。\n"; 153 } 154 if ($positionNotationToNotation & DOM_DOCUMENT_POSITION_PRECEDING) { 155 echo " - DOM_DOCUMENT_POSITION_PRECEDING (0x02): '$firstNotation->nodeName' が '$secondNotation->nodeName' より前にあります。\n"; 156 } 157 if ($positionNotationToNotation & DOM_DOCUMENT_POSITION_FOLLOWING) { 158 echo " - DOM_DOCUMENT_POSITION_FOLLOWING (0x04): '$firstNotation->nodeName' が '$secondNotation->nodeName' より後にあります。\n"; 159 } 160 // その他のフラグは、通常DOMNotation間の比較では設定されません。 161 echo "\n"; 162 } 163 } 164 165 libxml_clear_errors(); // セッション終了前にlibxmlのエラーをクリアします。 166} 167 168// ----------------------------------------------------------------------------- 169// サンプルコードの実行部分 170// ----------------------------------------------------------------------------- 171 172// DOMNotationを定義したXMLドキュメントの例 173$sampleXmlWithNotation = <<<XML 174<!DOCTYPE root [ 175 <!NOTATION image-gif PUBLIC "-//W3C//DTD GIF89a//EN" "image/gif"> 176 <!NOTATION image-jpeg SYSTEM "image/jpeg"> 177 <!NOTATION image-png PUBLIC "Image PNG" "image/png"> 178 <!ELEMENT root (item)*> 179 <!ELEMENT item EMPTY> 180 <!ATTLIST item 181 type NOTATION (image-gif | image-jpeg | image-png) #REQUIRED 182 > 183]> 184<root> 185 <item type="image-gif"/> 186 <item type="image-jpeg"/> 187</root> 188XML; 189 190echo "--- DOMNotationを含むXMLのデモンストレーションを開始 ---\n"; 191demonstrateDomNotationComparison($sampleXmlWithNotation); 192echo "--- DOMNotationを含むXMLのデモンストレーションを終了 ---\n\n"; 193 194// DOMNotationが存在しないXMLドキュメントの例 195$sampleXmlWithoutNotation = <<<XML 196<?xml version="1.0" encoding="UTF-8"?> 197<bookstore> 198 <book category="cooking"> 199 <title lang="en">Everyday Italian</title> 200 <author>Giada De Laurentiis</author> 201 <year>2005</year> 202 <price>30.00</price> 203 </book> 204</bookstore> 205XML; 206 207echo "--- DOMNotationを含まないXMLのデモンストレーションを開始 ---\n"; 208demonstrateDomNotationComparison($sampleXmlWithoutNotation); 209echo "--- DOMNotationを含まないXMLのデモンストレーションを終了 ---\n"; 210 211?>
このPHPサンプルコードは、XMLのDTD(Document Type Definition)で定義される記法を表すDOMNotationクラスのcompareDocumentPositionメソッドの使い方を示しています。このメソッドは、呼び出し元のDOMNotationオブジェクトと、引数として渡されたDOMNodeオブジェクト($other)のドキュメント内での相対的な位置関係を比較します。
引数$otherには、比較したい任意のDOMノードを指定します。戻り値はint型のビットマスクで、DOM_DOCUMENT_POSITION_DISCONNECTED(ノードが切断されている)、DOM_DOCUMENT_POSITION_PRECEDING(呼び出し元が引数より前に位置する)、DOM_DOCUMENT_POSITION_FOLLOWING(呼び出し元が引数より後に位置する)などの定数を組み合わせて、その関係を表します。
DOMNotationはXMLドキュメントの構造定義の一部であり、通常の要素ノードとは直接的な親子関係を持たないため、ほとんどの場合、他のDOMElementなどと比較するとDOM_DOCUMENT_POSITION_DISCONNECTEDという結果が返されます。サンプルコードでは、DOMNotationとDOMElementの比較でこの切断された関係が示され、また複数のDOMNotation同士の比較ではDTD内での宣言順序に基づく位置関係(PRECEDINGまたはFOLLOWING)が示される可能性についても説明されています。このメソッドは、XMLドキュメント内の複雑なノード構造において、特定のノードが他のノードに対してどの位置にあるかを知る必要がある場合に役立ちます。
DOMNotationはXMLのDTDで定義される特殊なノードであり、一般的なDOM要素ノードとはドキュメントツリー上での位置関係が異なります。そのため、compareDocumentPositionメソッドで他のノードと比較する際は、ほとんどの場合「DOM_DOCUMENT_POSITION_DISCONNECTED」(切断されている)という結果が返されることに注意してください。このメソッドの戻り値はビットマスク形式で複数の状態を示すことがあるため、結果は各DOM_DOCUMENT_POSITION定数とビットAND演算子(&)を用いて慎重に解釈する必要があります。XMLファイルのロード時には、libxml_use_internal_errors()とlibxml_get_errors()を併用し、エラーを適切にハンドリングすることが重要です。また、詳細なAPIドキュメントを生成するには、PHPDocコメントを適切に記述し、phpdocコマンドを利用すると良いでしょう。