2011年3月29日火曜日

e4x@Rhinoで名前空間の付いた属性とか雑多なこと

e4xネタです。 Rhino 1.7R2 で名前空間付きの属性を変更しようとしてハマったので調べてみました。

ブログでいっぺんに書くにはダラダラしたコードなので分断して書きます。 テストコード全体は下の方に載せます。 まずはコードの先頭。

var x =
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
    <elem id='i0' test_attr='t'/>
    <elem id='i1' test_attr=''/>
    <elem id='i2'/>
    <elem id='i3' xlink:href="#i0"/>
    <elem id='i4' xlink:href="#i1"/>
    <elem id='i5' xlink:href="#i2"><child_elem/></elem>
</root>;

var xlink = new Namespace('http://www.w3.org/1999/xlink');
var i, elem, elems, attr, attrs, str;

属性へのアクセス

普通に、特定の属性ノードを一覧表示するコードです。

print('\nid属性の一覧');
attrs = x.elem.@id;
print_nodes(attrs);

print('\ntest_attr属性の一覧');
attrs = x.elem.@test_attr;
print_nodes(attrs);

print('\nxlink:href属性の一覧');
attrs = x.elem.@xlink::href;
print_nodes(attrs);

結果は、

id属性の一覧
'i0', 'i1', 'i2', 'i3', 'i4', 'i5'

test_attr属性の一覧
't', ''

xlink:href属性の一覧
'#i0', '#i1', '#i2'

この辺問題なし。

次はフィルタ演算で取り出す属性に条件を付けてみます。 このあたりからハマり要素が...

print('\nid属性を持った「要素」をフィルタ演算で引く');
elems = x.elem.(@id == 'i1');
print("x.elem.(@id == 'i1') ) ... id = " + elems[0].@id);

print('\ntest_attr属性を持った「要素」をフィルタ演算で引く');
elems = x.elem.(@test_attr == 't');
print("x.elem.(@test_attr == 't') ) ... test_attr = " + elems[0].@test_attr);

print('\n@xlink:href属性を持った要素 → javaの例外が発生');
print("名前空間付きの属性を持った要素はフィルタ演算では引けない?");
try
{
    elems = x.elem.(@xlink::href == '#i2');
    print("x.elem.(@xlink::href == '#i2') ) ... href = " + elems[0].@xlink::href);
}
catch(e) // ← javascriptのtry~catchでは補足出来ない
{
    print(e);
}

最初の2つ、名前空間なしのフィルタ演算は普通に出来てます。 まずはその部分だけの結果。

id属性を持った「要素」をフィルタ演算で引く
x.elem.(@id == 'i1') ) ... id = i1

test_attr属性を持った「要素」をフィルタ演算で引く
x.elem.(@test_attr == 't') ) ... test_attr = t

問題は3つ目の名前空間ありのフィルタ演算です。 このコードを含んでいると、rhino起動直後にjavaで例外が発生しました。 Rhinoの例外表示は、

Exception in thread "main" java.lang.RuntimeException: Bad tree in codegen
        at org.mozilla.javascript.optimizer.Codegen.badTree(Codegen.java:1273)
        ...

名前空間付きの属性にアクセスするにはまずオーナー要素を探さないとダメかもしれません。

var xlink_href = new QName(xlink, 'href');
attr = x.elem.(@id == 'i5').attribute(xlink_href);
print("x.elem.(@id == 'i5').attribute(xlink_href) ... attr = '" + attr + "' (QNameを使用)");
print("attrの名前空間は" + attr.namespace() );

結果は、

x.elem.(@id == 'i5').attribute(xlink_href) ... attr = '#i2' (QNameを使用)
attrの名前空間はhttp://www.w3.org/1999/xlink

QNameを使わずに属性にアクセスする方法はまだ見つけてません。 残念。

「名前空間付きの属性はフィルタ演算が使えない」... これはそれだけの問題じゃないんですよね。 詳しくは後で。

名前空間自体を表す属性

名前空間自体を表す属性にアクセスするのは通常の方法では無理でした。 こんなコードは失敗。

var xmlns = new Namespace();
var xmlns_xlink = new QName(xmlns, 'xlink');
attr = x.attribute(xmlns_xlink);
print("x.attribute(xmlns_xlink) = '" + x.attribute(xmlns_xlink) + "' (QNameを使用)");
attr = x.@xmlns::xlink;
print("x.@xmlns::xlink = '" + x.@xmlns::xlink + "'");

空白文字しか帰りません。

x.attribute(xmlns_xlink) = '' (QNameを使用)
x.@xmlns::xlink = ''

そもそも、名前空間オブジェクトに何を渡せばいいのか分かってない状態。 正しいアクセス方法の文献とか、どっかにあるんでしょうけどねぇ。 今はこんなコードで調べています。

print("\nx.attributes()で全部リストアップしてnamespaceオブジェクトを持たないものを探す");
attrs = x.attributes();
for(i in attrs)
{
    attr = attrs[i];
    if(attr.namespace() == null)
    {
        print('  属性 ' + attr.localName() + '=' + attr + ' はnamespaceを持たない');
    }
    else
    {
        print("  属性 " + attr.localName() + " のnamespaceは'" + attr.namespace() + "'");
    }
}

結果は、

x.attributes()で全部リストアップしてnamespaceオブジェクトを持たないものを探す
  属性 dummy のnamespaceは''
  属性 xlink=http://www.w3.org/1999/xlink はnamespaceを持たない

dummy属性はデフォルトネームスペース上の属性なので「何も登録されていないNamespaceオブジェクト」を持っています。 xmlns:xlink="http://~"はNamespaceオブジェクトを持っていません。 それで普通の属性と判別できます。

属性の有無を調べる

「e4xで属性の有無を調べるにはhasOwnPropertyが使えるらしい。」ってのはどっかのサイトに書いてありました。

elems = x.elem;
for(i in elems)
{
    elem = elems[i];
    str = "elem[" + elem.@id +
          "] hasOwnProperty('@test_attr') = " +
          elem.hasOwnProperty('@test_attr') +
          "\n         hasOwnProperty('@xlink::href') = " +
          elem.hasOwnProperty('@xlink::href');
    print(str);
}

これだと名前空間付きの属性の有無が分からないんですよね。

elem[i0] hasOwnProperty('@test_attr') = true
         hasOwnProperty('@xlink::href') = false
elem[i1] hasOwnProperty('@test_attr') = true
         hasOwnProperty('@xlink::href') = false
elem[i2] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i3] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i4] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i5] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false

さらに、フィルタ演算で使えないようです。

elems = x.elem.(hasOwnProperty('@test_attr') );
print("x.elem.(hasOwnProperty('@test_attr') ) ... elems = '" + elems + "'");

このコードの結果は空でした。

x.elem.(hasOwnProperty('@test_attr') ) ... elems = ''

他のサイトで見たコードだとフィルタ演算で使えてたんですけどね。 Rhinoじゃなかったのか、それともバージョン違いか...?

フィルタ演算で特定の属性を持った要素を得るのは次のコードでできました。

elems = x.elem.(0 < @test_attr.length() );
print("x.elem.(0 < @test_attr.length() ) ... elems = '" +
    to_string_long_node(elems) + "'");

結果は、

x.elem.(0 < @test_attr.length() ) ... elems = '<elem xmln ... 略'

ってサンプルコードで表示省略してしまった。 まぁ、できます。 現時点でバッドノウハウかどうか分からないのが辛いところ。

「フィルタ演算で」ってことで、もちろん名前空間付きの属性でこのコードは使えません。

あと、凡ミスをしやすいので注意。 フィルタ演算の中身が(0 < @属性.length)ではダメです。

elems = x.elem.(0 < @test_attr.length); // ← カッコがない
print("x.elem.(0 < @test_attr.length) ... elems = '" + elems + "'");

このコードの結果は空になります。

x.elem.(0 < @test_attr.length) ... elems = ''

何のlengthが帰ってるんだろ? functionオブジェクトが帰ってる? そこまでは確認してないです。 まぁ、書き間違えなけりゃいいか...

属性の書き換え

属性を書き換えるには、オーナー要素を得て、それに@でアクセスして書き換えます。

elems = x.elem.(@id == 'i1');
print('書き換え前\n' + x);
elems[0].@id = 'new_id';
print('書き換え後\n' + x);

結果は想定したとおりになります。

書き換え前
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>
書き換え後
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="new_id" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>

書き換え前の属性値にもどして続き。

要素から属性オブジェクトを取り出して書き換えてもxmlオブジェクトに反映されません。

attr = elems[0].@id;
print('書き換え前\n' + x);
attr = 'new_id';
print('書き換え後 ... 変化なし\n' + x);

結果は、

書き換え前
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>
書き換え後 ... 変化なし
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>

この「属性オブジェクトを書き換えても無駄」ってのも大きなハマり要素でした。

属性オブジェクトの型

「属性オブジェクトを書き換えても無駄なら、オーナー要素を探して書き換えればいいじゃない?」

最初はそう思いました。 Javaなら Attr.getOwnerElement() でオーナー要素が探せますよね。 でもe4xでそれに当たるメソッドが無いみたいなんですよ。 代わりに XML.parent() を使ってみてもダメでした。

そこで、e4x@Rhinoの属性オブジェクトがどうなっているのかちょっと調べてみました。 前のコードの続きで、

elems = x.elem.(@id == 'i1'); // ←前のコードより抜粋
attr = elems[0].@id;          // ←前のコードより抜粋

print("\n属性の型は? (elems = x.elem.(@id == 'i1') → attr = elems[0].@idの場合)");
try
{
    print('typeof(attr) = ' + typeof(attr) );
    print('typeof(attr.parent) = ' + typeof(attr.parent) );
    print('attr.parent() = ' + attr.parent() );
}
catch(e)
{
    print(e);
}

print('\n属性の型は? (elem.attribute()の場合)');
attr = x.elem.(@id == 'i5').attribute(xlink_href);
print('typeof(attr) = ' + typeof(attr) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('attr.parent() = ' + attr.parent() );

print('\n属性リストの型は? (x.elem.@idの場合)');
attrs = x.elem.@id;
attr = attrs[0];
print('attrs instanceof XMLList = ' + (attrs instanceof XMLList) );
print('typeof(attr) = ' + typeof(attr) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('attr.parent() = ' + attr.parent() );

print('\n属性リストの型は? (elem.attributes()の場合)');
attrs = x.elem.(@id == 'i4').attributes();
attr = attrs[0];
print('attrs instanceof XMLList = ' + (attrs instanceof XMLList) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('typeof(attr) = ' + typeof(attr) );
print('attr.parent() = ' + attr.parent() );

結果は、

属性の型は? (elems = x.elem.(@id == 'i1') → attr = elems[0].@idの場合)
typeof(attr) = string
typeof(attr.parent) = undefined
TypeError: Cannot find function parent in object new_id.

属性の型は? (elem.attribute()の場合)
typeof(attr) = xml
typeof(attr.parent) = function
attr.parent() = undefined

属性リストの型は? (x.elem.@idの場合)
attrs instanceof XMLList = true
typeof(attr) = xml
typeof(attr.parent) = function
attr.parent() = null

属性リストの型は? (elem.attributes()の場合)
attrs instanceof XMLList = true
typeof(attr.parent) = function
typeof(attr) = xml
attr.parent() = null

え? 文字列型?

XMLオブジェクトじゃないのが混じってるとは思いませんでした。 そして、アクセスの仕方によって XML.parent() の扱いが違う...

もちろん、要素の親は普通に分かりますよ?

elems = x.elem.child_elem;
print('elems[0].parent() = ' +
    to_string_long_node(elems[0].parent() ) );

結果は、

elems[0].parent() = <elem xmln ... 略

属性もこういう結果になって欲しかったんですけどね。

まとめ?

まぁ、ダラダラ書きましたが、名前空間付きの属性を書き換えるのがとても面倒というお話です。

  1. フィルタ演算で名前空間付きの属性を持った「要素」を探せない。
  2. フィルタ演算を使わなければ名前空間付きの属性オブジェクト全部の一覧は作れる。
  3. 属性オブジェクトを持ってきても、それを書き換えるのは無意味。オーナー要素を書き換える必要アリ。
  4. 属性オブジェクトからオーナー要素にはアクセスできない。

名前空間付きの属性を書き換えるには、全部の要素をチェックして該当の属性があったら書き換えるしかないのかな?

あ~。 ここまで書いた文章が、長い。 これは、ブログに投稿するには長すぎますね。 書いている中身も筋道がないです。 でも、考えてまとめて書き直してっていう気力がもうないのでそのまま投稿で...

コードと結果全部

テストコード全体はこちら、

// for Rhino 1.7R2
var x =
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
    <elem id='i0' test_attr='t'/>
    <elem id='i1' test_attr=''/>
    <elem id='i2'/>
    <elem id='i3' xlink:href="#i0"/>
    <elem id='i4' xlink:href="#i1"/>
    <elem id='i5' xlink:href="#i2"><child_elem/></elem>
</root>;

var xlink = new Namespace('http://www.w3.org/1999/xlink');
var i, elem, elems, attr, attrs, str;

print('======== 属性へのアクセス ========');

print('\nid属性の一覧');
attrs = x.elem.@id;
print_nodes(attrs);

print('\ntest_attr属性の一覧');
attrs = x.elem.@test_attr;
print_nodes(attrs);

print('\nxlink:href属性の一覧');
attrs = x.elem.@xlink::href;
print_nodes(attrs);

print('\nid属性を持った「要素」をフィルタ演算で引く');
elems = x.elem.(@id == 'i1');
print("x.elem.(@id == 'i1') ) ... id = " + elems[0].@id);

print('\ntest_attr属性を持った「要素」をフィルタ演算で引く');
elems = x.elem.(@test_attr == 't');
print("x.elem.(@test_attr == 't') ) ... test_attr = " + elems[0].@test_attr);

/*
print('\n@xlink:href属性を持った要素 → javaの例外が発生');
print("名前空間付きの属性を持った要素はフィルタ演算では引けない?");
try
{
    elems = x.elem.(@xlink::href == '#i2');
    print("x.elem.(@xlink::href == '#i2') ) ... href = " + elems[0].@xlink::href);
}
catch(e) // ← javascriptのtry~catchでは補足出来ない
{
    print(e);
}
// Rhinoの例外表示は
// Exception in thread "main" java.lang.RuntimeException: Bad tree in codegen
//         at org.mozilla.javascript.optimizer.Codegen.badTree(Codegen.java:1273)
//         ...
*/

print('\n名前空間付きの属性にアクセスするにはまずオーナー要素を探さないとダメかも?');
var xlink_href = new QName(xlink, 'href');
attr = x.elem.(@id == 'i5').attribute(xlink_href);
print("x.elem.(@id == 'i5').attribute(xlink_href) ... attr = '" + attr + "' (QNameを使用)");
print("attrの名前空間は" + attr.namespace() );

print('\n======== 名前空間自体を表す属性 ========');

print('\n名前空間自体を表す属性にアクセスするのは通常の方法では無理');
var xmlns = new Namespace();
var xmlns_xlink = new QName(xmlns, 'xlink');
attr = x.attribute(xmlns_xlink);
print("x.attribute(xmlns_xlink) = '" + x.attribute(xmlns_xlink) + "' (QNameを使用)");
attr = x.@xmlns::xlink;
print("x.@xmlns::xlink = '" + x.@xmlns::xlink + "'");

print("\nx.attributes()で全部リストアップしてnamespaceオブジェクトを持たないものを探す");
attrs = x.attributes();
for(i in attrs)
{
    attr = attrs[i];
    if(attr.namespace() == null)
    {
        print('  属性 ' + attr.localName() + '=' + attr + ' はnamespaceを持たない');
    }
    else
    {
        print("  属性 " + attr.localName() + " のnamespaceは'" + attr.namespace() + "'");
    }
}

print('\n======== 属性の有無を調べる ========');

print('\nhasOwnPropertyで属性の有無を調べる');
elems = x.elem;
for(i in elems)
{
    elem = elems[i];
    str = "elem[" + elem.@id +
          "] hasOwnProperty('@test_attr') = " +
          elem.hasOwnProperty('@test_attr') +
          "\n         hasOwnProperty('@xlink::href') = " +
          elem.hasOwnProperty('@xlink::href');
    print(str);
}
// → @xlink::hrefは調べられず

print("\nhasOwnProperty('test_attr')はフィルタ演算に使えない");
elems = x.elem.(hasOwnProperty('@test_attr') );
print("x.elem.(hasOwnProperty('@test_attr') ) ... elems = '" + elems + "'");

print("\nフィルタ演算(0 < @属性.length() )で引ける");
elems = x.elem.(0 < @test_attr.length() );
print("x.elem.(0 < @test_attr.length() ) ... elems = '" +
    to_string_long_node(elems) + "'");

print("\nフィルタ演算(0 < @属性.length)ではダメ(カッコ注意)");
elems = x.elem.(0 < @test_attr.length);
print("x.elem.(0 < @test_attr.length) ... elems = '" + elems + "'");

print('\n======== 属性の書き換え ========');

print('\nid属性を持った「要素」を書き換え');
elems = x.elem.(@id == 'i1');
print('書き換え前\n' + x);
elems[0].@id = 'new_id';
print('書き換え後\n' + x);

elems[0].@id = 'i1';

print('\n要素から属性を取り出して書き換え');
attr = elems[0].@id;
print('書き換え前\n' + x);
attr = 'new_id';
print('書き換え後 ... 変化なし\n' + x);

print('\n======== 属性の型 ========');

print("\n属性の型は? (elems = x.elem.(@id == 'i1') → attr = elems[0].@idの場合)");
try
{
    print('typeof(attr) = ' + typeof(attr) );
    print('typeof(attr.parent) = ' + typeof(attr.parent) );
    print('attr.parent() = ' + attr.parent() );
}
catch(e)
{
    print(e); // TypeError
}

print('\n属性の型は? (elem.attribute()の場合)');
attr = x.elem.(@id == 'i5').attribute(xlink_href);
print('typeof(attr) = ' + typeof(attr) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('attr.parent() = ' + attr.parent() );

print('\n属性リストの型は? (x.elem.@idの場合)');
attrs = x.elem.@id;
attr = attrs[0];
print('attrs instanceof XMLList = ' + (attrs instanceof XMLList) );
print('typeof(attr) = ' + typeof(attr) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('attr.parent() = ' + attr.parent() );

print('\n属性リストの型は? (elem.attributes()の場合)');
attrs = x.elem.(@id == 'i4').attributes();
attr = attrs[0];
print('attrs instanceof XMLList = ' + (attrs instanceof XMLList) );
print('typeof(attr.parent) = ' + typeof(attr.parent) );
print('typeof(attr) = ' + typeof(attr) );
print('attr.parent() = ' + attr.parent() );

print('\n当然、要素の親は普通に分かる');
elems = x.elem.child_elem;
print('elems[0].parent() = ' +
    to_string_long_node(elems[0].parent() ) );

function print_nodes(nodes)
{
    var str = '';
    for(var i in nodes)
    {
        str += "'" + nodes[i] +"', ";
    }
    print(str.substring(0, str.length - 2) );
}

function to_string_long_node(nodes)
{
    return nodes.toString().substring(0, 10) + ' ... 略';
}

実行結果の全文はこちら、

======== 属性へのアクセス ========

id属性の一覧
'i0', 'i1', 'i2', 'i3', 'i4', 'i5'

test_attr属性の一覧
't', ''

xlink:href属性の一覧
'#i0', '#i1', '#i2'

id属性を持った「要素」をフィルタ演算で引く
x.elem.(@id == 'i1') ) ... id = i1

test_attr属性を持った「要素」をフィルタ演算で引く
x.elem.(@test_attr == 't') ) ... test_attr = t

名前空間付きの属性にアクセスするにはまずオーナー要素を探さないとダメかも?
x.elem.(@id == 'i5').attribute(xlink_href) ... attr = '#i2' (QNameを使用)
attrの名前空間はhttp://www.w3.org/1999/xlink

======== 名前空間自体を表す属性 ========

名前空間自体を表す属性にアクセスするのは通常の方法では無理
x.attribute(xmlns_xlink) = '' (QNameを使用)
x.@xmlns::xlink = ''

x.attributes()で全部リストアップしてnamespaceオブジェクトを持たないものを探す
  属性 dummy のnamespaceは''
  属性 xlink=http://www.w3.org/1999/xlink はnamespaceを持たない

======== 属性の有無を調べる ========

hasOwnPropertyで属性の有無を調べる
elem[i0] hasOwnProperty('@test_attr') = true
         hasOwnProperty('@xlink::href') = false
elem[i1] hasOwnProperty('@test_attr') = true
         hasOwnProperty('@xlink::href') = false
elem[i2] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i3] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i4] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false
elem[i5] hasOwnProperty('@test_attr') = false
         hasOwnProperty('@xlink::href') = false

hasOwnProperty('test_attr')では属性を持った要素は引けない
x.elem.(hasOwnProperty('@test_attr') ) ... elems = ''

フィルタ演算(0 < @属性.length() )で引ける
x.elem.(0 < @test_attr.length() ) ... elems = '<elem xmln ... 略'

フィルタ演算(0 < @属性.length)ではダメ
x.elem.(0 < @test_attr.length) ... elems = ''

======== 属性の書き換え ========

id属性を持った「要素」を書き換え
書き換え前
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>
書き換え後
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="new_id" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>

要素から属性を取り出して書き換え
書き換え前
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>
書き換え後 ... 変化なし
<root xmlns:xlink="http://www.w3.org/1999/xlink" dummy="d">
  <elem id="i0" test_attr="t"/>
  <elem id="i1" test_attr=""/>
  <elem id="i2"/>
  <elem id="i3" xlink:href="#i0"/>
  <elem id="i4" xlink:href="#i1"/>
  <elem id="i5" xlink:href="#i2">
    <child_elem/>
  </elem>
</root>

======== 属性の型 ========

属性の型は? (elems = x.elem.(@id == 'i1') → attr = elems[0].@idの場合)
typeof(attr) = string
typeof(attr.parent) = undefined
TypeError: Cannot find function parent in object new_id.

属性の型は? (elem.attribute()の場合)
typeof(attr) = xml
typeof(attr.parent) = function
attr.parent() = undefined

属性リストの型は? (x.elem.@idの場合)
attrs instanceof XMLList = true
typeof(attr) = xml
typeof(attr.parent) = function
attr.parent() = null

属性リストの型は? (elem.attributes()の場合)
attrs instanceof XMLList = true
typeof(attr.parent) = function
typeof(attr) = xml
attr.parent() = null

当然、要素の親は普通に分かる
elems[0].parent() = <elem xmln ... 略