jQuery コロン付きセレクタの指定方法

先日 Nike+ Running アプリが改悪になった記事で、このアプリの カスタマーレビューをRSS feedで表示してみました。その際に「<im:rating/>」という5段階評価のXML要素を取得し、五つ星で表示する処理を実装したのですが、iPhoneで表示したらなぜか星が表示されず、気持ち悪いので調査してみました。どうもXMLのコロン付きセレクタの場合、ブラウザによりjQueryの挙動が違うようです。

HTML

$(“コロン付きidセレクタ”)

  • コロン付きidセレクタの場合
    [cc lang=”php” width=”99%”]
    hogehoge1
    [/cc]

    以下の記述ではコロンがfilterと判断され、セレクタを識別できません。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] $(“#im:rating”)
    [/cc]

    以下のようにバックスラッシュでエスケープする必要があります。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] $(“#im\\:rating”)
    [/cc]

    基本ですね。idセレクタに関してはブラウザによる差異はありません。

$(“コロン付き要素名”)

  • コロン付き要素名の場合
    [cc lang=”php” width=”99%”] hogehoge2
    [/cc]

    以下の記述ではコロンがfilterと判断され、セレクタを識別できません。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] $(“im:rating”)
    [/cc]

    以下のようにバックスラッシュでエスケープする必要があります。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] $(“im\\:rating”)
    [/cc] こちらもブラウザによる差異はありません。

XML

ご存じのようにXMLには名前空間という仕組みがあり、名前空間は接頭辞とローカル名をコロンで区切ります。こちらのIBM記事を見ますと、jQueryはこのXML名前空間を全く処理することができないようです。したがって要素名は完全修飾子名(接頭辞:ローカル名(<im:rating/>))でアクセスする必要があるかと思います。

XML名前空間なしの場合

.find(“コロン付き属性値”)

  • コロン付き属性値の場合
    [cc lang=”xml” width=”99%”]
    hogehoge1

    [/cc]

    属性値であればエスケープ不要で以下の記述で動作します。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“content[id=’im:rating’]”)
    [/cc]

    こちらもブラウザによる差異はありません。

XML名前空間ありの場合

.find(“コロン付き属性名”)

  • コロン付き属性名の場合
    [cc lang=”xml” width=”99%”]
    hogehoge2

    [/cc]

    以下の記述ではコロンは構文エラーとなります。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“content[im:rating]”)
    [/cc]

    以下のようにバックスラッシュでエスケープする必要があります。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“content[im\\:rating]”)
    [/cc]

    こちらもブラウザによる差異はありません。

.find(“コロン付き要素名”)

  • コロン付き要素名の場合
    [cc lang=”xml” width=”99%”]
    hogehoge3

    [/cc]

    以下のようにエスケープするとIE,FireFoxでは動作しましたが、Chrome,Safariでは動作しませんでした。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“im\\:rating”)
    [/cc]

    一方以下のようにローカル名「rating」のみで指定ですと、今度はChrome,Safariでは動作しましたが、IE,FireFoxでは動作しませんでした。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“rating”)
    [/cc]

    ■ブラウザによる差異

    jQuery IE10 FireFox33 Chrome39 Safari7
    .find(“im\\:rating”) × ×
    .find(“rating”) × ×

    以下のように両方をカンマ区切りで指定すればブラウザによる差異を吸収できます。カンマは論理和「OR」と等価です。
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“im\\:rating, rating”)
    [/cc]

XML名前空間あり要素名、もといコロン付き要素名の場合のみ、なぜかブラウザにより差異がありました。バグかもしれませんね。またエスケープにバックスラッシュ2個というのもミソですが、JAVAの正規表現置換などですと、バクスラ8個(円マーク)なんて言う場合もありますからね!(笑)でもまあスッキリしてよかったです。

以下のサンプルをIE/FireFoxとChrome/Safariとで見比べてみれば違いがわかると思います。


カスタマーレビューに関しては、ローカル名が一意(ユニーク)であったため上記方法で解決しましたが、一般的には、名前空間が複数あり接頭辞が別でローカル名が同じという場合も多々あります。そのための名前空間なのですからね。この場合、今回の方法ではIE,FireFoxでは完全修飾子名で指定しますので識別できますが、Chrome,Safariはローカル名のみのため識別できず破綻します。find()メソッドの限界ですかね。ということで別の方法を探りました。

.children(“コロン付き要素名”)

  • コロン付き要素名の場合
    [cc lang=”xml” width=”99%”]
    hogehoge1
    hogehoge2

    [/cc]

    親要素<feed>の直下の子要素をターゲットにして指定
    [cc lang=”javascript” theme=”blackboard” width=”99%”] .find(“feed”).children(“im\\:rating”)
    .find(“feed”).children(“jn\\:rating”)
    [/cc]

    この指定方法であればブラウザによる差異はありません。子要素というピンポイントでの指定が必要ですが、jQueryであれば、この方法が一番かと思います。

getElementsByTagNameNS(“名前空間”, “ローカル名”)

  • コロン付き要素名の場合
    [cc lang=”xml” width=”99%”]
    hogehoge1
    hogehoge2

    [/cc]

    DOM操作による名前空間とローカル名を指定
    [cc lang=”javascript” theme=”blackboard” width=”99%”] getElementsByTagNameNS(“http://itunes.apple.com/rss/im”, “rating”)[0].childNodes[0].nodeValue
    getElementsByTagNameNS(“http://itunes.apple.com/rss/jn”, “rating”)[0].childNodes[0].nodeValue
    [/cc]

  • jQueryにこだわらなければ、IE9以降の条件付きですが、DOMによるgetElementsByTagNameNS(ns, name)メソッドでの取得も可能です。サレオツではないですが、この方法こそが名前空間を解決する一番ベタ(基本)なメソッドなんでしょうね!この指定方法もブラウザによる差異はありません。

    蛇足ですが、getElementsByTagName(name)で指定しますと、上記のようなブラウザによる差異がありました。ソースを見ないとなんともですが、おそらくjQueryのfind()メソッドの実装がgetElementsByTagName(name)なのではないかと思います。

jQueryはXMLをあまり得意としていないということがわかりました。このIBM記事での指摘でもありましたが、IE(Internet Explorer)がgetElementsByTagNameNS()をサポートしていないのが原因のようですが、これは5年前の記事の話で、確かにIE8まではサポート外でしたが、IE9でサポートされています。実際IE10では問題なく動作しています。レガシーが足を引っ張っている感じですね!
2014/11時点でIE8のブラウザシェアが15.11%(当サイトはIE7,8が12%)ほどですから、まだまだですかね。またOSシェアを見る限り、Windows XPが13.57%とそのままIE8のシェアになってる感じですかね。Safari以下にならないと斬り捨てるのはまだまだ躊躇しますね。
jQuery2.x系はIE9以降のサポート(IE <9 not supported)のはずなので、ぜひ対応して欲しいですね!