質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

DOM操作がChromeで動くのにIEやEdgeで動かない (childrenやinnerHTMLが動かない)

受付中

回答 4

投稿

  • 評価
  • クリップ 2
  • VIEW 20K+

HisayukiIgeta

score 10

前提・実現したいこと

初めて投稿します。調べたがわからず途方にくれてしまい、勇気を出して書きます。
Chromeで動いているとても簡単なJavaScriptなのですが、IEやEdgeで動かないのです。
DOM操作のところです。
これだけシンプルだと、IEやEdgeのバグか仕様かな?とも思ったのですが、ネットで検索しても同様の報告はなく、私の初歩的な勘違いの可能性もあって、質問してみることにしました。

下記サンプルコードにあるように、DOM操作をしているのですが、ChildNodesは動くのですがChildrenが動きません。
またinnerHTMLも動きませんし、getElementByIdも動かないようです。
対象をDocumentにするとIEやEdgeでも動くのですが、このようにparseFromStringを使って代入したDOMのオブジェクトに対する操作で起きてしまいます。
何がいけないのでしょうか?
ご教授いただければ幸いです。

発生している問題・エラーメッセージ

下記サンプルで、Alertで<3>を表示しようとしているところでAbortします。
サンプルソースをシンプルにするために省きましたが、innerHTMLや、getElementByIdも動きません。

該当のソースコード

<html>
<HEAD>
<meta charset="UTF-8" />
<script>
function init() {
var dpObj = new DOMParser();
var xmlDoc = dpObj.parseFromString('<?xml version="1.0" encoding="UTF-8"?><root id="11" result="abc"><test id="22">てすと</test>\n<test id="33"><test2></test2></test></root>', "text/xml");
alert("<1>"+document.getElementById("table01").children.length+", "+document.getElementById("table01").innerHTML);
alert("<2>"+xmlDoc.getElementsByTagName("root")[0].childNodes.length+", "+xmlDoc.getElementsByTagName("root")[0].childNodes[2].innerHTML);
alert("<3>"+xmlDoc.getElementsByTagName("root")[0].children.length);
}
</script>
</HEAD> 
<BODY onLoad="init()"> 
<table id='table01' border=1><tr><td>あいうえお</td></tr></table>
</BODY>
</html>

試したこと

補足情報(言語/FW/ツール等のバージョンなど)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+4

確認してみました。

https://developer.mozilla.org/ja/docs/Web/API/ParentNode/children

こちらを見るとわかりますがIEの場合、Elementに対してはchildrenをサポートするものの、Documentに対してはサポートがされていません。
取得するものがElementオブジェクトだったとしても、親に依存するようで、親がDocumentオブジェクトの場合はサポートされていませんでした。(Chrome等はDocumentオブジェクトでもサポートしているのでうまくいく)

なので結論としては仕様ですね・・・。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/30 15:27

    Safariでも確認いただき、ありがとうございます。

    XMLパーサーに問題がある・・・、Microsoftとしたことが・・・。
    一番落としどころにしたくなかった結果にいきそうですね。

    かなり作り込んでしまっているので、後戻りもできず、もう少しもがきたいと思いますが、最後は「IE/Edge未対応」アプリとなるしかなさそうですね。
    ありがとうございます。

    キャンセル

  • 2017/06/30 15:32

    ActiveXのXMLParserを試してみるのも良いかもしれません。
    私の方では確認していません。。。

    キャンセル

  • 2017/06/30 15:36

    なるほど。
    ブラウザ判定をして、IEかEdgeだったら、ActiveXのXMLParserを使うわけですね。
    ちょっと調べてやってみますね。
    ありがとうございます。

    キャンセル

+4

 DOMParser#parseFromString

DOMParser#parseFromString は「Document インターフェースを持つオブジェクト」を返します。

Interface ParentNode

下記URLより引用。

interface ParentNode {
  [SameObject] readonly attribute HTMLCollection children;
  (中略)
};
Document implements ParentNode;
DocumentFragment implements ParentNode;
Element implements ParentNode;

Interface Document, Element は Interface ParentNode を実装しなければなりません。
つまり、ParentNode#children を実装しなければなりません。

 Interface Node

下記URLより引用。

interface Node : EventTarget {
(中略)
    [SameObject] readonly attribute NodeList childNodes;

Interface Node は childNodes プロパティを持たなければなりません。

 Interface NonElementParentNode

下記URLより引用。

interface NonElementParentNode {
  Element? getElementById(DOMString elementId);
};
Document implements NonElementParentNode;

Interface Document は Interface NonElementParentNode を実装しなければなりません。
つまり、NonElementParentNode#getElementById も実装しなければなりません。

 Interface Document

下記URLより引用。

interface Document : Node {
  (中略)
  HTMLCollection getElementsByTagName(DOMString qualifiedName);

Interface Document は Interface Node を継承します。
Interface Document は getElementsByTagName を実装しなければなりません。

 Interface Element

下記URLより引用。

interface Element : Node {
  HTMLCollection getElementsByTagName(DOMString

Interface Element は Interface Node を継承します。
Interface Element は getElementsByTagName を実装しなければなりません。

 Interface Element の拡張

下記URLより引用。

partial interface Element {
  (中略)
  attribute DOMString innerHTML;

Interface Element は innerHTML を実装しなければなりません。
更に同URLより引用。

element . innerHTML [ = value ]
(中略)
In the case of an XML document, throws a "InvalidStateError" DOMException if the Element cannot be serialized to XML, or a "SyntaxError" DOMException if the given string is not well-formed.

XMLドキュメントの場合、要素をXMLにシリアル化できない場合は "InvalidStateError" DOMExceptionをスローし、指定された文字列が整形式でない場合は "SyntaxError" DOMExceptionをスローします。

 まとめ

まとめると、下記のようになります。

  • DOMParser#parseFromString は「Document インターフェースを持つオブジェクト」を返します。
  • Interface Document は getElementsByTagName, getElementById, childNodes, children を持ちます。
  • Interface Element は getElementsByTagName,  childNodes, children を持ち、getElementById を持ちません。
  • Interface Element は innerHTML を持ちますが、それがXML文書であるならば、XML構文が正しくなければ例外を返します。

この通りに動かないブラウザがあるなら、未実装もしくは実装バグと思われます。
http://caniuse.com やMDNに実装状況は載っているので、そちらを参照して下さい。

 innerHTML のIE実装バグ

下記URLより引用。

The innerHTML property is read-only on the col, colGroup, frameSet, html, head, style, table, tBody, tFoot, tHead, title, and tr objects.

innerHTMLプロパティは、col、colGroup、frameSet、html、head、style、table、tBody、tFoot、tHead、titleおよびtrオブジェクトでは読み取り専用です。
従って、IE ではtable要素ノードをinnerHTMLで書き換える事は出来ません。
(このバグはどこかのバージョンで修正されたような記憶があったようななかったような…、失念しました。)
table関係要素ノードのAPIを使うか、appendChild等の単純なDOM APIで操作して下さい。

 不具合の詳細について

Chromeで動いているとても簡単なJavaScriptなのですが、IEやEdgeで動かないのです。

IEやEdgeのバージョンはいくつですか。

下記サンプルコードにあるように、DOM操作をしているのですが、ChildNodesは動くのですがChildrenが動きません。
またinnerHTMLも動きませんし、getElementByIdも動かないようです。

childNodes は複数ありますが、全ての場所で動かないのですか。動く場所と動かない場所があるのではありませんか。
他のプロパティ(children, innerHTML, getElementById,, getElementsByTagName)についても同様に確認していますか。

エラーメッセージがコンソールに出力されていませんか。

 実装状況の検証 (IE11, Edge38)

SCRIPT5007: Unable to get property 'length' of undefined or null reference
というエラーが出た個所が、
xmlDoc.getElementsByTagName("root")[0].children.length
です。

エラーメッセージを言葉通りに読むと、Element インターフェースが children を持っていない(undefined)と読めます。
私の環境で検証したところでは、次のようになりました。

  • IE11 は Element.prototype.innerHTMLElement.prototype.childrenDocument.prototype.children を実装していない
  • Edge38 は Element.prototype.childrenDocument.prototype.children を実装していない
'use strict';
console.log('children' in Element.prototype);     // false (IE11, Edge38)
console.log('children' in HTMLElement.prototype); // true (IE11, Edge38)
console.log('children' in Document.prototype);    // false (IE11, Edge38)

console.log('innerHTML' in Element.prototype);      // false (IE11) / true (Edge38)
console.log('innerHTML' in HTMLElement.prototype);  // true (IE11, Edge38)

console.log('getElementsByTagName' in Document.prototype); // true (IE11, Edge38)
console.log('getElementsByTagName' in Element.prototype);  // true (IE11, Edge38)

console.log('getElementById' in Document.prototype); // true (IE11, Edge38)

console.log('evaluate' in Document.prototype);    // false (IE11) / true (Edge38)

HisayukiIgeta さんが書かれたコードを私の環境で検証したところ、Edge38 ではエラー無く実行できましたが、

var xmlDoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?><root id="11" result="abc"><test id="22">てすと</test>\n<test id="33"><test2></test2></test></root>', "text/xml");
console.log("<1>"+document.getElementById("table01").children.length+", "+document.getElementById("table01").innerHTML);  // <1>1, <tbody><tr><td>あいうえお</td></tr></tbody>
console.log("<2>"+xmlDoc.getElementsByTagName("root")[0].childNodes.length+", "+xmlDoc.getElementsByTagName("root")[0].childNodes[2].innerHTML);  // <2>3, <test2/>
console.log("<3>"+xmlDoc.getElementsByTagName("root")[0].children.length);  // <3>2

IE11では <2> の innerHTML が undefined を返し、<3> は例外で処理が停止されました。

var xmlDoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?><root id="11" result="abc"><test id="22">てすと</test>\n<test id="33"><test2></test2></test></root>', "text/xml");
console.log("<1>"+document.getElementById("table01").children.length+", "+document.getElementById("table01").innerHTML);  // <1>1, <tbody><tr><td>あいうえお</td></tr></tbody>
console.log("<2>"+xmlDoc.getElementsByTagName("root")[0].childNodes.length+", "+xmlDoc.getElementsByTagName("root")[0].childNodes[2].innerHTML);  // <2>3, undefined
console.log("<3>"+xmlDoc.getElementsByTagName("root")[0].children.length);  // 未定義または NULL 参照のプロパティ 'length' は取得できません

 未知のHTML要素ノード (HTMLUnknownElement)

parseFromStringの2つ目のパラメータで、text/xmlのところをtext/htmlにするとエラーがなくなるところにもヒントがありそうです。

そこまで検証する時間がとれなかったので未確認ですが、HTML文書には未知のタグ名を持つ要素ノード(HTMLUnknownElement)は HTMLElement を継承する仕様があり、IE11がHTMLUnknownElement を実装している可能性があります。
これは Object.prototype.toStringinstanceof 演算子を使うことではっきりします。
https://triple-underscore.github.io/HTML-dom-ja.html#htmlunknownelement

var root = xmlDoc.getElementsByTagName("root")[0];

console.log(Object.prototype.toString.call(root));
console.log(root instanceof HTMLElement);

 修正案

Edge38 では Document#evaluate が使えます。
IE11 にはありませんが、Function#call を併用すれば実用だと思います(そこまで時間が取れなかったので、未確認)。

document.evaluate.call(xmlDoc, ...);

Re: HisayukiIgeta さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/02 13:51

    ちなみに、「getElementByIdも動かない」のところですが、
    私が書いたサンプルソースの
    alert("<3>"・・・の1行を
    alert("<5>"+xmlDoc.getElementById("11").getAttribute("result"));
    に置き換えると再現を確認出来ます。
    Chromeでは問題なく動くのですが、IE/Edgeでは動かないのです。。
    と思っていま再確認してみたところ、Edgeでは動き、IEで動かないですね。

    キャンセル

  • 2017/07/02 17:47

    > text/htmlで実装し直すとどうなるのだろうとテストしていると
    HTML文書として認識させると、XML整形式違反などの強力なバリデーション機能が働かなくなるので、私としてもお勧めはし難いです。

    > Document#evaluateは、xpathの指定で欲しいエレメントをゲットする手法なのですね?そしてIEでは使えないのですね?
    IE11ではHTML文書にしかその関数がないので、callでthis値を書き換えてやることでXML文書でも使えるようにしてやる事を試みます。
    元々、XPathはXML用途なので、使いこなせれば強力な味方になると思います。

    > alert("<5>"+xmlDoc.getElementById("11").getAttribute("result"));
    > に置き換えると再現を確認出来ます。
    確認てすが、「getElementById が動かない」で間違いないでしょうか。
    つまり、「getAttributeが動かない」ではない事は確認済みという事になるのですが…。

    また、「動かない」とは具体的にどういう動作なので。
    「getElementByIdが」何らかのエラーを返すのでしょうか。
    期待に反して「getElementById が」 null を返すのでしょうか。

    こういう複雑な問題に対処する場合は関数をメソッドチェーンで呼び出さず、「getElementById のみでテスト」する事が重要です。
    ある程度、経験を積めばエラーメッセージから察する事もありますが、確実なのはなやはり単体テストです。
    シンプルな構成でテストをし、条件を変えてみたり、仕様書をあたってみたりして、トライアンドエラーを繰り返すことになります。

    とりあえず、気になるのはXML文書において、「id属性値は数字から始まることが出来ない」事でしょうか。
    文法エラーになってもおかしくないですが、getElementById がそのエラーを返すとは想定しづらいので別問題かもしれません。
    http://w4ard.eplusx.net/translation/W3C/REC-xml-20081126/#id

    キャンセル

  • 2017/07/03 01:03

    いろいろとありがとうございます。
    確かにXPathを使いこなせば強力な武器になりますね。

    今日は私が電池切れを起こし、もう寝ますが、明日も追及したいと思います。

    P.S.
    getElementByIdのところは、getAttributeではなく、getElementByIdのところですね。

    キャンセル

+1

DOMParser 未実装ブラウザーへの対応がこちらに記載されていますね

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/30 13:36

    ご回答ありがとうございます。早速試してみましたが、上手くいきませんでした。
    natively supportedの方に判定されてしまうようです。またソースを見るとtext/htmlの方の対処のようですね。
    こんなにシンプルなソースなのにChromeで動いてIE/Edgeで動かないのが不思議です。
    IEもV9以上から、EdgeはもちろんこういったDOMの基本的な機能を見落としているはずもないと思うので、余計混乱しています・・・。

    キャンセル

0

解決したわけではないのですが、当問題は結局マイクロソフトのバグだと認識し、以下のような回避策を試みようと思っています。
ご丁寧に回答いただいた3人の方々、本当にありがとうございました!

【回避方法】
var xmlDoc = dpObj.parseFromString('<?xml version="1.0" encoding="UTF-8"?><root id="11" result="abc"><test id="22">てすと</test>\n<test id="33"><test2></test2></test></root>', "text/html");

wk_xml = xmlDoc.getElementsByTagName("root")[0].cloneNode(true);
xmlDoc.removeChild(xmlDoc.childNodes[1]);
xmlDoc.appendChild(wk_xml);

【ポイント】
・text/xmlを諦め、text/htmlにしました。
・text/htmlにするとparseFromStringで出来上がるXML構造が変わりますので、既存のプログラムへの影響を最小限にするため、rootエレメント以下を保存した後に、htmlエレメント以下を一旦削除(removeChild)し、そしてルート配下にrootエレメントを付け直しました(appendChild)
・これにより、IE/Edgeでは効かなかった.childrenや、.innerHTMLが効くようになりました。

【残存する配慮点】
・ノード名が大文字で返って来るようになってしまったので、JavaScript上でノード名を比較する時は、比較する相手側に.toUpperCase()関数を付けて比較しなければならない
・xmlDoc.children[0]が相変わらず効かないので、ここは、xmlDoc.childNodes[1]で代替する

スッキリしない自己解決ですが、当面はこれで切り抜けようかと思います。

もし、もっとベターな方法がありましたら、引き続きご教授いただけたら幸いです。

ご回答いただけた方、またそうしようと当質問をお読みいただいた方々には感謝です。
ありがとうございました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る