[PHP]xml_parser_create_nsとは?XML名前空間に対応したパーサーの使い方を徹底解説

PHP

はじめに

前回の記事では、ExpatベースのXMLパーサーを生成する基本の関数 xml_parser_create() を解説し、その中で名前空間に対応した兄弟関数 xml_parser_create_ns() の存在に触れました。今回は、この xml_parser_create_ns() を正面から取り上げます。

XML文書では、<ns:item> のように名前空間プレフィックスが付いた要素がしばしば登場します。SOAP APIのレスポンス、RSS/Atomフィード、SVG、あるいは複数のスキーマを組み合わせた業界標準フォーマットなど、実務で名前空間に遭遇する機会は少なくありません。xml_parser_create() では、こうしたプレフィックス付きの要素名がそのまま1つの文字列("ns:item")として渡されてしまいますが、xml_parser_create_ns() を使うことで、プレフィックスと要素名を分離して扱えるようになります。本記事では基本的な使い方から実践的な活用例まで詳しく解説します。


関数概要

項目内容
関数名xml_parser_create_ns()
所属拡張XML Parser拡張(Expatベース、標準で有効)
シグネチャxml_parser_create_ns(?string $encoding = null, string $separator = ":"): XMLParser
引数1$encoding — 入力データのエンコーディング(省略時は自動判定)
引数2$separator — 名前空間URIと要素名を連結する際の区切り文字(デフォルトは":"
戻り値名前空間対応のXMLパーサーインスタンス(XMLParserオブジェクト)
対応バージョンPHP 4.0.5以降
通常版との違い要素名が「名前空間URI + 区切り文字 + ローカル名」の形式で渡される
関連関数xml_parser_create()(通常版)、xml_set_start_namespace_decl_handler()(名前空間宣言の検知)

名前空間対応の全体像(イメージ図)

  名前空間を含むXML
  <ns:item xmlns:ns="http://example.com/schema">値</ns:item>
              │
   ┌──────────┴───────────────────────┐
   │                                     │
   ▼                                     ▼
  xml_parser_create()                xml_parser_create_ns()
  (通常版)                            (名前空間対応版)
   │                                     │
   ▼                                     ▼
  要素名 = "NS:ITEM"                    要素名 = "http://example.com/schema:ITEM"
  (プレフィックスと要素名が             (URIとローカル名が区切り文字で
   区別されず1つの文字列)                連結された形式)

ポイントは、xml_parser_create_ns() を使うと、プレフィックス(ns)そのものではなく、そのプレフィックスが指す「名前空間URI」を基準に要素が識別されるという点です。これはXML名前空間の仕様上、プレフィックス自体には意味がなく(文書ごとに自由に付け替え可能)、URIこそが名前空間を一意に識別する情報だからです。


実践サンプル7選

例1:基本的な使い方(通常版との違いを確認)

<?php

class NamespaceParserDemo
{
    public function parseWithNamespace(string $xml): array
    {
        $tags = [];
        $parser = xml_parser_create_ns();

        xml_set_element_handler(
            $parser,
            function ($p, $name) use (&$tags) {
                $tags[] = $name;
            },
            fn () => null
        );

        xml_parse($parser, $xml, true);
        xml_parser_free($parser);

        return $tags;
    }
}

$demo = new NamespaceParserDemo();
$xml = '<ns:root xmlns:ns="http://example.com/ns"><ns:item>value</ns:item></ns:root>';
print_r($demo->parseWithNamespace($xml));
// ["http://example.com/ns:ROOT", "http://example.com/ns:ITEM"]

例2:区切り文字をカスタマイズして扱いやすくするクラス

<?php

class CustomSeparatorNamespaceParser
{
    /**
     * デフォルトの":"区切りだとURIに含まれるコロン(http://など)と
     * 紛らわしいため、専用の区切り文字に変更する
     */
    public function parse(string $xml): array
    {
        $tags = [];
        // "|" のような、URIに含まれにくい文字を区切りに指定する
        $parser = xml_parser_create_ns(null, '|');

        xml_set_element_handler(
            $parser,
            function ($p, $name) use (&$tags) {
                $tags[] = $name;
            },
            fn () => null
        );

        xml_parse($parser, $xml, true);
        xml_parser_free($parser);

        return $tags;
    }
}

$parser = new CustomSeparatorNamespaceParser();
print_r($parser->parse('<ns:root xmlns:ns="http://example.com/ns"><ns:item/></ns:root>'));
// ["http://example.com/ns|ROOT", "http://example.com/ns|ITEM"]

例3:URIとローカル名を分離して扱うヘルパークラス

<?php

class NamespaceTagSplitter
{
    /**
     * xml_parser_create_ns()が返す "URI:ローカル名" 形式の文字列を、
     * 扱いやすいように分割するヘルパー
     */
    public function splitTagName(string $qualifiedName, string $separator = ':'): array
    {
        $pos = strrpos($qualifiedName, $separator);

        if ($pos === false) {
            // 名前空間を持たない要素の場合
            return ['namespace' => null, 'localName' => $qualifiedName];
        }

        return [
            'namespace' => substr($qualifiedName, 0, $pos),
            'localName' => substr($qualifiedName, $pos + 1),
        ];
    }
}

$splitter = new NamespaceTagSplitter();
print_r($splitter->splitTagName('http://example.com/ns:ITEM'));
// ['namespace' => 'http://example.com/ns', 'localName' => 'ITEM']

例4:名前空間宣言そのものを検知するハンドラの追加

<?php

class NamespaceDeclarationTracker
{
    private array $declarations = [];

    /**
     * xml_set_start_namespace_decl_handler()を使うことで、
     * "xmlns:prefix=..." の宣言自体を捕捉できる
     */
    public function trackDeclarations(string $xml): array
    {
        $this->declarations = [];
        $parser = xml_parser_create_ns();

        xml_set_start_namespace_decl_handler(
            $parser,
            function ($p, $prefix, $uri) {
                $this->declarations[] = ['prefix' => $prefix, 'uri' => $uri];
            }
        );

        xml_parse($parser, $xml, true);
        xml_parser_free($parser);

        return $this->declarations;
    }
}

$tracker = new NamespaceDeclarationTracker();
print_r($tracker->trackDeclarations(
    '<root xmlns:ns1="http://example.com/a" xmlns:ns2="http://example.com/b"><ns1:item/></root>'
));

例5:SOAPレスポンスから特定の名前空間の要素だけを抽出するクラス

<?php

class SoapNamespaceFilter
{
    private const TARGET_NS = 'http://schemas.xmlsoap.org/soap/envelope/';

    /**
     * SOAP Envelope名前空間に属する要素のみを抽出する
     */
    public function extractSoapElements(string $xml): array
    {
        $found = [];
        $parser = xml_parser_create_ns();

        xml_set_element_handler(
            $parser,
            function ($p, $name) use (&$found) {
                if (str_starts_with($name, self::TARGET_NS . ':')) {
                    $found[] = $name;
                }
            },
            fn () => null
        );

        xml_parse($parser, $xml, true);
        xml_parser_free($parser);

        return $found;
    }
}

$filter = new SoapNamespaceFilter();
$soap = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><result>ok</result></soap:Body></soap:Envelope>';
print_r($filter->extractSoapElements($soap));

例6:複数の名前空間が混在するAtomフィードを解析するクラス

<?php

class MultiNamespaceFeedParser
{
    /**
     * デフォルト名前空間とプレフィックス付き名前空間が
     * 混在するフィードでも、URIベースで一貫して要素を識別できる
     */
    public function parseFeed(string $xml): array
    {
        $entries = [];
        $parser = xml_parser_create_ns();

        xml_set_character_data_handler($parser, function ($p, $data) use (&$entries) {
            $trimmed = trim($data);
            if ($trimmed !== '') {
                $entries[] = $trimmed;
            }
        });

        xml_parse($parser, $xml, true);
        xml_parser_free($parser);

        return $entries;
    }
}

$parser = new MultiNamespaceFeedParser();
$atom = '<feed xmlns="http://www.w3.org/2005/Atom"><entry><title>記事タイトル</title></entry></feed>';
print_r($parser->parseFeed($atom));

例7:通常版と名前空間対応版のパフォーマンスと用途の使い分け

<?php

class ParserSelector
{
    /**
     * XMLに名前空間宣言(xmlns)が含まれているかどうかを簡易判定し、
     * 適切なパーサーを選択するファクトリー
     */
    public function selectParser(string $xml): XMLParser
    {
        $hasNamespace = str_contains($xml, 'xmlns');

        return $hasNamespace
            ? xml_parser_create_ns()
            : xml_parser_create();
    }
}

$selector = new ParserSelector();
$parser1 = $selector->selectParser('<root xmlns:ns="http://example.com"><ns:item/></root>');
var_dump(get_class($parser1));
$parser2 = $selector->selectParser('<root><item/></root>');
var_dump(get_class($parser2));
xml_parser_free($parser1);
xml_parser_free($parser2);

関連関数との比較

関数役割xml_parser_create_nsとの違い
xml_parser_create_ns()名前空間対応のパーサーを生成本記事の対象。要素名がURIベースで識別される
xml_parser_create()名前空間非対応のパーサーを生成プレフィックス込みの文字列がそのまま渡される
xml_set_start_namespace_decl_handler()名前空間宣言の開始を検知するハンドラを登録xmlns:prefix=...宣言自体を捕捉する専用の仕組み
xml_set_end_namespace_decl_handler()名前空間宣言のスコープ終了を検知するハンドラを登録宣言が有効な範囲の終わりを検知する
DOMDocument + DOMXPath名前空間を考慮したXPathクエリでXMLを操作より高機能だが、SAX方式の軽量さとは異なるアプローチ

よくある落とし穴(注意点)

  1. 区切り文字のデフォルト値(:)がURIと衝突しやすい 名前空間URIには http:// のようにコロンが含まれることが多いため、デフォルトの区切り文字 : のままだと、要素名の文字列が読みにくく、パースミスの原因にもなり得ます。| など、URIに含まれにくい文字への変更を検討しましょう(例2を参照)。
  2. プレフィックスではなくURIで要素を識別する必要がある xml_parser_create() の感覚のまま、"ns:item" のようなプレフィックス文字列で判定しようとすると、名前空間対応版では正しくマッチしません。必ず実際の名前空間URIを基準に条件分岐を組み立てましょう(例5を参照)。
  3. デフォルト名前空間(xmlns="...")の扱いを見落とす xmlns:prefix="..." のようなプレフィックス付き宣言だけでなく、プレフィックスを持たない「デフォルト名前空間」(xmlns="...")も存在します。この場合も要素名にはURIが付与されるため、双方のパターンを考慮した実装が必要です(例6を参照)。
  4. すべてのXMLに名前空間対応版が必要なわけではない 名前空間を持たないシンプルなXMLに対して xml_parser_create_ns() を使うと、余計な処理オーバーヘッドが発生する可能性があります。文書の特性に応じて xml_parser_create() と使い分けましょう(例7を参照)。
  5. PHP 8.0以降での型変更に注意する 他のXML関連関数と同様、PHP 8.0以降で戻り値がリソース型から XMLParser オブジェクトに変更されています。型チェックを行う古いコードには注意が必要です。

まとめ

観点まとめ
何をする関数かXML名前空間に対応したExpatパーサーインスタンスを生成する
主な用途SOAP APIレスポンス、Atom/RSSフィード、複数スキーマが混在するXMLの解析
通常版との違い要素名がプレフィックスではなく、名前空間URIを基準に識別される
区切り文字第2引数でカスタマイズ可能(URIとの衝突を避けるため変更推奨)
注意点URIベースでの要素識別、デフォルト名前空間の考慮、不要な場合は通常版を選ぶこと

xml_parser_create_ns() は、名前空間を含む複雑なXML文書を正確に解析するために欠かせない関数です。プレフィックスではなくURIを基準に要素を識別するというXML名前空間の本質的な仕組みを理解した上で活用することで、SOAP APIやフィードデータなど、実務でよく登場する名前空間付きXMLの処理を正確に実装できます。

タイトルとURLをコピーしました