PHP の SimpleXMLElement が使いものにならない
◆ 使えなすぎ
◆ Simple は「簡単に使える」じゃなくて「機能がなさすぎでまともに使えない」ってことだと思う
◆ データの取得に関してなら十分だけど XML の編集だと DOMDocument のほうが遥かに簡単
◆ JavaScript で DOM を触った経験あるなら特に DOMDocument がオススメです
◆ Simple は「簡単に使える」じゃなくて「機能がなさすぎでまともに使えない」ってことだと思う
◆ データの取得に関してなら十分だけど XML の編集だと DOMDocument のほうが遥かに簡単
◆ JavaScript で DOM を触った経験あるなら特に DOMDocument がオススメです
SimpleXMLElement が使えなすぎです
初心者は使うべきじゃないと思います
e を取り出してみる
配列っぽい var_dump 表示だけど SimpleXMLElement のオブジェクトなので -> で取得
属性が @attributes で子要素はタグ名になってる
a をみると b が配列で要素数 3 になっているのに
a->b で b をとりだしたのだとオブジェクト 配列じゃない
var_dump の表示は当てにしたらダメ
-> でプロパティ取り出しをしたタグがひとつだけだと 子要素のタグ名がプロパティにあるけど text の場合はタグ名じゃなく添字 0 なよう
それから タグが一つじゃない b をみると添字の 0 から順に各 b タグが入っているみたい
タグがひとつか複数かで変わるのも扱いづらい
->{0} は使えなくてここは [0] でアクセス
var_dump からアクセス方法が判別できない
やっぱり var_dump はつk……
だけど結果は x->e と一緒の表示
[0] では中身は取り出せないみたい
attributes にアクセスしようとすると
配列でもオブジェクトでもアクセスできない
そんな感じで var_dump は全然あてにならない
例えばこんな場合
x に a タグとテキストがあります
var_dump では sample のテキストは見えないですが asXML で XML 化するとちゃんと保持はされています
x->a->b みたいな同じタグが複数あるときに foreach で回すと
全部 key が一緒!!
あとから一意にアクセスするためのキーとして使えません
text タグが欲しい時
[0] を繰り返しても自分自身を参照しているようで取得できません
xpath が使えるようなので使ってみる
取得できてるのは同じで text 要素じゃないようです
SimpleXMLElement は Element と言ってしまっているので TextNode は対象外?
一応文字列キャストで文字列取得はできます
ただし HTML の innerHTML や textContent みたいな動きではなく子要素が TextNode でないと空文字になります
それでも改行は保持されてるようです
リファレンスによると children というメソッドがあるみたい
使っては見たけど
何が違うんだろう
SimpleXMLElement だと最初からプロパティアクセスで子要素が取れるので違いがわかりません
また attributes メソッドもありました
こっちは @attributes で直接属性を取れないので重要です
属性だけど SimpleXMLElement
プロパティアクセスで属性の指定できますが ここでも文字列型では取得できないです
attributes を使う要素が空の場合は var_dump は一緒に見えます
foreach やプロパティアクセスしないと違いがわからないです
同じタグが複数ある場合の タグのプロパティ ($x->a->b) の attributes は一番最初のタグのプロパティになっています
$x->a->b[1] のように指定した上で attributes を使う必要があります
タグが複数ある配列のようなものの場合は attributes なしにしてくれたほうがわかりやすいのですけどねー
削除や置き換えを大きく行うときは新しく SimpleXMLElement を作ってそっちに必要なものだけ追加していったほうが楽なくらいに面倒です
一度 attributes の key を保存してから key を使ったアクセスで消すようにします
これで a のタグが全部消えました
そのタグに null をいれるだけです
xpath で取得したものを foreach する場合は [0] に null を入れます
a と e の中を空にします
ただし 属性と同じで foreach で unset したらずれのせいでエラーになるので foreach でやる場合は一度 $value を保存しておきます
保存しておいたものに unset をかければ消すことができます
SimpleXMLElement では整形するオプションが無いみたいなので消したところは空白になるだけで詰めることはできないです
また 一行になっているものを複数行で見やすい位置で改行とインデントを入れてくれるようなこともできないです
それと <d></d> が <d/> にまとめられているように 空のタグは開始と終了の 2 つでなく一つにされてしまいます
ただし 改行や空白があれば空白文字の TextNode を含んでいるのでそのままです
libxml オプションで LIBXML_NOEMPTYTAG という空タグでも開始と終了の 2 つのタグにするオプションがありますが SimpleXMLElement の asXML (saveXML) では未対応なようです
DOMDocument で使えます
さすがダメな言語で有名な PHP のデフォルトだけあります
そもそも デバッグ用の var_dump で正しい値が見ることができない辺り使いものにならないと思います
なんのためのデバッグ用関数なのか不明です
これをみると PHP で XML は触れるべきじゃない と思うかもです
というか思うはず
ですが……
こっちは色々メソッドやプロパティがあって複雑そうですがすごく簡単です
JavaScript で DOM 操作をしたことがあれば ほとんど同じ感じで使えます
formatOutput を true にしても TextNode で改行などの空白文字があるところはそのまま残ります
なので空白文字を TextNode から除去します
余白がなくなりましたね
単純に trim してるので 前後の空白を維持したいところでそのまま使えませんが こんな風に空白文字のノードを除去すれば綺麗に整形できます
完全に空文字でも remove しない場合は綺麗に整形されません
trim の結果 "text" 以外は空文字になりました
その場合の出力された XML を見ると整形するはずなのに何もされず1行で出力されています
空文字の TextNode を挟むとそこでは整形しても改行挟まないということになります
TextNode で文字列があるわけですから 勝手に整形のための空白を追加するとデータが変わってしまいますからね
初心者は使うべきじゃないと思います
SimpleXMLElement
$x = simplexml_load_string('
<x>
<a aa="1" bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d></d></c>
</a>
<e gg="kk">text</e>
</x>
');
printHR('x');
var_dump($x);
// 見やすく表示するだけ
function printHR($text){
$line_chars = 50;
echo $text . ' ' . str_repeat('#', $line_chars - strlen($text) - 1) . PHP_EOL;
}
<x>
<a aa="1" bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d></d></c>
</a>
<e gg="kk">text</e>
</x>
');
printHR('x');
var_dump($x);
// 見やすく表示するだけ
function printHR($text){
$line_chars = 50;
echo $text . ' ' . str_repeat('#', $line_chars - strlen($text) - 1) . PHP_EOL;
}
object(SimpleXMLElement)#1 (2) {
["a"]=>
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#3 (1) {
["d"]=>
object(SimpleXMLElement)#4 (0) {
}
}
}
["e"]=>
string(4) "text"
}
["a"]=>
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#3 (1) {
["d"]=>
object(SimpleXMLElement)#4 (0) {
}
}
}
["e"]=>
string(4) "text"
}
e を取り出してみる
printHR("x['e']");
var_dump($x['e']);
printHR('x->e');
var_dump($x->e);
var_dump($x['e']);
printHR('x->e');
var_dump($x->e);
x['e'] ###########################################
NULL
x->e #############################################
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
NULL
x->e #############################################
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
配列っぽい var_dump 表示だけど SimpleXMLElement のオブジェクトなので -> で取得
printHR('x->a');
var_dump($x->a);
printHR('x->a->b');
var_dump($x->a->b);
printHR('x->e');
var_dump($x->e);
var_dump($x->a);
printHR('x->a->b');
var_dump($x->a->b);
printHR('x->e');
var_dump($x->e);
x->a #############################################
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#4 (1) {
["d"]=>
object(SimpleXMLElement)#5 (0) {
}
}
}
x->a->b ##########################################
object(SimpleXMLElement)#4 (4) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
x->e #############################################
object(SimpleXMLElement)#4 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#4 (1) {
["d"]=>
object(SimpleXMLElement)#5 (0) {
}
}
}
x->a->b ##########################################
object(SimpleXMLElement)#4 (4) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
x->e #############################################
object(SimpleXMLElement)#4 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
属性が @attributes で子要素はタグ名になってる
a をみると b が配列で要素数 3 になっているのに
a->b で b をとりだしたのだとオブジェクト 配列じゃない
var_dump の表示は当てにしたらダメ
-> でプロパティ取り出しをしたタグがひとつだけだと 子要素のタグ名がプロパティにあるけど text の場合はタグ名じゃなく添字 0 なよう
それから タグが一つじゃない b をみると添字の 0 から順に各 b タグが入っているみたい
タグがひとつか複数かで変わるのも扱いづらい
printHR('x->e->{0}');
var_dump($x->e->{0});
printHR('x->e[0]');
var_dump($x->e[0]);
var_dump($x->e->{0});
printHR('x->e[0]');
var_dump($x->e[0]);
x->e->{0} ########################################
object(SimpleXMLElement)#4 (0) {
}
x->e[0] ##########################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
object(SimpleXMLElement)#4 (0) {
}
x->e[0] ##########################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
->{0} は使えなくてここは [0] でアクセス
var_dump からアクセス方法が判別できない
やっぱり var_dump はつk……
だけど結果は x->e と一緒の表示
[0] では中身は取り出せないみたい
attributes にアクセスしようとすると
printHR('x->a->{"@attributes"}');
var_dump($x->a->{"@attributes"});
printHR('x->a["@attributes"]');
var_dump($x->a["@attributes"]);
var_dump($x->a->{"@attributes"});
printHR('x->a["@attributes"]');
var_dump($x->a["@attributes"]);
x->a->{"@attributes"} ############################
object(SimpleXMLElement)#4 (0) {
}
x->a["@attributes"] ##############################
NULL
object(SimpleXMLElement)#4 (0) {
}
x->a["@attributes"] ##############################
NULL
配列でもオブジェクトでもアクセスできない
そんな感じで var_dump は全然あてにならない
例えばこんな場合
x に a タグとテキストがあります
$x2 = simplexml_load_string('
<x>
<a>test</a>
sample
</x>
');
printHR('x2');
var_dump($x2);
printHR('x2->asXML()');
var_dump($x2->asXML());
<x>
<a>test</a>
sample
</x>
');
printHR('x2');
var_dump($x2);
printHR('x2->asXML()');
var_dump($x2->asXML());
object(SimpleXMLElement)#3 (1) {
["a"]=>
string(4) "test"
}
string(58) "<?xml version="1.0"?>
<x>
<a>test</a>
sample
</x>
["a"]=>
string(4) "test"
}
string(58) "<?xml version="1.0"?>
<x>
<a>test</a>
sample
</x>
var_dump では sample のテキストは見えないですが asXML で XML 化するとちゃんと保持はされています
x->a->b みたいな同じタグが複数あるときに foreach で回すと
printHR("foreach x->a->b");
foreach($x->a->b as $key => $val){
printHR("key");
var_dump($key);
printHR("val");
var_dump($val);
}
foreach($x->a->b as $key => $val){
printHR("key");
var_dump($key);
printHR("val");
var_dump($val);
}
foreach x->a->b ##################################
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
[0]=>
string(4) "test"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#5 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "5"
}
[0]=>
string(5) "test2"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "6"
}
[0]=>
string(5) "test3"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
[0]=>
string(4) "test"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#5 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "5"
}
[0]=>
string(5) "test2"
}
key ##############################################
string(1) "b"
val ##############################################
object(SimpleXMLElement)#3 (2) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "6"
}
[0]=>
string(5) "test3"
}
全部 key が一緒!!
あとから一意にアクセスするためのキーとして使えません
text タグが欲しい時
[0] を繰り返しても自分自身を参照しているようで取得できません
printHR("x->e[0][0][0][0][0][0]");
var_dump($x->e[0][0][0][0][0][0]);
var_dump($x->e[0][0][0][0][0][0]);
x->e[0][0][0][0][0][0] ###########################
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
xpath が使えるようなので使ってみる
printHR("x->xpath('/x/e/text()')");
var_dump($x->xpath('/x/e/text()'));
var_dump($x->xpath('/x/e/text()'));
x->xpath('/x/e/text()') ##########################
array(1) {
[0]=>
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
}
array(1) {
[0]=>
object(SimpleXMLElement)#2 (2) {
["@attributes"]=>
array(1) {
["gg"]=>
string(2) "kk"
}
[0]=>
string(4) "text"
}
}
取得できてるのは同じで text 要素じゃないようです
SimpleXMLElement は Element と言ってしまっているので TextNode は対象外?
一応文字列キャストで文字列取得はできます
printHR("(string)x->e");
var_dump((string)$x->e);
var_dump((string)$x->e);
(string)x->e #####################################
string(4) "text"
string(4) "text"
ただし HTML の innerHTML や textContent みたいな動きではなく子要素が TextNode でないと空文字になります
それでも改行は保持されてるようです
printHR("(string)x->a");
var_dump((string)$x->a);
var_dump((string)$x->a);
(string)x->a #####################################
string(41) "
"
string(41) "
"
リファレンスによると children というメソッドがあるみたい
使っては見たけど
printHR("x");
var_dump($x);
printHR("x->children()");
var_dump($x->children());
var_dump($x);
printHR("x->children()");
var_dump($x->children());
x ################################################
object(SimpleXMLElement)#1 (2) {
["a"]=>
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#3 (1) {
["d"]=>
object(SimpleXMLElement)#4 (0) {
}
}
}
["e"]=>
string(4) "text"
}
x->children() ####################################
object(SimpleXMLElement)#2 (2) {
["a"]=>
object(SimpleXMLElement)#4 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#5 (1) {
["d"]=>
object(SimpleXMLElement)#6 (0) {
}
}
}
["e"]=>
string(4) "text"
}
object(SimpleXMLElement)#1 (2) {
["a"]=>
object(SimpleXMLElement)#2 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#3 (1) {
["d"]=>
object(SimpleXMLElement)#4 (0) {
}
}
}
["e"]=>
string(4) "text"
}
x->children() ####################################
object(SimpleXMLElement)#2 (2) {
["a"]=>
object(SimpleXMLElement)#4 (3) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
["b"]=>
array(3) {
[0]=>
string(4) "test"
[1]=>
string(5) "test2"
[2]=>
string(5) "test3"
}
["c"]=>
object(SimpleXMLElement)#5 (1) {
["d"]=>
object(SimpleXMLElement)#6 (0) {
}
}
}
["e"]=>
string(4) "text"
}
何が違うんだろう
SimpleXMLElement だと最初からプロパティアクセスで子要素が取れるので違いがわかりません
また attributes メソッドもありました
こっちは @attributes で直接属性を取れないので重要です
printHR("x->a->attributes()");
var_dump($x->a->attributes());
var_dump($x->a->attributes());
x->a->attributes() ###############################
object(SimpleXMLElement)#4 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
object(SimpleXMLElement)#4 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
属性だけど SimpleXMLElement
printHR("x->a->attributes()->aa");
var_dump($x->a->attributes()->aa);
var_dump($x->a->attributes()->aa);
x->a->attributes()->aa ###########################
object(SimpleXMLElement)#3 (1) {
[0]=>
string(1) "1"
}
object(SimpleXMLElement)#3 (1) {
[0]=>
string(1) "1"
}
プロパティアクセスで属性の指定できますが ここでも文字列型では取得できないです
attributes を使う要素が空の場合は var_dump は一緒に見えます
foreach やプロパティアクセスしないと違いがわからないです
$x3 = simplexml_load_string('
<a aa="1" bb="zz"></a>
');
$xattrs = $x3->attributes();
printHR("x3");
var_dump($x3);
printHR("xattrs");
var_dump($xattrs);
printHR("x3->bb");
var_dump($x3->bb);
printHR("xattrs->bb");
var_dump($xattrs->bb);
printHR("foreach x3");
foreach($x3 as $v) var_dump($v);
printHR("foreach xattrs");
foreach($xattrs as $v) var_dump($v);
<a aa="1" bb="zz"></a>
');
$xattrs = $x3->attributes();
printHR("x3");
var_dump($x3);
printHR("xattrs");
var_dump($xattrs);
printHR("x3->bb");
var_dump($x3->bb);
printHR("xattrs->bb");
var_dump($xattrs->bb);
printHR("foreach x3");
foreach($x3 as $v) var_dump($v);
printHR("foreach xattrs");
foreach($xattrs as $v) var_dump($v);
x3 ###############################################
object(SimpleXMLElement)#2 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
xattrs ###########################################
object(SimpleXMLElement)#3 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
x3->bb ###########################################
object(SimpleXMLElement)#5 (0) {
}
xattrs->bb #######################################
object(SimpleXMLElement)#5 (1) {
[0]=>
string(2) "zz"
}
foreach x3 #######################################
foreach xattrs ###################################
object(SimpleXMLElement)#4 (1) {
[0]=>
string(1) "1"
}
object(SimpleXMLElement)#6 (1) {
[0]=>
string(2) "zz"
}
object(SimpleXMLElement)#2 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
xattrs ###########################################
object(SimpleXMLElement)#3 (1) {
["@attributes"]=>
array(2) {
["aa"]=>
string(1) "1"
["bb"]=>
string(2) "zz"
}
}
x3->bb ###########################################
object(SimpleXMLElement)#5 (0) {
}
xattrs->bb #######################################
object(SimpleXMLElement)#5 (1) {
[0]=>
string(2) "zz"
}
foreach x3 #######################################
foreach xattrs ###################################
object(SimpleXMLElement)#4 (1) {
[0]=>
string(1) "1"
}
object(SimpleXMLElement)#6 (1) {
[0]=>
string(2) "zz"
}
同じタグが複数ある場合の タグのプロパティ ($x->a->b) の attributes は一番最初のタグのプロパティになっています
$x->a->b[1] のように指定した上で attributes を使う必要があります
printHR("x->a->b->attributes()");
var_dump($x->a->b->attributes());
printHR("x->a->b[1]->attributes()");
var_dump($x->a->b[1]->attributes());
var_dump($x->a->b->attributes());
printHR("x->a->b[1]->attributes()");
var_dump($x->a->b[1]->attributes());
x->a->b->attributes() ############################
object(SimpleXMLElement)#3 (1) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
}
x->a->b[1]->attributes() #########################
object(SimpleXMLElement)#2 (1) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "5"
}
}
object(SimpleXMLElement)#3 (1) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "4"
}
}
x->a->b[1]->attributes() #########################
object(SimpleXMLElement)#2 (1) {
["@attributes"]=>
array(1) {
["cc"]=>
string(1) "5"
}
}
タグが複数ある配列のようなものの場合は attributes なしにしてくれたほうがわかりやすいのですけどねー
削除
SimpleXMLElement では追加するメソッドはありますが置き換えや削除がありません削除や置き換えを大きく行うときは新しく SimpleXMLElement を作ってそっちに必要なものだけ追加していったほうが楽なくらいに面倒です
属性を削除
attributes を取得して それぞれの [0] を unset すればよいのですが unset したタイミングで attributes が更新されるようで次のループで問題が出ますprintHR("remove attributes");
$aattrs = $x->a->attributes();
foreach($aattrs as $v){
unset($v[0]);
}
echo $x->asXML();
$aattrs = $x->a->attributes();
foreach($aattrs as $v){
unset($v[0]);
}
echo $x->asXML();
remove attributes ################################
Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 18
<?xml version="1.0"?>
<x>
<a bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
結果も aa は消えましたが bb が残っていますWarning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 18
<?xml version="1.0"?>
<x>
<a bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
一度 attributes の key を保存してから key を使ったアクセスで消すようにします
printHR("remove attributes");
$aattrs = $x->a->attributes();
$attrs = [];
foreach($aattrs as $k => $v){
$attrs[] = $k;
}
foreach($attrs as $v){
unset($aattrs->$v[0]);
}
echo $x->asXML();
$aattrs = $x->a->attributes();
$attrs = [];
foreach($aattrs as $k => $v){
$attrs[] = $k;
}
foreach($attrs as $v){
unset($aattrs->$v[0]);
}
echo $x->asXML();
remove attributes ################################
<?xml version="1.0"?>
<x>
<a>
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
<?xml version="1.0"?>
<x>
<a>
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
これで a のタグが全部消えました
子要素を削除
innerHTML = "" のような処理をしたいときそのタグに null をいれるだけです
printHR("remove inner nodes");
$x->a = null;
echo $x->asXML();
$x->a = null;
echo $x->asXML();
remove inner nodes ###############################
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz"></a>
<e gg="kk">text</e>
</x>
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz"></a>
<e gg="kk">text</e>
</x>
xpath で取得したものを foreach する場合は [0] に null を入れます
a と e の中を空にします
printHR("remove inner nodes");
foreach($x->xpath("/x/a|/x/e") as $val){
$val[0] = null;
}
echo $x->asXML();
foreach($x->xpath("/x/a|/x/e") as $val){
$val[0] = null;
}
echo $x->asXML();
remove inner nodes ###############################
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz"></a>
<e gg="kk"></e>
</x>
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz"></a>
<e gg="kk"></e>
</x>
要素の削除
要素自身を削除するときは 子要素を消す時とほぼ同じですが null を入れるのではなく unset しますただし 属性と同じで foreach で unset したらずれのせいでエラーになるので foreach でやる場合は一度 $value を保存しておきます
printHR("remove node");
foreach($x->a->b as $val){
unset($val[0]);
}
echo $x->asXML();
foreach($x->a->b as $val){
unset($val[0]);
}
echo $x->asXML();
remove node ######################################
Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
PHP Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17
Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
PHP Warning: main(): Node no longer exists in C:\Users\user\Desktop\s.php on line 17
保存しておいたものに unset をかければ消すことができます
printHR("remove node");
$arr = [];
foreach($x->a->b as $val){
$arr[] = $val;
}
foreach($arr as $val){
unset($val[0]);
}
echo $x->asXML();
$arr = [];
foreach($x->a->b as $val){
$arr[] = $val;
}
foreach($arr as $val){
unset($val[0]);
}
echo $x->asXML();
remove node ######################################
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c><d/></c>
</a>
<e gg="kk">text</e>
</x>
ムリなこと
↑では要素を削除したところに空白行が残ってしまっていますSimpleXMLElement では整形するオプションが無いみたいなので消したところは空白になるだけで詰めることはできないです
また 一行になっているものを複数行で見やすい位置で改行とインデントを入れてくれるようなこともできないです
それと <d></d> が <d/> にまとめられているように 空のタグは開始と終了の 2 つでなく一つにされてしまいます
ただし 改行や空白があれば空白文字の TextNode を含んでいるのでそのままです
libxml オプションで LIBXML_NOEMPTYTAG という空タグでも開始と終了の 2 つのタグにするオプションがありますが SimpleXMLElement の asXML (saveXML) では未対応なようです
DOMDocument で使えます
まとめ
シンプルに扱うにしても 削除ができないとか 属性であろうとテキストノードであろうと全部エレメントでひとつのクラスにしてる無理矢理感のせいで扱いづらいとかヒドイ作りだと思いますさすがダメな言語で有名な PHP のデフォルトだけあります
そもそも デバッグ用の var_dump で正しい値が見ることができない辺り使いものにならないと思います
なんのためのデバッグ用関数なのか不明です
これをみると PHP で XML は触れるべきじゃない と思うかもです
というか思うはず
ですが……
DOMDocument がおすすめ
PHP には Simple じゃない XML を扱う DOMDocument というのもありますこっちは色々メソッドやプロパティがあって複雑そうですがすごく簡単です
JavaScript で DOM 操作をしたことがあれば ほとんど同じ感じで使えます
a の属性表示
$doc = new DOMDocument();
$doc->loadXML('
<x>
<a aa="1" bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d>
</d></c>
</a>
<e gg="kk">text</e>
</x>
');
printHR("a tag attributes");
$a = $doc->getElementsByTagName("a")->item(0);
foreach($a->attributes as $attr){
echo $attr->name . " : " . $attr->value . PHP_EOL;
}
$doc->loadXML('
<x>
<a aa="1" bb="zz">
<b cc="4">test</b>
<b cc="5">test2</b>
<b cc="6">test3</b>
<c><d>
</d></c>
</a>
<e gg="kk">text</e>
</x>
');
printHR("a tag attributes");
$a = $doc->getElementsByTagName("a")->item(0);
foreach($a->attributes as $attr){
echo $attr->name . " : " . $attr->value . PHP_EOL;
}
a tag attributes #################################
aa : 1
bb : zz
aa : 1
bb : zz
b の削除
JavaScript と違い NodeList が動的に更新されるので iterator_to_array が必要printHR("remove b tags");
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
$doc->formatOutput = true;
var_dump($doc->saveXML());
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
$doc->formatOutput = true;
var_dump($doc->saveXML());
remove b tags ####################################
string(146) "<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c><d>
</d></c>
</a>
<e gg="kk">text</e>
</x>
"
string(146) "<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c><d>
</d></c>
</a>
<e gg="kk">text</e>
</x>
"
整形
上の例では消したところに空白が残っていますformatOutput を true にしても TextNode で改行などの空白文字があるところはそのまま残ります
なので空白文字を TextNode から除去します
printHR("remove b tags and pretty print");
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
removeMargin($doc);
$doc->formatOutput = true;
var_dump($doc->saveXML());
function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text = trim($text_node->data);
if(strlen($text)){
$text_node->data = $text;
}else{
$text_node->parentNode->removeChild($text_node);
}
}
}
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
removeMargin($doc);
$doc->formatOutput = true;
var_dump($doc->saveXML());
function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text = trim($text_node->data);
if(strlen($text)){
$text_node->data = $text;
}else{
$text_node->parentNode->removeChild($text_node);
}
}
}
remove b tags and pretty print ###################
string(109) "<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c>
<d/>
</c>
</a>
<e gg="kk">text</e>
</x>
"
string(109) "<?xml version="1.0"?>
<x>
<a aa="1" bb="zz">
<c>
<d/>
</c>
</a>
<e gg="kk">text</e>
</x>
"
余白がなくなりましたね
単純に trim してるので 前後の空白を維持したいところでそのまま使えませんが こんな風に空白文字のノードを除去すれば綺麗に整形できます
完全に空文字でも remove しない場合は綺麗に整形されません
$bs = $doc->getElementsByTagName("b");
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
removeMargin($doc);
$doc->formatOutput = true;
printHR("remove b tags and pretty print");
var_dump($doc->saveXML());
function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text_node->data = trim($text_node->data);
printHR("text node length");
var_dump(strlen($text_node->data));
}
}
foreach(iterator_to_array($bs) as $b){
$b->parentNode->removeChild($b);
}
removeMargin($doc);
$doc->formatOutput = true;
printHR("remove b tags and pretty print");
var_dump($doc->saveXML());
function removeMargin($doc){
$xpath = new DOMXpath($doc);
foreach ($xpath->evaluate('//text()') as $text_node){
$text_node->data = trim($text_node->data);
printHR("text node length");
var_dump(strlen($text_node->data));
}
}
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(4)
text node length #################################
int(0)
remove b tags and pretty print ###################
string(85) "<?xml version="1.0"?>
<x><a aa="1" bb="zz"><c><d></d></c></a><e gg="kk">text</e></x>
"
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(0)
text node length #################################
int(4)
text node length #################################
int(0)
remove b tags and pretty print ###################
string(85) "<?xml version="1.0"?>
<x><a aa="1" bb="zz"><c><d></d></c></a><e gg="kk">text</e></x>
"
trim の結果 "text" 以外は空文字になりました
その場合の出力された XML を見ると整形するはずなのに何もされず1行で出力されています
空文字の TextNode を挟むとそこでは整形しても改行挟まないということになります
TextNode で文字列があるわけですから 勝手に整形のための空白を追加するとデータが変わってしまいますからね