PHP5でのSimpleXMLの整形方法とか

投稿日:

PHP5でXMLを作る時って大体SimpleXML使うじゃないですか。使いますよね?
で、SimpleXMLでXMLを作るのって全然楽なんですけど、その作ったXMLファイルを修正しやすい様に見やすくしてくれって言われたのが今回のお話です。

2016/12/21 追記

以下のライブラリを使うと、若干簡単に書けるかもしれません。
https://github.com/shimabox/SMBArrayto
ページ下部にサンプルを書きました。

XMLの作成

まずはXMLの作成から。
こんなXMLを作りたいとします。

<?xml version="1.0" encoding="UTF-8" ?>
<hoge>
    <users>
        <user id='1'>
            <name>たろう</name>
            <music>NumberGirl</music>
            <sex>boy</sex>
        </user>
        <user id='2'>
            <name>はなこ</name>
            <music>ラッツ&スター</music>
            <sex>girl</sex>
        </user>
    </users>
</hoge> 

で、これをPHPで書くとこんな感じ。

<?php
//XMLのルートタグとXML宣言
$root = '<?xml version="1.0" encoding="UTF-8" ?><hoge></hoge>';
//XML作成開始
$xml = new SimpleXMLElement($root);
//usersタグ追加
$usersTag = $xml->addChild('users');

//user情報
//この辺はDBから普通は取得すると思うけど
$users = array(
    array(
        'id' => "1",
        'name' => "たろう",
        'music' => "NumberGirl",
        'sex' => "男"
    ),
    array(
        'id' => 2,
        'name' => "はなこ",
        'music' => "ラッツ&スター",
        'sex' => "女"
    )
);

foreach($users as $user) {
    //usersタグにuserタグ作成
    $userTag = $usersTag->addChild('user');
    //userタグに要素を追加
    $userTag->addAttribute('id', $user['id']);
    //子要素を追加
    $userTag->addChild('name', _h($user['name']));
    $userTag->addChild('music', _h($user['music']));
    $userTag->addChild('sex', _h($user['sex']));
}

//XML保存
$file = @fopen('./sample.xml', "w");
@fwrite($file, $xml->asXML());
@fclose($file);

/**
 * エスケープ処理
 * XMLの値の中に&が入ってるとエラーで落ちるので
 * 
 * @param String
 * @return String
 */
function _h($value)
{
    return htmlspecialchars($value, ENT_QUOTES);
}

こんな感じだと思います。うわ楽ちん♪

注意

一つ注意点をあげると、XMLに追加する値の中に アンパサンド & が入ってるとPHPに怒られるのできちんとエスケープ処理を入れませう。

整形

んで、ここからが本番なのですが、これだと以下の様に

<?xml version="1.0" encoding="UTF-8"?>
<hoge><users><user id="1"><name>たろう</name><music>NumberGirl</music><sex>男</sex></user><user id="2"><name>はなこ</name><music>ラッツ&スター</music><sex>女</sex></user></users></hoge>

ばぁーーーっと、なってしまっています。これだと修正出来る様に見やすくしてくれと言われた要件にはそぐわないわけです。
※自分的にXMLを直接編集すんのってどうなの?って思ったんですけど。。

さてどうしよう、ググりました。
SimpleXMLには整形する機能はない様でした。
で、一番良さそうだったのがDOMから作ってそれをXML形式で出力する方法でしたが、?DOMから作る?のメンドクセでした。

SimpleXMLをDOMに食わせる

メンドクセでしたので、逆にXMLをDOMに食わせればいけるんじゃね?と思って、さらにググるとありました。
PHP 開発者のための XML: 第 1 回 PHP での XML を 15 分で学ぶ
の、 “SimpleXML フォーマットをインポートして DOM フォーマットの XML を出力する” 以下に書いてありました。
これです。

$dom_sxe = dom_import_simplexml($sxe);

これ何をするかというと、SimpleXMLElementオブジェクトからDOMElementオブジェクトを取得する関数なんですね。
で、ここから他にも調べて以下の様に書きなおしたらバッチグーでした。

//DOMDocument生成 encodingは任意で
$dom = new DOMDocument('1.0', 'utf-8');

//DOMNodeをコピーして返す。第2引数trueで属性もコピー
//DOMElementはDOMNodeを継承している
//この$xmlは上記で作ったSimpleXMLElement
$node = $dom->importNode(dom_import_simplexml($xml), true);

//コピーしたDOMNodeを追加
$dom->appendChild($node);

//余分な空白を取り除く
$dom->preserveWhiteSpace = false;
//きれいに整形した出力を行う
$dom->formatOutput = true;
//ファイルに出力
$dom->save('./sample.xml');

結果は

<?xml version="1.0" encoding="utf-8"?>
<hoge>
  <users>
    <user id="1">
      <name>たろう</name>
      <music>NumberGirl</music>
      <sex>男</sex>
    </user>
    <user id="2">
      <name>はなこ</name>
      <music>ラッツ&スター</music>
      <sex>女</sex>
    </user>
  </users>
</hoge>

Good Job!!

他の方法

ちなみに他の方法もありました。

//ownerDocumentはノードが含まれるトップレベルのドキュメントオブジェクトを返す
$dom = dom_import_simplexml($xml)->ownerDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;

$file = @fopen('./sample.xml', "w");
@fwrite($file, $dom->saveXML());
@fclose($file);

てか、こっちの方がシンプルかもしれないですね。

前者と後者ですが、局所的な場面だったら好きな方でいいと思います。
めちゃくちゃデータがあったり、いくつもXMLファイルを作る場合だったら速度とかメモリ使用率とか調べてやったほうがいいと思います。
すいません適当で。。

おまけ

DOM関数がPHPに組み込まれていない場合

yum install php-xml

で組み込めますよよい。
※自分はCentOS(さくらVPS)です

2013/06/28 修正

//ownerDocumentはノードが含まれるトップレベルのドキュメントオブジェクトを返す
$dom = dom_import_simplexml($xml)->ownerDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;

$file = @fopen('./sample.xml', "w");
@fwrite($file, $dom->saveXML());
@fclose($file);

↑ですが、冗長じゃないかとご指摘受けました。こう書けます。

//ownerDocumentはノードが含まれるトップレベルのドキュメントオブジェクトを返す
$dom = dom_import_simplexml($xml)->ownerDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->save('./sample.xml');

確かにそうですね。。
多分どっかからコピペしたままだったんだと思います。恥ずかしい。。

2016/12/21 追記

https://github.com/shimabox/SMBArrayto
こんなものを書きました。
php5.4以上、composerは必要になりますが、このライブラリを使うとこう書けます。

composerでインストール

$ composer require shimabox/smbarrayto

php

require_once '/your/path/to/vendor/autoload.php';

// こんな配列を作って
$rows['users'] = [
    [
        '_attributes' => ['id' => 1],
        'name'  => 'たろう',
        'music' => 'NumberGirl',
        'sex'   => 'boy'
    ],
    [
        '_attributes' => ['id' => 2],
        'name'  => 'はなこ',
        'music' => 'ラッツ&スター',
        'sex'   => 'girl'
    ],
];

$xml = SMB\Arrayto\Xml::factory();
$xml->getWriter()
    ->setRows($rows) // 配列を渡す
    ->setRootElementName('hoge') // root名をセット
    ->setFileName('/your/path/to/sample.xml') // 書き込むパスをセット
    ->write() // 保存
    ;

結果

<?xml version="1.0" encoding="UTF-8"?>
<hoge>
  <users id="1">
    <name>たろう</name>
    <music>NumberGirl</music>
    <sex>boy</sex>
  </users>
  <users id="2">
    <name>はなこ</name>
    <music>ラッツ&スター</music>
    <sex>girl</sex>
  </users>
</hoge>

Good Job!!

投稿日:
カテゴリー: PHP

作成者: shimabox

Web系のプログラマをやっています。 なるべく楽しく生きていきたい。

2件のコメント

  1. 他の方法と書かれている成形方法について

    dom_import_simplexmlでDOMDocumentを作っているのだから
    saveの方法も

    $file = @fopen(‘./sample.xml’, “w”);
    @fwrite($file, $dom->saveXML());
    @fclose($file);

    ではなく、

    $dom->save(‘./sample.xml’);
    で良いと思うんですが。。。

  2. > maoさん
    う。。確かにそうですね。
    多分どっかからコピペしたままだったんだと思います(反省)。
    ご指摘ありがとうございます。修正させて頂きました。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください