【PHP8.x】Dom\Notation::C14N()メソッドの使い方
C14Nメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
C14Nメソッドは、DOM NotationノードをCanonical XML形式にシリアライズするメソッドです。Canonical XML (C14N) は、XMLドキュメントを正規化するための標準規格であり、異なるシステム間でのXMLデータの比較や検証を容易にすることを目的としています。
このメソッドは、Dom\Notationオブジェクトに対して呼び出され、そのノード以下のサブツリーをC14N形式の文字列として返します。C14N形式は、XMLドキュメントの属性の順序、名前空間の宣言、エンティティ参照など、XMLドキュメントの意味を変えずに表現形式を統一するものです。
具体的には、C14Nメソッドは以下の処理を行います。
- コメントの削除(オプションで保持可能)
- 属性のソート
- 名前空間の明示的な宣言
- エンティティ参照の解決
- 文字コードの正規化
C14Nメソッドは、XMLデータの整合性を確保する必要がある場合や、XML署名などのセキュリティ関連の処理を行う場合に特に有用です。たとえば、同じ内容のXMLドキュメントであっても、作成された環境やツールによって表現形式が異なる場合があります。C14Nを適用することで、表現形式の違いを吸収し、内容に基づいて一意に識別できるようになります。これにより、データの比較や検証がより正確に行えるようになります。
Dom\NotationクラスのC14Nメソッドは、このようなCanonical XML形式への変換処理を簡単に行うための機能を提供します。
構文(syntax)
1<?php 2$domNotationInstance = null; // Dom\Notationインスタンスを保持する変数 3$exclusiveCanonicalization = false; // 排他的正規化のフラグ (bool) 4$includeCommentsInOutput = true; // 出力にコメントを含めるかのフラグ (bool) 5 6$resultCanonicalXmlString = $domNotationInstance->C14N($exclusiveCanonicalization, $includeCommentsInOutput);
引数(parameters)
bool $exclusive = false, bool $with_comments = false, ?array $xpath = null, ?array $ns_prefixes = null
- bool $exclusive = false: 排他的な正規化を行うかどうかを指定します。
trueの場合、要素のID属性は無視されます。 - bool $with_comments = false: コメントノードを含めて正規化するかどうかを指定します。
- ?array $xpath = null: 正規化の対象を絞り込むために使用するXPathクエリの配列。
- ?array $ns_prefixes = null: 正規化の際に考慮する名前空間プレフィックスの配列。
戻り値(return)
string|false
C14N メソッドは、XML ノードを正規化された文字列として返します。正規化に失敗した場合は false を返します。
サンプルコード
PHP 8 XML C14N正規化する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * XML文字列を正規化(C14N)し、その結果を比較表示します。 7 * 8 * C14N (Canonicalization) とは、XML文書の論理的な内容を保ったまま、 9 * テキスト表現を統一的な形式(正規形)に変換するプロセスです。 10 * これにより、テキスト上では異なっていても論理的に等価なXML文書を 11 * 文字列として比較できるようになります。 12 * このサンプルでは、DOMDocumentオブジェクトに対してC14N()メソッドを使用します。 13 */ 14function demonstrateXmlCanonicalization(): void 15{ 16 // 正規化対象のXML文字列。 17 // 属性の順序が統一されておらず、余分なインデントや改行が含まれています。 18 $originalXml = <<<XML 19<order id="123"> 20 <!-- Customer Information --> 21 <customer 22 zip="100-0001" 23 name="Taro Yamada" /> 24 <item part_no="A-456">Pen</item> 25</order> 26XML; 27 28 // DOMDocumentオブジェクトを作成し、XMLを読み込みます 29 $dom = new DOMDocument(); 30 $dom->loadXML($originalXml); 31 32 // C14N() メソッドを呼び出して、ドキュメント全体を正規化します。 33 // デフォルトではコメントも出力に含まれます。 34 // この変換により、主に以下の変更が行われます: 35 // - 各要素の属性が、属性名でアルファベット順にソートされる 36 // - 空白や改行の扱いが標準化される 37 $canonicalXml = $dom->C14N(); 38 39 // 比較のために結果を出力します 40 echo "--- 元のXML ---\n"; 41 echo $originalXml . "\n\n"; 42 43 if ($canonicalXml !== false) { 44 echo "--- 正規化されたXML (C14N) ---\n"; 45 echo "属性の順序が 'id', 'name', 'zip' / 'part_no' に統一されている点に注目してください。\n"; 46 echo $canonicalXml . "\n"; 47 } else { 48 echo "XMLの正規化に失敗しました。\n"; 49 } 50} 51 52// サンプル関数を実行します 53demonstrateXmlCanonicalization();
C14N() は、XML文書を正規形(Canonical Form)と呼ばれる統一的なテキスト表現に変換するためのメソッドです。XMLは、属性の記述順序や空白の有無が異なっていても、論理的には同じ内容を示す場合があります。このメソッドは、そうした表現上の差異を吸収し、内容が等価なXML文書からは常に同一の文字列を生成します。これにより、XML文書のデジタル署名や、内容が同じであるかの文字列比較を正確に行えるようになります。
サンプルコードでは、属性の順序が統一されていないXML文字列を DOMDocument オブジェクトとして読み込んでいます。その後、C14N() メソッドを呼び出すことで、XML全体を正規化しています。変換後の文字列では、各要素の属性が属性名でアルファベット順にソートされ、不要な空白や改行が標準化されていることが確認できます。
このメソッドは、引数で動作を調整できます。例えば、第2引数の $with_comments を true に設定すると、結果にXMLコメントを含めることができます。引数を指定しない場合、コメントは除去されます。処理が成功すると正規化された文字列(string)を返し、失敗した場合は false を返します。
C14N()メソッドは、デフォルトではXML内のコメントを除去します。コメントを保持したい場合は、第二引数にtrueを明示的に指定する必要があります。また、このメソッドやloadXML()は処理に失敗するとfalseを返す可能性があるため、サンプルコードのように戻り値を必ずチェックし、エラー処理を記述することが重要です。出力されるXML文字列のエンコーディングはUTF-8に統一されますので、元のXMLが異なる文字コードの場合は注意してください。大きなXML文書を扱う際は、処理に時間がかかりメモリを消費する可能性がある点も考慮しましょう。
PHPでXMLを排他的正規化する
1<?php 2 3/** 4 * XML文字列を排他的正規化 (Exclusive C14N 1.0) して返す関数。 5 * 6 * XMLの正規化(Canonicalization, C14N)とは、論理的に同じXML文書が 7 * 異なるテキスト表現(属性の順序、空白など)を持つ場合でも、 8 * 唯一のバイト表現に変換するプロセスです。これは電子署名などで重要となります。 9 * キーワードの C14N 1.1 に最も近い機能として、排他的正規化 (exclusive) を使用します。 10 * 11 * @param string $xmlString 正規化対象のXML文字列 12 * @return string|false 正規化されたXML文字列。失敗した場合は false 13 */ 14function getCanonicalXml(string $xmlString): string|false 15{ 16 // DOMDocumentオブジェクトを作成します。 17 $document = new \DOMDocument(); 18 19 // XML文字列をDOMオブジェクトに読み込みます。 20 if (!$document->loadXML($xmlString)) { 21 // XMLのパースに失敗した場合 22 return false; 23 } 24 25 // C14N() メソッドでXMLを正規化します。 26 // PHP 8の名前付き引数を使用して、各パラメータの意味を明確にしています。 27 // exclusive: true - 排他的正規化(Exclusive C14N)を有効にします。 28 // 正規化対象のノードセットで使用されている名前空間のみを出力します。 29 // with_comments: true - XML内のコメントを正規化後の出力に含めます。 30 return $document->C14N( 31 exclusive: true, 32 with_comments: true 33 ); 34} 35 36// --- サンプル実行 --- 37 38// 正規化前のXMLデータ。 39// 属性の順序が異なり、余分な空白やコメントが含まれています。 40$originalXml = <<<XML 41<?xml version="1.0" encoding="UTF-8"?> 42<!-- Document comment --> 43<root xmlns="http://www.example.com/ns/default" xmlns:pref="http://www.example.com/ns/prefix"> 44 <element 45 attr2="value2" 46 attr1="value1" > 47 Some Text 48 </element> 49 <pref:item>Prefixed Item</pref:item> 50</root> 51XML; 52 53// 関数を呼び出してXMLを正規化します。 54$canonicalXml = getCanonicalXml($originalXml); 55 56if ($canonicalXml !== false) { 57 header('Content-Type: text/plain; charset=UTF-8'); 58 echo "--- 正規化前のXML ---\n"; 59 echo $originalXml . "\n\n"; 60 echo "--- 正規化後のXML (排他的正規化/コメントあり) ---\n"; 61 echo $canonicalXml . "\n"; 62} else { 63 echo "XMLの正規化に失敗しました。\n"; 64} 65 66?>
このPHPサンプルコードは、DOMDocumentクラスを用いてXML文字列を「排他的正規化(Exclusive C14N)」する方法を示しています。XMLの正規化とは、属性の順序や空白の有無といったテキスト表現の違いを吸収し、論理的に等価なXML文書を常に同一の形式に変換する処理のことです。この処理は、XMLデータに対する電子署名の検証など、データの同一性を厳密に確認する場面で重要となります。
コードの中核をなすのはDOMDocumentオブジェクトのC14N()メソッドです。このメソッドは、引数に基づいてXMLの正規化を行い、結果を文字列として返します。第一引数exclusiveにtrueを指定すると排他的正規化が実行され、処理対象のノードで使用されている名前空間だけが出力に含まれます。第二引数with_commentsにtrueを指定すると、元のXMLに含まれるコメントを正規化後の出力にも残します。PHP 8の名前付き引数を使用することで、各引数がどのような役割を持つのかが直感的に理解しやすくなっています。
このメソッドは、処理に成功すると正規化されたXML文字列を返し、何らかの理由で失敗した場合はfalseを返します。サンプルを実行すると、属性の順序が整えられ、空白が統一された一貫した形式のXML文字列が得られることを確認できます。
このC14Nメソッドは、XML正規化の標準であるC14N 1.0と排他的正規化1.0を実装しており、C14N 1.1には直接対応していない点に注意が必要です。loadXML()は不正なXML文字列を読み込むと失敗するため、戻り値のチェックは必須です。C14N()自体も失敗時にfalseを返すため、関数呼び出し後は必ずfalseでないかを確認し、適切にエラー処理を行ってください。exclusive引数をtrueにすると、電子署名などで重要となる排他的正規化が行われます。また、with_commentsをtrueにするとコメントが残りますが、署名の対象からはコメントを除外することが多いため、要件に応じてfalseに設定することも検討しましょう。