HTMLDocument と XMLDocument を見分ける方法

HTMLDocument と XMLDocument を見分ける方法を模索していました。

2008-10-12追記: 内容を大幅に更新しました

何がしたいのか

uupaa-selector.js version 1.2では、HTMLDocuemnt と XMLDocument で、CSSセレクタの挙動が変化します。

  • HTMLDocuemnt なら 小文字の a タグ と 大文字の A タグ は一緒。 uu.css("a"); で <a>, <A> がヒットする
  • XMLDocument なら タグの大小は区別する。 uu.css("a"); なら <a> のみヒットする

これを実装するためには、XMLDocument と HTMLDocuemnt を見分けたうえで動作する必要があります。

どこを調べれば、XMLDocument を区別できるのか調べてみた

text/html, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body
Firefox2 true false HTMLBodyElement
Firefox3 true false HTMLBodyElement
IE6 定義されていません 定義されていません [Object]
IE8β2 定義されていません 定義されていません HTMLBodyElement
Safari3.1 true true HTMLBodyElement
Chrome true true HTMLBodyElement
Opera9.2x true 定義されていません HTMLBodyElement
Opera9.6β true false HTMLBodyElement

elm instanceof HTMLDocument === true なら、HTMLDocument と判断できそうです。
WebKit(Safari, Chrome)は、HTMLDocument と XMLDocument を多重にimplementしているのでしょうか。なかなか面白いですね。

application/xml, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body
Firefox2 false true undefined
Firefox3 false true undefined
IE6 - - -
IE8β2 - - -
Safari3.1 false true HTMLBodyElement
Chrome false true HTMLBodyElement
Opera9.2x true 定義されていません HTMLBodyElement
Opera9.6β false true undefined

elm instanceof HTMLDocument === false なら、XMLDocument と判断できそうです。

さて、IE, Opera をどうするか。

  • IEは、application/xmlレンダリングしませんが、サイ本によると Node.selectNodes は XML文書でしか存在しないメソッドらしいのでこれが使えそう。
  • Opera9.2x は、HTMLDocumentが常にtrueなので使えず。
    • IE専用メソッドのはずの Node.selectNodes が存在するが、HTML/XML文書共に存在するため、こちらも判断に使えず。
      • Opera9.2x で、XHTML/XML文書ならば、DTDは以下のような形になってるはずなので、document.doctype.publicId を検索し "XHTML"が含まれているならXHTML(XML)と判断できそう。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

      • os0xさんからのコメントで、document.createElement('p').tagName === 'P' が使えるとの情報が。

できた


最初の版

function isXMLDocument(elm) {
  return uu.ua.ie ? !!elm.selectNodes : // IE
         (uu.ua.opera && !uu.ua.opera95) ? /XHTML/i.test(elm.doctype.publicId) : // Opera9.2x
         !(elm instanceof HTMLDocument); // Gecko, WebKit, Opera95+
}

os0xさんからの情報を元に改善された版

function isXMLDocument(elm) {
  return uu.ua.ie ? !!elm.selectNodes : // IE
         (uu.ua.opera && !uu.ua.opera95) ? elm.createElement("p").tagName !== "P" : // Opera9.2x
         !(elm instanceof HTMLDocument); // Gecko, WebKit, Opera95+
}


参考までにjQueryの実装

/* jQuery 1.2.6 - New Wave Javascript
 * Copyright (c) 2008 John Resig (jquery.com)
 */
// check if an element is in a (or is an) XML document
  isXMLDoc: function( elem ) {
    return elem.documentElement && !elem.body ||
           elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
  },

jQuery のコードだと Opera9.2x, Safari, Chrome で動かないみたい。

反省会

ここから下は、2008-10-12 追記分

再調査した。

こんな感じのスクリプトを元に、 header('Content-Type: text/xml'); の部分をちょこちょこ替えてテストします。

<?php
header('Content-Type: text/xml');
$rv =<<<EOD
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>test</title>
</head>
<body>text/xml
<script>
function judge() {
  var rv = [];
  try { rv.push(document instanceof HTMLDocument); } catch(e) { rv.push("fail HTMLDocument"); }
  try { rv.push(document instanceof XMLDocument); } catch(e) { rv.push("fail XMLDocument"); }
  try { rv.push(document.body); } catch(e) { rv.push("fail document.body"); }
  try { rv.push(document.createElement("p").tagName !== document.createElement("P").tagName); } catch(e) { rv.push("fail p !== P"); }
  try { alert(rv.join(",")); } catch(e) { ; }
  return rv.join(",");
}
judge();
</script>
</body>
</html>
EOD;
print $rv;
?>
Content-Type: text/html, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body judge()
Firefox2 true false HTMLBodyElement false
Firefox3 true false HTMLBodyElement false
IE6 × × [object] false
IE8β2 × × HTMLBodyElement false
Safari3.1 true true HTMLBodyElement false
Chrome true true HTMLBodyElement false
Opera9.2x true × HTMLBodyElement false
Opera9.6β true false HTMLBodyElement false
Content-Type: text/xml, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body judge()
Firefox2 false true undefined true
Firefox3 false true undefined true
IE6 × × × ×
IE8β2 × × × ×
Safari3.1 false true HTMLBodyElement true
Chrome false true HTMLBodyElement true
Opera9.2x true × HTMLBodyElement true
Opera9.6β false true undefined true
Content-Type: application/xml, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body judge()
Firefox2 false true undefined true
Firefox3 false true undefined true
IE6 × × × ×
IE8β2 × × × ×
Safari3.1 false true HTMLBodyElement true
Chrome false true HTMLBodyElement true
Opera9.2x true × HTMLBodyElement true
Opera9.6β false true undefined true
Content-Type: application/xhtml+xml, elm = document
Browser elm instanceof HTMLDocument elm instanceof XMLDocument elm.body judge()
Firefox2 true false HTMLBodyElement true
Firefox3 true false HTMLBodyElement true
IE6 × × ダウンロード(不明なMIMEタイプ) ×
IE8β2 × × ダウンロード(不明なMIMEタイプ) ×
Safari3.1 false true HTMLBodyElement true
Chrome false true HTMLBodyElement true
Opera9.2x true × HTMLBodyElement true
Opera9.6β true false HTMLBodyElement true

結論(take2)

できた(take2)

os0xさんからの更なる情報を元に改善された版

function isXMLDocument(elm) {
  return elm.createElement("p").tagName !== elm.createElement("P").tagName;
}

反省会(take2)

  • 手抜きとかじゃなくて、リアルに勘違いしてました。ごめんなさい。
    • 今日中に、p !== P 方式に差し替えた uupaa-selector.js version 1.3 をリリースする予定です。