uhyohyo.net

JavaScript初級者から中級者になろう

六章第三回 XMLとDOM

このページの最終更新日:

今回は、DOMでXMLを扱うときの方法について解説します。とはいっても基本はHTMLと同じなので、XMLに特有の部分を解説することになります。

今回は、簡単に使えるXML文書として、前回に引き続きXHTML文書を使います。XHTML文書がサンプルに出てきた場合、拡張子は.xhtmlで保存してください。そうしないとXHTML文書として扱われません。

XHTMLではないXML文書を扱うには他のファイルから読み込む必要があり、それは大変なのでまた今度扱うことにします。

ノードと名前空間

前回述べたように、XMLではノードが名前空間に属しています。

名前空間の情報を得る

次の場合を見てみましょう。


<?xml version="1.0" ?>
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xhtml:meta charset="UTF-8" />
    <xhtml:title>test</xhtml:title>
  </xhtml:head>
  <xhtml:body>
    <xhtml:p>test</xhtml:p>

    <xhtml:script type="text/javascript">
      <![CDATA[
        console.log(document.documentElement.tagName);
      ]]></xhtml:script>
  </xhtml:body>
</xhtml:html>

デフォルト名前空間を使用せずに、名前空間接頭辞にxhtmlを使って書いた形です。上の文書ではdocument.documentElement.tagNameを表示しています。これはhtml要素のノードのtagNameを表示することになります。

このとき、html要素のtagNameは"xhtml:html"となっています。つまり、tagNameには名前空間接頭辞も含んだタグ名が入っているということです。

ちなみにこのように名前空間接頭辞も含んだタグ名は「qualified name」の略で「QName」とよばれます。

ただ、同じ名前空間でも名前空間接頭辞は人によって違う可能性があり、名前空間接頭辞の情報はあまり役に立ちません。名そこで、前空間接頭辞とそれ以外とを別々に取得する方法もあります。


<?xml version="1.0" ?>
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xhtml:meta charset="UTF-8" />
    <xhtml:title>test</xhtml:title>
  </xhtml:head>
  <xhtml:body>
    <xhtml:p>test</xhtml:p>

    <xhtml:script type="text/javascript">
      <![CDATA[
        console.log(document.documentElement.prefix);    // "xhtml"
        console.log(document.documentElement.localName); // "html"
      ]]></xhtml:script>
  </xhtml:body>
</xhtml:html>

prefixとlocalNameという2つのプロパティが出てきました。prefixが名前空間接頭辞、localNameがローカル名(接頭辞を含まないタグ名)です。

また、名前空間の実体は接頭辞ではなくURIです。例えばXHTMLの場合はhttp://www.w3.org/1999/xhtmlでした。情報としては名前空間接頭辞よりもこちらのほうが有用です。名前空間自体を取得するにはnamespaceURIプロパティを使います。


<?xml version="1.0" ?>
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xhtml:meta charset="UTF-8" />
    <xhtml:title>test</xhtml:title>
  </xhtml:head>
  <xhtml:body>
    <xhtml:p>test</xhtml:p>

    <xhtml:script type="text/javascript">
      <![CDATA[
        console.log(document.documentElement.namespaceURI); // "http://www.w3.org/1999/xhtml"
      ]]></xhtml:script>
  </xhtml:body>
</xhtml:html>

名前空間を持ったノードを作る

さて、上のnamespaceURIは書き換えられません。だから、あるノードを作るとき、そのノードが属する名前空間は作るときに決めることが必要になります。まあ、tagNameなども書き換えられないので当然ですね。

名前空間を指定してノードを作るには次のようにします。


<?xml version="1.0" ?>
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xhtml:meta charset="UTF-8" />
    <xhtml:title>test</xhtml:title>
  </xhtml:head>
  <xhtml:body>
    <xhtml:p>test</xhtml:p>

    <xhtml:script type="text/javascript"><![CDATA[
      var newp = document.createElementNS("http://www.w3.org/1999/xhtml", "xhtml:p");

      newp.appendChild(document.createTextNode("newp"));

      document.body.appendChild(newp);

      console.log(newp.namespaceURI);
    ]]></xhtml:script>
  </xhtml:body>
</xhtml:html>

ここで、名前空間を指定して要素のノードを作っているのはcreateElementNSというメソッドです。createElementの名前空間あり版です。

引数も2つに増えていて、1つめが名前空間のURI、2つめが接頭辞つきのタグ名(QName)です。

これで名前空間に属するノードができます。最後にconsole.logでnamespaceURIを表示していますが、ちゃんと"http://www.w3.org/1999/xhtml"が表示されます。

ちなみに、createElementNSの次にテキストノードを作って追加していますが、テキストノードは特に名前空間に属したりはしないので、特に変わりません。

また、今回はQNameとして接頭辞つきの名前を指定しましたが、JavaScriptから新しく要素を作るときには接頭辞はあまり関係ありません。


<?xml version="1.0" ?>
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xhtml:meta charset="UTF-8" />
    <xhtml:title>test</xhtml:title>
  </xhtml:head>
  <xhtml:body>
    <xhtml:p>test</xhtml:p>

    <xhtml:script type="text/javascript"><![CDATA[
      var newp = document.createElementNS("http://www.w3.org/1999/xhtml", "aaaaaaa:p");

      newp.appendChild(document.createTextNode("newp"));

      document.body.appendChild(newp);

      console.log(newp.namespaceURI);
    ]]></xhtml:script>
  </xhtml:body>
</xhtml:html>

のようにしても正しくXHTMLの名前空間に属します。

だから、事実上接頭辞は省略できます。第二引数はただ単に"p"としても一応問題ないわけです。

また、名前空間を含む文書で普通のcreateElementを使用した場合、名前空間に属しません。ただし、XHTML文書の場合は親切にも自動で"http://www.w3.org/1999/xhtml"に設定してくれます。なので、XHTML文書中でHTML要素のノードを作る場合はcreateElementを使ってもOKです。

getElementsByTagNameNS

getElementsByTagNameの名前空間対応版もあります。それがgetElementsByTagNameNSです。これが威力を発揮するのは、例えばXHTML文書に他の名前空間の要素を埋め込んだ場合です。


<?xml version="1.0" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:abc="http://example.com/abc">
  <head>
    <title>test</title>
  </head>
  <body>
    <p><a href="http://google.com/">Go<abc:a>ogl</abc:a>e</a></p>
    <p><a href="http://google.com/"><abc:a>Goo</abc:a>gle</a></p>
  </body>
</html>

この例では2つの名前空間があり、両方の名前空間に属するaという要素が登場しています。この中からリンクのa(XHTMLのa)だけを抜き出したい場合に威力を発揮します。

例えば、次のようにします。

document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "a")

第一引数が名前空間で、第二引数がタグ名です。これで、指定したタグのち、その名前空間に属するものだけを取得できます。

注意すべき点は、ここでのタグ名はローカル名(接頭辞がつかない名前)を指定するという点です。

一方、普通のgetElementsByTagNameの場合、指定するのはQNameです。名前空間は関係なく指定されたQNameを持つ要素が列挙されます。

QNameは接頭辞によって変わりうるので、名前空間を意識する場合はgetElementsByTagNameNSを使いましょう。

ちなみに、getElementsByTagNameNSの第一引数に"*"を指定すると、全ての名前空間から探すことができます。

属性の名前空間

前回解説しませんでしたが、実は属性も名前空間を持ちます。

つまり、例えば

<abc:a abc:aaa="aaaaaa" />

という感じですね。ここでは属性にabcという名前空間接頭辞が付いてabc:aaa属性になっています。

実は、要素に対する名前空間と属性に対する名前空間では少し違う部分があります。タグ名に接頭辞がつかない場合はデフォルト名前空間に属することになっていましたが、属性の場合はそのような名前空間に属しません

それに対し、属性に接頭辞をつけた場合、その属性はグローバル属性というものになります。この属性は、「要素から独立している」のだそうです。一応要素とくっついていますが、要素に属しているというわけではないらしいです。例えば、

<xhtml:a href="http://google.com/" abc:aaa="aaaa">

のような場合に、このa要素はxhtmlの名前空間に属する要素です。hrefは名前空間に属していないから、このa要素に属していることになりますが、対してabc:aaa属性は、abcで表される別の名前空間に属する属性ということになります。

このときこのaaaはグローバル属性です。XHTMLで書かれた文書に何か独自の情報を追加したいとき、このように別の名前空間の属性を要素の属性としてくっつけておくことができます。

もともと属する名前空間が違うから、このa要素はaaaなんて属性は知りません。だから、aaaがa要素に属しているというわけではないそうです。そういう意味で、このaaaは独立しているといえます。

これらの属性は、JavaScriptでの扱い方が違います。グローバル属性でない場合(要素に属した属性の場合)、今まで同様getAttributeとsetAttribute(二章第十回)で操作できます。

対して、グローバル属性は、名前空間に属しているから扱い方が違います。

<a aaa:abc="..." bbb:abc="..." />

のような場合に区別する必要があるからですね。

ここで使うのが、getAttributeNSsetAttributeNSです。

まず、getAttributeNSは引数が2つになりました。


<?xml version="1.0" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:abc="http://example.com/abc">
  <head>
    <title>test</title>
  </head>
  <body abc:aaa="ababababab">
    <p>test</p>

    <script type="text/javascript"><![CDATA[
      console.log(document.body.getAttributeNS("http://example.com/abc", "aaa") );
    ]]></script>
  </body>
</html>

第一引数は名前空間のURIで、第二引数が属性のローカル名(接頭辞がつかない名前)です。

setAttributeNSも引数が1つ増えます。


<?xml version="1.0" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:abc="http://example.com/abc">
  <head>
    <title>test</title>
  </head>
  <body abc:aaa="ababababab">
    <p>test</p>

    <script type="text/javascript"><![CDATA[
      document.body.setAttributeNS("http://example.com/abc", "abc:aaa", "cccccc");
    ]]></script>
  </body>
</html>

第一引数が名前空間、第二引数が属性名、第三引数が変更後の値ということになります。

ここで注意するのが、getAttributeNSではローカル名だった第二引数が、setAttributeNSではQName(接頭辞も含む名前)になっていることです。とはいっても、createElementNSの場合と同じように接頭辞はあまり意味がありません。

以上でXHTML・XMLに関する解説は終了です。ここで解説しなかったことに関しては、だいたいはHTMLのノウハウが通用します(というより、本来DOMとはそもそもXMLを操作するためのものであって、それをHTMLにも適用しているのだという見方もできます)。