第 10 回 DOM

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

10-1. DOM

DOM は XML 文書などをプログラムで扱うために提供される、オブジェクト指 向型の API です。 もともとは、 Netscape 社が提案した Dynamic HTML という技術が発端です。 これはブラウザが読み込んだ HTML を Javascript 言語が取り扱えるようにす るために、HTML 文書に API を与えたものです。 すぐに Microsoft 社が DHTML と JScript というよく似ているけど、互換性 があまりない形態で追従しました。 これらは現在では DOM Level 0 と呼ばれていますが、これは規格化されたも のではありません。

W3C が制定している DOM は Level 1, 2, 3 があります。 これらはプログラミング言語には中立ということで、 OMG IDL(ISO14750) というインターフェイス記述言語により定義されています。 但し、規格書の中では Java と Javascript(ECMA Script) の両方の記述も含 まれています。

WHATWGはHTML Standard を定める際に、 DOM Standard も定めていて、2019年 より、 DOM の標準化も WHATWGが担当します。

DOM の考え方

前回の XML 文書の例に対して、下記のようなオブジェクトを生成することが 目標となります。

例10-1

<class name="データ構造とアルゴリズム II" >
<teacher name="坂本直志" />
<time day="tue" period="2" />
</class>
XML のオブジェクト図

基本的には、要素間の関係は含む含まないの関係となり、さらに、ある要素を 直接含む要素は一つだけなので、木構造になります。 したがって、コンポジットデザインパターンを使用し、各要素を順に取り出す ことができます。

この他、DOM の検索 API が Document インタフェースに用意されて います。

getElementById
id 属性による検索
getElementsByTagName
タグ名による検索
getElementsByTagNameNS
名前空間を指定したタグ名検索

DOM Standard の基本構成

DOM Standard の基本構成は以下のようになっています。

Goals 
1 Infrastructure 
1.1 Trees 
1.2 Ordered sets 
1.3 Selectors 
1.4 Namespaces 
2 Events 
2.1 Introduction to "DOM Events" 
2.2 Interface Event 
2.3 Legacy extensions to the Window interface 
2.4 Interface CustomEvent 
2.5 Constructing events 
2.6 Defining event interfaces 
2.7 Interface EventTarget 
2.8 Observing event listeners 
2.9 Dispatching events 
2.10 Firing events 
2.11 Action versus occurrence 
3 Aborting ongoing activities 
3.1 Interface AbortController 
3.2 Interface AbortSignal 
3.3 Using AbortController and AbortSignal objects in APIs 
4 Nodes 
4.1 Introduction to "The DOM" 
4.2 Node tree 
4.2.1 Document tree 
4.2.2 Shadow tree 
4.2.2.1 Slots 
4.2.2.2 Slottables 
4.2.2.3 Finding slots and slottables 
4.2.2.4 Assigning slottables and slots 
4.2.2.5 Signaling slot change 
4.2.3 Mutation algorithms 
4.2.4 Mixin NonElementParentNode 
4.2.5 Mixin DocumentOrShadowRoot 
4.2.6 Mixin ParentNode 
4.2.7 Mixin NonDocumentTypeChildNode 
4.2.8 Mixin ChildNode 
4.2.9 Mixin Slottable 
4.2.10 Old-style collections: NodeList and HTMLCollection 
4.2.10.1 Interface NodeList 
4.2.10.2 Interface HTMLCollection 
4.3 Mutation observers 
4.3.1 Interface MutationObserver 
4.3.2 Queuing a mutation record 
4.3.3 Interface MutationRecord 
4.3.4 Garbage collection 
4.4 Interface Node 
4.5 Interface Document 
4.5.1 Interface DOMImplementation 
4.6 Interface DocumentType 
4.7 Interface DocumentFragment 
4.8 Interface ShadowRoot 
4.9 Interface Element 
4.9.1 Interface NamedNodeMap 
4.9.2 Interface Attr 
4.10 Interface CharacterData 
4.11 Interface Text 
4.12 Interface CDATASection 
4.13 Interface ProcessingInstruction 
4.14 Interface Comment 
5 Ranges 
5.1 Introduction to "DOM Ranges" 
5.2 Boundary points 
5.3 Interface AbstractRange 
5.4 Interface StaticRange 
5.5 Interface Range 
6 Traversal 
6.1 Interface NodeIterator 
6.2 Interface TreeWalker 
6.3 Interface NodeFilter 
7 Sets 
7.1 Interface DOMTokenList 
8 XPath 
8.1 Interface XPathResult 
8.2 Interface XPathExpression 
8.3 Mixin XPathEvaluatorBase 
8.4 Interface XPathEvaluator 
9 Historical 
9.1 DOM Events 
9.2 DOM Core 
9.3 DOM Ranges 
9.4 DOM Traversal 
Acknowledgments 
Index 
Terms defined by this specification 
Terms defined by reference 
References 
Normative References 
Informative References 
IDL Index

特に、構成で特徴的なのは、Event に関する章です。 プログラムがドキュメントを操作する際に、イベントにより動作をさせること になります。 従来は、イベントを生成する呼び出し側に onclick="..." のように開きタグの属性にプログラムを書くようになっていました。 しかし、DOM Standard では、ドキュメントが木構造で管理され、 イベントは送られたノードから親ノードに向かって送られるとし、 各ノードにイベントリスナを設定できるようになりました。

DOMの基本 API

DOM は XML 用のインタフェイスを記述しています。 これから説明するのは、 org.w3c.dom パッケージの内容です。 この節では主要なインタフェイスについて説明します。

Node

Node は DOM のすべてのオブジェクトが実装しているインタフェイスです。

まず、Node は名前と、値を持ちます。 値に関しては setter も用意されています。


[Exposed=Window]
interface Node : EventTarget {
  readonly attribute DOMString nodeName;
  [CEReactions] attribute DOMString? nodeValue;
  [CEReactions] attribute DOMString? textContent;
...
...

ノードの種類によって、番号が割り当てられていて、 getNodeType で 番号を取り出せます。


  readonly attribute unsigned short nodeType;
  const unsigned short ELEMENT_NODE = 1;
  const unsigned short ATTRIBUTE_NODE = 2;
  const unsigned short TEXT_NODE = 3;
  const unsigned short CDATA_SECTION_NODE = 4;
  const unsigned short ENTITY_REFERENCE_NODE = 5; // 歴史的
  const unsigned short ENTITY_NODE = 6; // 歴史的
  const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
  const unsigned short COMMENT_NODE = 8;
  const unsigned short DOCUMENT_NODE = 9;
  const unsigned short DOCUMENT_TYPE_NODE = 10;
  const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
  const unsigned short NOTATION_NODE = 12; // 歴史的

また 子ノードを持つので、それに関するメソッドが定義されています。


  readonly attribute Node? parentNode;
  readonly attribute Element? parentElement;
  boolean hasChildNodes();
  [SameObject] readonly attribute NodeList childNodes;
  readonly attribute Node? firstChild;
  readonly attribute Node? lastChild;
  readonly attribute Node? previousSibling;
  readonly attribute Node? nextSibling;

  [CEReactions] Node insertBefore(Node node, Node? child);
  [CEReactions] Node appendChild(Node node);
  [CEReactions] Node replaceChild(Node node, Node child);
  [CEReactions] Node removeChild(Node child);

なお、上記のメソッドで示されていますが、子ノードは NodeList 形式で集め られます。

古いDOMでは属性に関しては Node で定義されていて、 Element に継承さ れ、Element だけで利用可能でした。 しかし、DOM Standard では、 Element に移動し、付随するデータ構造なども Element 付近に定義が移動しました。

NodeList

NodeList の定義は以下の通りです。これは抜粋ではなく、すべてです。


package org.w3c.dom;
public interface NodeList {
    public Node item(int index);
    public int getLength();
}

このように、 NodeList 型は単に番号により Node が取り出せるだけの仕組み しか持ってなく、 iterator や for each 構文などもそのままでは使えません。

XML に関するインタフェース

これから、Node を継承した各データタイプの定義を見ていきます。 各データタイプにおいて、 nodeName, nodeValue, attributes の解釈が変わっ てきます。 始めに、 org.w3c.dom.Node のマニュアルにに記載されている表を抜粋します。

インタフェース nodeName nodeValue attributes
Attr Attr.name と同じ Attr.value と同じ null
CDATASection "#cdata-section" CharacterData.data (CDATA セクションの内容) と同じ null
Comment "#comment" CharacterData.data (コメントの内容) と同じ null
Document "#document" null null
DocumentFragment "#document-fragment" null null
DocumentType DocumentType.name と同じ null null
Element Element.tagName と同じ null NamedNodeMap
Entity エンティティー名 null null
EntityReference 参照されるエンティティーの名前 null null
Notation 表記法名 null null
ProcessingInstruction ProcessingInstruction.target と同じ ProcessingInstruction.data と同じ null
Text "#text" CharacterData.data (テキストノードの内容) と同じ null

Element

nodeType = 1 である、 ELEMENT_NODE のクラスは、値、名前、属性すべてを 持っています。 なお、 getNodeName と getTagName は同じ値になります。 また、 getAttribute(文字列) は、 getAttributes().getNamedItem(文字列) と同じです。 なお、setAttribute(文字列, 文字列) は自動的に属性オブジェクトを生成し て要素を追加してくれるので便利ですが、CDATA の検査などはしないとマニュ アルに注釈があります。

また、属性を持ちます。 、 Attibute は NamedNodeMap という形式で集められています。

Attr

属性を示すノードは org.w3c.dom.Attr インタフェースです。 Element で説明しましたが、Element では文字列を介して属性値を操作可能に するメソッドが追加されてました。 そのため、このノードを使用しなくても属性値の操作は可能です。

NamedNodeMap

NamedNodeMap の定義は以下がすべてです。


package org.w3c.dom;
public interface NamedNodeMap {
    public Node item(int index);
    public int getLength();

    public Node getNamedItem(String name);
    public Node setNamedItem(Node arg) throws DOMException;
    public Node removeNamedItem(String name) throws DOMException;

    public Node getNamedItemNS(String namespaceURI, String localName);
    public Node setNamedItemNS(Node arg) throws DOMException;
    public Node removeNamedItemNS(String namespaceURI, String localName)
                                  throws DOMException;
}

NodeList と同様に番号で呼び出すメソッドもありますが、 getNamedItem と いうメソッドで名前から Node を取り出すことができます。 setNamedItem では Node の名前を使って登録します。 これが void 型でないのは、置き換えられる Node がある場合に、それが返る ようになっているからです。 また、名前空間を指定するメソッドもあります。

Text, Comment, CDATASection

Text, Comment, CDATASection は Node を直接継承せず、 CharacterData イ ンタフェースを継承しています。 CharacterData インタフェースは下記のように、文字列に関するメソッドを追 加しています。 なお、CharacterData インタフェースの使用する文字は UTF-16 で記述されていると 仮定されています。 そのため、文字列操作においては 16 bit が基準になっています。


package org.w3c.dom;
public interface CharacterData extends Node {
    public String getData() throws DOMException;
    public void setData(String data) throws DOMException;
    public int getLength();
    public String substringData(int offset, int count) throws DOMException;
    public void appendData(String arg) throws DOMException;
    public void insertData(int offset, String arg) throws DOMException;
    public void deleteData(int offset, int count) throws DOMException;
    public void replaceData(int offset, int count, String arg) throws DOMException;
}

この CharacterData インタフェースをそれぞれ継承するのが Text, Comment, CDATASection ですが、 Text に splitText メソッドが追加される他は、メソッ ドは追加されません。


package org.w3c.dom;
public interface Text extends CharacterData {
    public Text splitText(int offset) throws DOMException;
}

package org.w3c.dom;
public interface Comment extends CharacterData {
}

package org.w3c.dom;
public interface CDATASection extends Text {
}

Document

Document は DOM の根の Node になります。 そのため、管理的なメソッドが定義されています。 Doctype, 根の Element が取得できます。


public interface Document extends Node {
    public DocumentType getDoctype();
    public Element getDocumentElement();
...

また、文書型定義にしたがって要素や属性などのファクトリが定義されていま す。 これを用いて様々な Node を作り、加えるなど操作を行います。


    public Element createElement(String tagName) throws DOMException;
    public DocumentFragment createDocumentFragment();
    public Text createTextNode(String data);
    public Comment createComment(String data);
    public CDATASection createCDATASection(String data) throws DOMException;
    public ProcessingInstruction createProcessingInstruction(String target,
                                           String data) throws DOMException;
    public Attr createAttribute(String name) throws DOMException;
    public EntityReference createEntityReference(String name) throws DOMException;

さらに、 XML 文書から特定のタグや ID を用いて要素を抜き出すメソッドも あります。


    public NodeList getElementsByTagName(String tagname);
    public Element getElementById(String elementId);

この他、名前空間を指定するものなどもあります。

10-2. HTML の DOM

DOM の一般的な構造については DOM Standard に記載されています。 一方、各要素固有の属性などについては、 HTML Standard に記載されて います。

なお、HTML のDOMに関しては、強力なAPIが追加されています。

querySelector
querySelectorAll

DOMの使用例

例10-2

古くは、input のボタンなどに onclick を指定して文字列の中にプログ ラムを埋め込んでいました。 新しい DOM では、イベントを定義していて、 イベントリスナにイベントと関数の対を追加するようになっています。 例10-2


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>例10-2</title>
    <style>
      .white{background:white;}
      .red { background: red;}
      .blue {background: blue;}
      .green {background: green;}
    </style>
  </head>
  <body>
    <p id="target">
      Hello world
    </p>
    <p>
      <input type="button" value="red" id="inputelement">
      <input type="button" value="blue" onclick="change('blue')">
      <a id="aelement">green</a>
      <a onclick="change('blue');">blue</a>
      <select id="selector">
	<option>white</option>
	<option>red</option>
	<option>blue</option>
	<option>green</option>
      </select>
    </p>
    <p id="pelement">
      hover
    </p>
    <script>
      const target=document.getElementById("target");
      function change(x){
      target.setAttribute('class',x);
      }
      const inputElement = document.getElementById('inputelement');
      inputElement.addEventListener('click',(event)=>{
      change('red');});
      const aElement = document.getElementById('aelement');
      aElement.addEventListener('click',(event)=>{
      change('green');});
      const selectElement = document.getElementById('selector');
      selectElement.addEventListener('change', (event) => {
      change(event.target.value);});
      const pElement = document.getElementById('pelement');
      pElement.addEventListener('mouseenter',(event)=>{
      change('red');});
      pElement.addEventListener('click',(event)=>{
      change('blue');});
      pElement.addEventListener('mouseleave',(event)=>{
      change('white');});
    </script>
  </body>
</html>

例10-3

特定のタグだけを取り出す方法を提示しています。 "*"で全てを取り出してから、名前の照合で抽出する必要があります。 また、HTMLCollection は for each 構文が使えないので、従来通りルー プ変数でアクセスします。例10-3


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>例10-3</title>
    <style>
      strong { background: red;}
      em {background: blue;}
      .yellow {background: yellow;}
    </style>
  </head>
  <body>
    <h1>見出し<em>1</em></h1>
    <p>本文1</p>
    <h2><strong>見出し</strong>2</h2>
    <p>本文2</p>
    <h3>見出し3</h3>
    <p>本文3</p>
    <h4>見出し4</h4>
    <p>本文4</p>
    <h2>見出し5</h2>
    <p>本文5</p>
    <h3>見出し6</h3>
    <p>本文6</p>
    <div id="target" class="yellow">
</div>
    <script>
      const t = document.getElementById('target');
      const elem =document.getElementsByTagName("*");
      const regex = new RegExp('^H[1-6]');
      const m=elem.length;
      for (var i=0; i< m; i++){
	  if(regex.test(elem[i].nodeName)){
	      var c=elem[i].childNodes;
	      for(var j=0; j< c.length; j++){
		  t.appendChild(c[j].cloneNode(true));
	      }
	  }
      }
    </script>
  </body>
</html>
  

例10-4

プログラムでHTML文書を自動的に生成する例を示します。 要素、テキストなどは document.createElement, document.createText で作成します。 作った要素を appendChild で目的の要素に追加します。 例10-4


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>例10-4</title>
  </head>
  <body>
    <ol id="target">
    </ol>
    <script>
      const ta=document.getElementById('target');
      for(var i=0; i<10; i++){
	  var e = document.createElement('li');
	  var t = document.createTextNode(i+'番目');
	  e.appendChild(t);
	  ta.appendChild(e);
      }
    </script>
  </body>
</html>
  

CanvasのDOM



  

10-3. DOM演習

演習10-1

2つの数の入力欄と、「合計」のボタンを画面に配置し、2つの数値を入力 して、合計ボタンを押すと、2つの数値の合計を計算して画面に表示する ようなWebアプリケーションを作りなさい。

10-4. DOM演習解答


坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科