【PHP8.x】DOMProcessingInstruction::previousSiblingプロパティの使い方
previousSiblingプロパティの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
previousSiblingプロパティは、DOMツリーにおいて、現在の処理命令ノード(DOMProcessingInstruction)の直前に位置する兄弟ノードを保持するプロパティです。XMLやHTML文書は、プログラム上では階層的なツリー構造(DOMツリー)として扱われ、同じ親を持つノード同士は兄弟関係にあります。このプロパティにアクセスすると、現在のノードと同じ階層にあり、その直前に記述されているノードをDOMNodeオブジェクトとして取得できます。もし現在のノードが親ノードの最初の子ノードである場合、直前の兄弟ノードは存在しないため、このプロパティの値は null となります。取得されるノードの種類は、要素ノードやテキストノード、コメントノードなど様々です。このプロパティは読み取り専用であるため、値を代入してDOMの構造を直接変更することはできません。文書の構造を前の要素に向かって順番にたどるような処理を実装する際に利用されます。
構文(syntax)
1<?php 2 3$xml = '<?xml version="1.0"?><root><child1/><?target data?></root>'; 4$doc = new DOMDocument(); 5$doc->loadXML($xml); 6 7// DOMProcessingInstructionノードを取得 8// この例では、<root>の子ノードの2番目 9$pi = $doc->documentElement->childNodes->item(1); 10 11// 直前の兄弟ノードを取得する 12$prevNode = $pi->previousSibling; 13 14// 直前の兄弟ノード(<child1>)の名前を出力 15if ($prevNode) { 16 echo $prevNode->nodeName; 17} 18 19?>
引数(parameters)
引数なし
引数はありません
戻り値(return)
DOMNode|null
このプロパティは、現在のノードの直前の兄弟ノードを表すDOMNodeオブジェクト、または直前の兄弟ノードが存在しない場合はnullを返します。
サンプルコード
PHP DOM: previousSiblingで前ノードを取得する
1<?php 2 3/** 4 * DOMProcessingInstruction の previousSibling プロパティの使用例 5 * このプロパティは、現在のノードの直前の兄弟ノードを返します。 6 * 兄弟ノードが存在しない場合は null を返します。 7 */ 8function demonstrateProcessingInstructionPreviousSibling(): void 9{ 10 // 1. 新しい DOM ドキュメントを作成します 11 $dom = new DOMDocument('1.0', 'UTF-8'); 12 $dom->formatOutput = true; // 出力を整形して見やすくします 13 14 // 2. ルート要素を作成し、ドキュメントに追加します 15 $root = $dom->createElement('root'); 16 $dom->appendChild($root); 17 18 // 3. 処理命令ノードの前に要素ノードを追加します 19 $elementBefore = $dom->createElement('elementBefore', 'これは処理命令の前の要素です'); 20 $root->appendChild($elementBefore); 21 22 // 4. 処理命令 (Processing Instruction: PI) ノードを作成し、ドキュメントに追加します 23 // <?php echo "Hello"; ?> のような形式のノードです 24 $processingInstruction = $dom->createProcessingInstruction('php', 'echo "Hello from PI";'); 25 $root->appendChild($processingInstruction); 26 27 // 5. 処理命令ノードの後に別の要素ノードを追加します 28 $elementAfter = $dom->createElement('elementAfter', 'これは処理命令の後の要素です'); 29 $root->appendChild($elementAfter); 30 31 echo "--- 生成されたドキュメントのXML構造 ---" . PHP_EOL; 32 echo $dom->saveXML(); 33 echo PHP_EOL; 34 35 echo "--- DOMProcessingInstruction::previousSibling の確認 ---" . PHP_EOL; 36 37 // 6. 処理命令ノード ($processingInstruction) の直前の兄弟ノードを取得します 38 $previousNode = $processingInstruction->previousSibling; 39 40 // 7. 取得したノードが存在するかどうかを確認し、情報を表示します 41 if ($previousNode instanceof DOMNode) { 42 echo "ProcessingInstruction の直前の兄弟ノードが見つかりました。" . PHP_EOL; 43 echo " ノードタイプ: " . get_node_type_name($previousNode->nodeType) . PHP_EOL; 44 echo " ノード名: " . $previousNode->nodeName . PHP_EOL; 45 echo " ノード値: " . $previousNode->nodeValue . PHP_EOL; 46 } else { 47 echo "ProcessingInstruction の直前の兄弟ノードは見つかりませんでした (null を返しました)。" . PHP_EOL; 48 } 49 50 // ドキュメントの先頭にPIを置いた場合の確認 51 echo PHP_EOL . "--- ドキュメントの先頭にPIがある場合の確認 ---" . PHP_EOL; 52 $dom2 = new DOMDocument('1.0', 'UTF-8'); 53 $dom2->formatOutput = true; 54 $piAtStart = $dom2->createProcessingInstruction('xml-stylesheet', 'href="style.css" type="text/css"'); 55 $dom2->appendChild($piAtStart); 56 $root2 = $dom2->createElement('data'); 57 $dom2->appendChild($root2); 58 59 echo "生成されたドキュメントのXML構造 (PIが先頭):" . PHP_EOL; 60 echo $dom2->saveXML(); 61 echo PHP_EOL; 62 63 $previousNodeAtStart = $piAtStart->previousSibling; 64 if ($previousNodeAtStart instanceof DOMNode) { 65 echo "先頭の ProcessingInstruction の直前の兄弟ノードが見つかりました。" . PHP_EOL; 66 echo " ノードタイプ: " . get_node_type_name($previousNodeAtStart->nodeType) . PHP_EOL; 67 echo " ノード名: " . $previousNodeAtStart->nodeName . PHP_EOL; 68 } else { 69 echo "先頭の ProcessingInstruction の直前の兄弟ノードは見つかりませんでした (null を返しました)。" . PHP_EOL; 70 } 71} 72 73/** 74 * DOMNode のノードタイプを示す定数を、人間が読みやすい文字列に変換するヘルパー関数です。 75 * 76 * @param int $type ノードタイプを示す整数値 (例: XML_ELEMENT_NODE) 77 * @return string ノードタイプの文字列表現 78 */ 79function get_node_type_name(int $type): string 80{ 81 return match ($type) { 82 XML_ELEMENT_NODE => '要素ノード (ELEMENT_NODE)', 83 XML_ATTRIBUTE_NODE => '属性ノード (ATTRIBUTE_NODE)', 84 XML_TEXT_NODE => 'テキストノード (TEXT_NODE)', 85 XML_CDATA_SECTION_NODE => 'CDATAセクションノード (CDATA_SECTION_NODE)', 86 XML_ENTITY_REF_NODE => '実体参照ノード (ENTITY_REF_NODE)', 87 XML_ENTITY_NODE => '実体ノード (ENTITY_NODE)', 88 XML_PI_NODE => '処理命令ノード (PROCESSING_INSTRUCTION_NODE)', 89 XML_COMMENT_NODE => 'コメントノード (COMMENT_NODE)', 90 XML_DOCUMENT_NODE => 'ドキュメントノード (DOCUMENT_NODE)', 91 XML_DOCUMENT_TYPE_NODE => 'ドキュメントタイプノード (DOCUMENT_TYPE_NODE)', 92 XML_DOCUMENT_FRAG_NODE => 'ドキュメントフラグメントノード (DOCUMENT_FRAGMENT_NODE)', 93 XML_NOTATION_NODE => '表記ノード (NOTATION_NODE)', 94 default => '不明なノードタイプ', 95 }; 96} 97 98// サンプルコードを実行します 99demonstrateProcessingInstructionPreviousSibling(); 100
PHPのDOMProcessingInstructionクラスが持つpreviousSiblingプロパティは、XMLドキュメント内の特定の処理命令ノードの直前にある兄弟ノードを取得します。このプロパティは引数を必要とせず、現在の処理命令ノードと同じ親を持つ、直前のノードを返します。戻り値は、直前の兄弟ノードが存在する場合にはDOMNodeオブジェクト、存在しない場合にはnullとなります。
サンプルコードでは、まずDOMDocumentを作成し、処理命令ノードの前にelementBeforeという要素ノードを追加しています。その後、$processingInstruction->previousSiblingを使用して直前の兄弟ノードを取得すると、elementBeforeノードが取得されることを確認できます。また、処理命令ノードがドキュメントの先頭に位置し、直前に兄弟ノードが存在しない場合にはnullが返される挙動も示されています。これにより、XMLツリー構造におけるノードの前方探索を正確に行い、柔軟なDOM操作を実装できます。
previousSiblingプロパティは、現在のノードの直前に兄弟ノードが存在しない場合にnullを返します。そのため、取得した結果がDOMNodeのインスタンスであるか、必ずif ($node instanceof DOMNode)のように型を確認する処理を記述し、予期せぬエラーを防ぐようにしてください。
兄弟ノードとは、同じ親ノードを持つノードのことを指します。要素ノードだけでなく、テキストノード、コメントノード、そして処理命令ノードなども、DOMツリー上では兄弟ノードとして扱われます。
ドキュメントのルート要素の直下にあるノードや、そのノードが子要素リストの最初のノードである場合など、直前に兄弟ノードが存在しない場合はnullが返されます。このプロパティは引数を取らずに、現在のノードのDOMツリー上の直前の兄弟ノードを簡単に取得するために利用できますが、その存在は保証されないため、常にnullチェックを行う習慣をつけることが重要です。
DOMProcessingInstruction::previousSibling を使って直前の兄弟ノードを取得する
1<?php 2 3/** 4 * DOMProcessingInstruction クラスの previousSibling プロパティの使用例を示します。 5 * このプロパティは、現在の処理命令ノードの直前の兄弟ノードを取得します。 6 * 戻り値は DOMNode オブジェクトまたは null です。 7 */ 8function demonstrateProcessingInstructionPreviousSibling(): void 9{ 10 // テスト用のXMLドキュメントを作成します。 11 // ここでは、処理命令ノードとその直前の兄弟ノード、 12 // およびドキュメントの最初の子ノードとしての処理命令を用意します。 13 $xmlString = <<<XML 14<?xml version="1.0" encoding="UTF-8"?> 15<!-- このコメントはルートノードの子ではありません --> 16<?initial-processing-instruction target="first"?> 17<root> 18 <element-before-pi attribute="value"/> 19 <?php echo "Hello World"; ?> 20 <element-after-pi/> 21</root> 22XML; 23 24 $dom = new DOMDocument(); 25 // XMLをロードし、空白ノードを無視することで、より予測しやすいノードツリーにします。 26 // (例: 改行やインデントがTextNodeとして扱われるのを避ける) 27 $dom->preserveWhiteSpace = false; 28 $dom->loadXML($xmlString); 29 30 // DOMXPath を使用して、目的の処理命令ノードを効率的に見つけます。 31 $xpath = new DOMXPath($dom); 32 33 echo "--- シナリオ1: 直前の兄弟ノードが存在する場合 ---" . PHP_EOL; 34 35 // 「php」というターゲットを持つ処理命令ノードを検索します。 36 $phpProcessingInstructions = $xpath->query('//processing-instruction("php")'); 37 38 if ($phpProcessingInstructions->length > 0) { 39 /** @var DOMProcessingInstruction $phpPiNode */ 40 $phpPiNode = $phpProcessingInstructions->item(0); 41 42 echo "対象の処理命令ノード (PHP PI):" . PHP_EOL; 43 echo " ノード名 (ターゲット): " . $phpPiNode->nodeName . PHP_EOL; // 例: php 44 echo " ノード値 (データ): " . $phpPiNode->nodeValue . PHP_EOL; // 例: echo "Hello World"; 45 46 // previousSibling プロパティを使って、直前の兄弟ノードを取得します。 47 $previousSiblingNode = $phpPiNode->previousSibling; 48 49 echo PHP_EOL . "previousSibling の結果:" . PHP_EOL; 50 if ($previousSiblingNode instanceof DOMNode) { 51 echo " 直前の兄弟ノードが見つかりました。" . PHP_EOL; 52 echo " ノード名: " . $previousSiblingNode->nodeName . PHP_EOL; // 例: element-before-pi 53 echo " ノードタイプ: " . $previousSiblingNode->nodeType . " (DOM_ELEMENT_NODE なら 1)" . PHP_EOL; 54 // ノードタイプ定数の例: DOM_ELEMENT_NODE (1), DOM_TEXT_NODE (3), DOM_COMMENT_NODE (8), DOM_PROCESSING_INSTRUCTION_NODE (7) 55 echo " ノード値 (もしあれば): " . $previousSiblingNode->nodeValue . PHP_EOL; 56 // エレメントノードの場合、nodeValue は通常空文字列です。 57 } else { 58 echo " 直前の兄弟ノードは存在しません (null が返されました)。" . PHP_EOL; 59 } 60 } else { 61 echo "「php」処理命令ノードが見つかりませんでした。" . PHP_EOL; 62 } 63 64 echo PHP_EOL . "--- シナリオ2: 直前の兄弟ノードが存在しない場合 (最初の兄弟) ---" . PHP_EOL; 65 66 // ドキュメントルートの最初の子である処理命令ノードを検索します。 67 // 今回は「initial-processing-instruction」というターゲットを持つもの。 68 $initialProcessingInstructions = $xpath->query('/processing-instruction("initial-processing-instruction")'); 69 70 if ($initialProcessingInstructions->length > 0) { 71 /** @var DOMProcessingInstruction $initialPiNode */ 72 $initialPiNode = $initialProcessingInstructions->item(0); 73 74 echo "対象の処理命令ノード (Initial PI):" . PHP_EOL; 75 echo " ノード名 (ターゲット): " . $initialPiNode->nodeName . PHP_EOL; // 例: initial-processing-instruction 76 echo " ノード値 (データ): " . $initialPiNode->nodeValue . PHP_EOL; // 例: target="first" 77 78 // previousSibling プロパティを使って、直前の兄弟ノードを取得します。 79 $previousSiblingOfInitialPi = $initialPiNode->previousSibling; 80 81 echo PHP_EOL . "previousSibling の結果:" . PHP_EOL; 82 if ($previousSiblingOfInitialPi instanceof DOMNode) { 83 echo " 直前の兄弟ノードが見つかりました (予期しない結果の場合があります)。" . PHP_EOL; 84 echo " ノード名: " . $previousSiblingOfInitialPi->nodeName . PHP_EOL; 85 echo " ノードタイプ: " . $previousSiblingOfInitialPi->nodeType . PHP_EOL; 86 } else { 87 echo " 直前の兄弟ノードは存在しません (null が返されました)。" . PHP_EOL; 88 echo " これは、このノードが親 (DOMDocument) の最初の子であるためです。" . PHP_EOL; 89 } 90 } else { 91 echo "「initial-processing-instruction」処理命令ノードが見つかりませんでした。" . PHP_EOL; 92 } 93} 94 95// サンプルコードを実行します。 96demonstrateProcessingInstructionPreviousSibling();
PHPのDOMProcessingInstruction::previousSiblingプロパティは、XMLドキュメントをPHPで扱う際に、現在の処理命令ノードの直前にある兄弟ノードを取得するために使われます。このプロパティには引数は必要ありません。
戻り値は、直前の兄弟ノードが存在する場合はDOMNodeオブジェクトです。このDOMNodeは、要素ノード、テキストノード、コメントノード、あるいは別の処理命令ノードなど、あらゆる種類のノードである可能性があります。しかし、現在の処理命令ノードが親の最初の子であり、直前に兄弟ノードが存在しない場合には、nullが返されます。
サンプルコードでは、まずXML文字列からDOMDocumentオブジェクトを作成し、preserveWhiteSpaceをfalseに設定することで、空白文字が余計なテキストノードとして扱われるのを防いでいます。そして、DOMXPathを使って特定の処理命令ノードを検索します。
最初のシナリオでは、「php」というターゲットを持つ処理命令ノードを対象とします。このノードの直前には<element-before-pi>という要素ノードが存在するため、previousSiblingプロパティは<element-before-pi>を表すDOMNodeオブジェクトを返します。これにより、そのノードの名前やタイプを確認できます。
二番目のシナリオでは、ドキュメントのルート直下にある「initial-processing-instruction」というターゲットを持つ処理命令ノードを対象とします。このノードは親(DOMDocument)の最初の子であるため、直前の兄弟ノードが存在しません。そのため、previousSiblingプロパティはnullを返します。このように、previousSiblingプロパティを使用することで、XMLノードツリーの構造を辿り、ノードの前後関係を効率的に調べることが可能です。
previousSiblingプロパティは、現在のノードの直前の兄弟ノードを返しますが、存在しない場合はnullを返します。そのため、取得した値がDOMNodeインスタンスであるか、必ずnullチェックを行ってから処理を進めるようにしてください。返されるノードは、要素ノードだけでなく、テキストノード、コメントノード、あるいは別の処理命令ノードなど、様々なタイプである可能性があります。ノードの種類に応じて処理を分岐させるには、nodeTypeプロパティで判別することが重要です。また、DOMDocumentのpreserveWhiteSpaceプロパティがtrueの場合、XML中の改行やインデントもテキストノードとして扱われ、previousSiblingの結果に影響を与えるため、意図しない空白ノードを避けるにはこの設定を理解し適切に扱う必要があります。