[PHP]xml_parse_into_structとは?XMLを構造化配列に一括変換する方法を徹底解説

PHP

はじめに

前回の記事では、コールバック関数を個別に登録しながらXMLを解析する xml_parse() を解説しました。ハンドラを自分で書いて要素の開始・終了イベントを処理するSAX方式は柔軟である一方、単純に「XMLの内容を配列として取り出したいだけ」という場面では、やや準備が煩雑に感じられることもあります。

そこで便利なのが xml_parse_into_struct() 関数です。この関数は、xml_parse() のようにハンドラを個別に登録する必要がなく、XML文書を1回の呼び出しで「フラットな構造を持つ配列」に変換してくれるという、お手軽さが特徴の関数です。本記事では基本的な使い方から、出力される配列構造の読み解き方、実践的な活用パターンまで詳しく解説します。


関数概要

項目内容
関数名xml_parse_into_struct()
所属拡張XML Parser拡張(Expatベース、標準で有効)
シグネチャxml_parse_into_struct(XMLParser $parser, string $data, array &$values, array &$index = null): int
引数1$parserxml_parser_create() で生成したパーサーインスタンス
引数2$data — 解析対象のXML文字列
引数3$values — 解析結果が格納される配列(参照渡し)
引数4$index — タグ名ごとの$values内インデックスが格納される配列(参照渡し、省略可)
戻り値成功時に 1、失敗時に 0
対応バージョンPHP 4以降(PHP 8.0以降は引数の型がXMLParserオブジェクトに変更)
出力形式ネストしないフラットな配列(タグ・レベル・タイプなどの情報を持つ)

変換の流れ(イメージ図)

  XML文書
  <person>
    <name>太郎</name>
    <age>30</age>
  </person>
              │
              ▼
     xml_parse_into_struct($parser, $xml, $values, $index)
              │
   ┌──────────┴───────────────────────────┐
   │ $values にはフラットな配列として          │
   │ 各要素の "tag", "type", "level", "value" が │
   │ 順番に格納される                          │
   └──────────┬───────────────────────────┘
              ▼
  $values = [
    ['tag' => 'PERSON', 'type' => 'open',    'level' => 1],
    ['tag' => 'NAME',   'type' => 'complete', 'level' => 2, 'value' => '太郎'],
    ['tag' => 'AGE',    'type' => 'complete', 'level' => 2, 'value' => '30'],
    ['tag' => 'PERSON', 'type' => 'close',    'level' => 1],
  ]

  $index = [
    'PERSON' => [0, 3],
    'NAME'   => [1],
    'AGE'    => [2],
  ]

ポイントは、xml_parse_into_struct() の出力がネストした階層構造ではなく、フラットな配列のリストであるという点です。各要素には level(階層の深さ)というキーが含まれているため、この値を使えば元の階層構造を再構築することも可能ですが、そのままでは「親要素の中の子要素」というアクセスは直接的にはできません。


実践サンプル7選

例1:基本的な使い方

<?php

class BasicStructParser
{
    public function parse(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values, $index);
        xml_parser_free($parser);

        return ['values' => $values, 'index' => $index];
    }
}

$parser = new BasicStructParser();
$result = $parser->parse('<person><name>太郎</name><age>30</age></person>');
print_r($result['values']);

例2:indexを使って特定タグの値だけを素早く取り出すクラス

<?php

class TagValueExtractor
{
    /**
     * 第4引数の$indexを利用することで、
     * 特定タグの値に高速にアクセスできる
     */
    public function extractTagValue(string $xml, string $tagName): ?string
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values, $index);
        xml_parser_free($parser);

        $upperTag = strtoupper($tagName); // タグ名は大文字化されて格納される
        if (!isset($index[$upperTag])) {
            return null;
        }

        $firstIndex = $index[$upperTag][0];
        return $values[$firstIndex]['value'] ?? null;
    }
}

$extractor = new TagValueExtractor();
echo $extractor->extractTagValue('<person><name>花子</name><age>25</age></person>', 'name') . PHP_EOL;

例3:フラットな配列から連想配列(単純な構造向け)に変換するクラス

<?php

class SimpleStructToArrayConverter
{
    /**
     * ネストしない単純なXML構造を前提に、
     * "タグ名 => 値" のシンプルな連想配列に変換する
     */
    public function convert(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values);
        xml_parser_free($parser);

        $result = [];
        foreach ($values as $item) {
            if ($item['type'] === 'complete') {
                $result[strtolower($item['tag'])] = $item['value'] ?? '';
            }
        }

        return $result;
    }
}

$converter = new SimpleStructToArrayConverter();
print_r($converter->convert('<config><host>localhost</host><port>8080</port></config>'));
// ['host' => 'localhost', 'port' => '8080']

例4:属性情報も含めて解析結果を利用するクラス

<?php

class AttributeAwareStructParser
{
    /**
     * openタイプの要素にはattributesキーが含まれることがあるため、
     * それも含めて情報を収集する
     */
    public function extractWithAttributes(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values);
        xml_parser_free($parser);

        $result = [];
        foreach ($values as $item) {
            if (isset($item['attributes'])) {
                $result[] = [
                    'tag'        => $item['tag'],
                    'attributes' => $item['attributes'],
                ];
            }
        }

        return $result;
    }
}

$parser = new AttributeAwareStructParser();
print_r($parser->extractWithAttributes('<item id="101" category="book">タイトル</item>'));

例5:case foldingを無効化して大文字小文字を保持するクラス

<?php

class CasePreservingStructParser
{
    /**
     * xml_parse()と同様、xml_parser_set_option()で
     * case foldingを無効化すれば元のタグ名を保持できる
     */
    public function parse(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);

        xml_parse_into_struct($parser, $xml, $values);
        xml_parser_free($parser);

        return $values;
    }
}

$parser = new CasePreservingStructParser();
$result = $parser->parse('<Person><FirstName>Taro</FirstName></Person>');
print_r(array_column($result, 'tag'));
// ['Person', 'FirstName', 'FirstName', 'Person'] (大文字小文字が保持される)

例6:levelを利用して簡易的な階層構造を再構築するクラス

<?php

class NestedStructureRebuilder
{
    /**
     * 各要素の"level"情報を使って、
     * フラットな配列から簡易的な階層構造を復元する
     */
    public function rebuild(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values);
        xml_parser_free($parser);

        $stack = [[]];

        foreach ($values as $item) {
            if ($item['type'] === 'open') {
                $stack[] = [];
            } elseif ($item['type'] === 'close') {
                $child = array_pop($stack);
                $stack[count($stack) - 1][strtolower($item['tag'])] = $child;
            } elseif ($item['type'] === 'complete') {
                $stack[count($stack) - 1][strtolower($item['tag'])] = $item['value'] ?? '';
            }
        }

        return $stack[0];
    }
}

$rebuilder = new NestedStructureRebuilder();
print_r($rebuilder->rebuild('<order><customer><name>太郎</name></customer><total>5000</total></order>'));

例7:外部フィードのXMLを一括で読み込むリーダークラス

<?php

class FeedItemReader
{
    /**
     * RSSフィードのような単純な繰り返し構造を持つXMLから、
     * item要素ごとの値を抽出する(簡易版のため厳密なRSSパーサーの代替ではない点に注意)
     */
    public function readItems(string $xml): array
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $values);
        xml_parser_free($parser);

        $items = [];
        $current = [];
        $inItem = false;

        foreach ($values as $entry) {
            if ($entry['tag'] === 'ITEM' && $entry['type'] === 'open') {
                $inItem = true;
                $current = [];
            } elseif ($entry['tag'] === 'ITEM' && $entry['type'] === 'close') {
                $inItem = false;
                $items[] = $current;
            } elseif ($inItem && $entry['type'] === 'complete') {
                $current[strtolower($entry['tag'])] = $entry['value'] ?? '';
            }
        }

        return $items;
    }
}

$reader = new FeedItemReader();
$xml = '<items><item><title>記事A</title><link>https://example.com/a</link></item></items>';
print_r($reader->readItems($xml));

関連関数との比較

関数/クラス役割xml_parse_into_structとの違い
xml_parse_into_struct()XMLを1回の呼び出しでフラットな配列に変換本記事の対象。ハンドラの個別登録が不要で手軽
xml_parse()ハンドラを登録してイベント駆動で解析より柔軟だが、コールバックの実装が必要
simplexml_load_string()XMLをオブジェクトツリーとして読み込む階層構造を直感的にプロパティアクセスできる
DOMDocumentXMLをDOMツリーとして読み込み・操作するより高機能だが、その分コード量も増える
json_decode()JSON文字列を配列/オブジェクトに変換対象のフォーマットが異なるが、「構造化データへの変換」という目的は共通

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

  1. 出力がフラットな配列であり、階層構造ではない xml_parse_into_struct() の結果はネストした配列ではなく、level キーを持つフラットなリストです。複雑な階層構造を持つXMLをそのままオブジェクトのように扱いたい場合は、SimpleXMLElement の方が適していることがあります。
  2. タグ名がデフォルトで大文字に変換される xml_parse() と同様、Expatパーサーのデフォルト設定ではタグ名が大文字化されます。元の大小文字を保持したい場合は XML_OPTION_CASE_FOLDING を無効化する必要があります(例5を参照)。
  3. typeの値(open/close/complete/cdata)の意味を正しく理解する 要素に子要素やテキストが存在するかどうかによって type の値が変わります。特にテキストを含まない空要素や、開始・終了タグが分かれているケースなど、type の分岐を正しく処理しないと値の取得漏れにつながります(例6・例7のように open/close/complete を意識した実装が必要です)。
  4. 属性を持つ要素の扱いを忘れる 要素が属性を持つ場合、$values の該当エントリに attributes キーが追加されます。属性情報も取得したい場合は、isset($item['attributes']) のチェックを忘れずに行いましょう(例4を参照)。
  5. 大容量XMLに対してはxml_parse()より不向きな場合がある xml_parse_into_struct() は結果をすべて配列としてメモリ上に保持するため、非常に大きなXMLファイルを扱う場合は、xml_parse() を使ったストリーミング処理(コールバックベースの逐次処理)の方がメモリ効率に優れます。

まとめ

観点まとめ
何をする関数かXML文書を1回の呼び出しで、フラットな構造を持つ配列に変換する
主な用途単純な構造のXML(設定ファイル、フィードなど)の手軽な読み込み
xml_parse()との違いハンドラの個別登録が不要で、より簡潔に書ける
出力の特徴ネストしないフラットな配列。level情報を使えば階層を再構築できる
注意点タグ名の大文字化、typeの分岐処理、属性情報の扱い、大容量データでのメモリ効率

xml_parse_into_struct() は、xml_parse() に比べて手軽にXMLを配列として扱いたい場合に便利な関数です。ただし出力がフラットな構造である点を理解し、必要に応じて level 情報を使った階層再構築のロジックを実装することで、単純な設定ファイルやフィードデータの読み込みに効果的に活用できます。

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