[PHP]xml_parser_set_optionとは?XMLパーサーの挙動をカスタマイズする方法を徹底解説

PHP

はじめに

前回の記事では、XMLパーサーの現在の設定値を取得する xml_parser_get_option() を解説しました。今回はその対となる、設定を実際に変更するための関数、xml_parser_set_option() を正面から取り上げます。

これまでの記事の中でも、要素名の大文字小文字変換を制御したり、出力エンコーディングを指定したりする場面で、この関数がたびたび登場してきました。xml_parser_set_option() は、ExpatベースのXMLパーサーのデフォルトの挙動を、プロジェクトの要件に合わせて細かく調整するための重要な関数です。本記事では、設定可能な4つのオプションそれぞれの意味と、実践的な活用パターンを詳しく解説します。


関数概要

項目内容
関数名xml_parser_set_option()
所属拡張XML Parser拡張(Expatベース、標準で有効)
シグネチャxml_parser_set_option(XMLParser $parser, int $option, string|int|bool $value): bool
引数1$parserxml_parser_create()等で生成したパーサーインスタンス
引数2$option — 設定したいオプションの定数(XML_OPTION_*
引数3$value — 設定する値
戻り値成功時に true
対応バージョンPHP 4以降(PHP 8.0以降は引数の型がXMLParserオブジェクトに変更)
対になる関数xml_parser_get_option()
呼び出しタイミングxml_parse()を呼び出すに設定するのが基本

4つの設定可能なオプション(イメージ図)

  xml_parser_set_option($parser, オプション定数, 値)
              │
   ┌──────────┼──────────┬──────────────┐
   ▼          ▼          ▼              ▼
XML_OPTION_  XML_OPTION_  XML_OPTION_    XML_OPTION_
CASE_FOLDING TARGET_      SKIP_TAGSTART  SKIP_WHITE
             ENCODING
   │          │          │              │
   ▼          ▼          ▼              ▼
要素名を     出力文字列   タグ名の先頭    要素間の空白
大文字に     のエンコー   何文字かを      文字のみの
変換するか   ディングを   スキップする    データを
(true/false) 指定する     文字数         無視するか
                                        (true/false)

ポイントは、xml_parser_set_option() で設定できるオプションがこの4種類に限定されているという点です。それぞれが解析結果の見た目や扱いやすさに直接影響するため、用途に応じて適切に組み合わせることが重要です。


実践サンプル7選

例1:基本的な使い方(case foldingの無効化)

<?php

class CaseFoldingDemo
{
    public function disableCaseFolding(XMLParser $parser): void
    {
        // false を指定すると、要素名の大文字変換が行われなくなる
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
    }
}

$parser = xml_parser_create();
$demo = new CaseFoldingDemo();
$demo->disableCaseFolding($parser);

$tags = [];
xml_set_element_handler($parser, function ($p, $name) use (&$tags) { $tags[] = $name; }, fn () => null);
xml_parse($parser, '<MyRoot><MyItem/></MyRoot>', true);
xml_parser_free($parser);

print_r($tags); // ['MyRoot', 'MyItem'] (大文字小文字が保持される)

例2:出力エンコーディングを統一するクラス

<?php

class OutputEncodingNormalizer
{
    /**
     * 入力データのエンコーディングに関わらず、
     * ハンドラに渡される文字列を常にUTF-8に統一する
     */
    public function createNormalizingParser(?string $inputEncoding = null): XMLParser
    {
        $parser = xml_parser_create($inputEncoding);
        xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
        return $parser;
    }
}

$normalizer = new OutputEncodingNormalizer();
$parser = $normalizer->createNormalizingParser('ISO-8859-1');

$values = [];
xml_set_character_data_handler($parser, function ($p, $data) use (&$values) {
    if (trim($data) !== '') $values[] = $data;
});
xml_parse($parser, "<root>caf\xe9</root>", true);
xml_parser_free($parser);
print_r($values);

例3:要素間の空白を無視して純粋なデータのみ扱うクラス

<?php

class WhitespaceSkippingParser
{
    /**
     * インデントや改行のためだけの空白文字を、
     * データとして扱わないように設定する
     */
    public function createCleanParser(): XMLParser
    {
        $parser = xml_parser_create();
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, true);
        return $parser;
    }
}

$factory = new WhitespaceSkippingParser();
$parser = $factory->createCleanParser();

$dataEvents = 0;
xml_set_character_data_handler($parser, function ($p, $data) use (&$dataEvents) {
    $dataEvents++;
});
// インデントや改行が多いXMLでも、余計な空白イベントが発生しにくくなる
xml_parse($parser, "<root>\n  <item>\n    value\n  </item>\n</root>", true);
xml_parser_free($parser);
echo "文字データイベント発生回数: {$dataEvents}" . PHP_EOL;

例4:複数オプションをまとめて設定するファクトリークラス

<?php

class ConfiguredParserFactory
{
    /**
     * よく使う組み合わせをまとめて設定する
     * ファクトリーメソッドパターンの実装
     */
    public static function createForCleanDataExtraction(): XMLParser
    {
        $parser = xml_parser_create('UTF-8');

        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, true);
        xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');

        return $parser;
    }
}

$parser = ConfiguredParserFactory::createForCleanDataExtraction();
xml_parse($parser, '<Root><Name>太郎</Name></Root>', true);
xml_parser_free($parser);

例5:設定変更が失敗した場合を検知するエラーハンドリング

<?php

class SafeOptionSetter
{
    /**
     * xml_parser_set_option()の戻り値をチェックし、
     * 設定に失敗した場合は例外をスローする
     */
    public function setOptionStrict(XMLParser $parser, int $option, mixed $value): void
    {
        if (!xml_parser_set_option($parser, $option, $value)) {
            throw new RuntimeException("オプション設定に失敗しました: option={$option}");
        }
    }
}

$parser = xml_parser_create();
$setter = new SafeOptionSetter();

try {
    $setter->setOptionStrict($parser, XML_OPTION_CASE_FOLDING, false);
    echo '設定に成功しました' . PHP_EOL;
} catch (RuntimeException $e) {
    echo 'エラー: ' . $e->getMessage() . PHP_EOL;
} finally {
    xml_parser_free($parser);
}

例6:解析開始後にオプションを変更しても反映されないことを確認するデモ

<?php

class LateOptionChangeDemonstrator
{
    /**
     * xml_parse()の呼び出し後にオプションを変更しても、
     * 既に処理済みの部分には影響しないことを確認する
     */
    public function demonstrate(): void
    {
        $parser = xml_parser_create();
        $tags = [];

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

                // 最初の要素処理後にcase foldingを変更してみる
                if (count($tags) === 1) {
                    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
                }
            },
            fn () => null
        );

        xml_parse($parser, '<Root><Item/></Root>', true);
        xml_parser_free($parser);

        print_r($tags);
        // 挙動は実装依存のため、必ずしも期待通りにならない可能性がある点に注意
    }
}

$demo = new LateOptionChangeDemonstrator();
$demo->demonstrate();

例7:設定オプションをテスト可能な形で注入するクラス設計

<?php

class ConfigurableXmlReader
{
    private array $options;

    /**
     * 設定内容を外部から注入できるようにすることで、
     * テスト時に異なる設定パターンを容易に検証できるようにする
     */
    public function __construct(array $options = [])
    {
        $this->options = $options + [
            XML_OPTION_CASE_FOLDING => true,
            XML_OPTION_SKIP_WHITE   => false,
        ];
    }

    public function read(string $xml): array
    {
        $parser = xml_parser_create();

        foreach ($this->options as $option => $value) {
            xml_parser_set_option($parser, $option, $value);
        }

        $tags = [];
        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;
    }
}

$reader = new ConfigurableXmlReader([XML_OPTION_CASE_FOLDING => false]);
print_r($reader->read('<MyRoot><MyItem/></MyRoot>'));

関連関数との比較

関数役割xml_parser_set_optionとの違い
xml_parser_set_option()パーサーのオプション設定を変更する本記事の対象。設定を書き込む側の関数
xml_parser_get_option()パーサーの現在のオプション設定を取得する設定を読み出すだけの、対になる関数
xml_parser_create()パーサーインスタンスを生成する生成直後はデフォルト設定になっており、変更にはこの関数を使う
mb_internal_encoding()PHP全体の内部文字エンコーディングを設定対象がXMLパーサー個別ではなく、PHP実行環境全体である点が異なる
ini_set()php.ini相当の設定を実行時に変更より広範なPHP設定を対象とし、XMLパーサー専用のオプションではない

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

  1. xml_parse() の呼び出し後に設定しても意味がない場合がある 基本的には、解析処理を開始する前にすべてのオプションを設定しておくのが安全です。解析途中でオプションを変更した場合の挙動は保証されないため、避けるべきです(例6を参照)。
  2. 設定可能なオプションは4種類に限定されている XML_OPTION_CASE_FOLDING, XML_OPTION_TARGET_ENCODING, XML_OPTION_SKIP_TAGSTART, XML_OPTION_SKIP_WHITE 以外の定数を指定すると、正しく動作しません。
  3. XML_OPTION_TARGET_ENCODINGに対応していないエンコーディング名を指定する サポートされていないエンコーディング名を指定すると、設定に失敗したり、意図しない文字化けが発生したりすることがあります。事前に対応エンコーディングを確認しましょう。
  4. 戻り値のチェックを省略する xml_parser_set_option() の戻り値(成功/失敗)を確認せずに処理を進めると、意図した設定が反映されないまま解析が進んでしまう可能性があります(例5を参照)。
  5. XML_OPTION_SKIP_TAGSTARTの意味を誤解する このオプションは「タグ名の先頭何文字をスキップするか」を指定するもので、真偽値ではなく整数を指定します。他の真偽値系オプションと混同しないよう注意しましょう。

まとめ

観点まとめ
何をする関数かXMLパーサーインスタンスのオプション設定を変更する
主な用途要素名の大文字小文字変換の制御、出力エンコーディングの統一、空白データの無視
設定可能なオプションCASE_FOLDING, TARGET_ENCODING, SKIP_TAGSTART, SKIP_WHITEの4種類
対になる関数xml_parser_get_option()(設定を確認する)
注意点解析開始前に設定すること、対応するオプションの範囲、戻り値チェックの徹底

xml_parser_set_option() は、ExpatベースのXMLパーサーのデフォルトの挙動を、実際のプロジェクト要件に合わせて調整するための重要な関数です。これまでの記事で紹介してきたcase foldingやエンコーディング調整の実例を通じて、この関数がXML処理の細部を制御する上でいかに実用的かを理解いただけたのではないでしょうか。

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