2. Document Object Model Range

2.1. はじめに

Range は、Document, DocumentFragment, Attr 内の一連の内容を識別する。それは、一組の境界点 (boundary-points) 間の内容全ての選択と見なされうるという意味で、それは隣接 (contiguous) している。

Note: テキストエディタやワードプロセサにおいて、文書内のある点でマウスを押し下げ、別の点までマウスを動かし、マウスを放すことでユーザは選択領域を作成する。結果の選択領域は接触していて、2 点間の内容で構成される。

用語 '選択 (selecting)' は、各 Range が GUI ユーザに作成される選択領域に該当することを意味しない; しかし、そのような選択領域は Range として DOM ユーザに返されてよい。

Note: 双方向書字系 (アラビア語、ヘブライ語) においては、範囲は論理選択領域に該当させてよく、表示の際に隣接している必要なない。視覚的に隣接している選択は、いくつかのケースで用いられる場合も、一つの論理選択に対応しなくてもよく、それゆえ 1 個以上の Range によって表される方がよいこともある。

Range インターフェイスは、文書ツリーにアクセスし操作する Node インターフェイスのメソッドよりも、より高水準の同様のメソッドを提供する。例外的なのは、Range インターフェイスが提供する内容の挿入、削除、複製の各メソッドが、 DOM Core で可能な一連の Node 編集操作に直接マップ可能なことである。この意味で Range 操作は、実装に共通の編集パターンの最適化を可能にさせる簡便メソッドとして見ることもできる。

本章では、 Range の生成と移動のメソッド、そして Range の内容を操作するメソッドを含む、 Range インターフェイスについて説明する。

本セクション内に見られるインターフェイスは強制的なものではない。 DOM アプリケーションは DOMImplementation インターフェイスの hasFeature(feature, version) メソッドにパラメータ "Range" と "2.0" (それぞれ) を使用して、このモジュールが実装にサポートされているかどうかを判定してよい。このモジュールの完全なサポートのためには、実装は DOM Level 2 Core specification [DOM Level 2 Core] で定義される "Core" の機能もサポートしなければならない。 DOM Level 2 Core specification [DOM Level 2 Core] 内の 適合 についての追加情報を参照されたい。

2.2. 定義と表記法について

2.2.1. 位置

本章は文書の 2 個の異なる表現: 文書マークアップを含むテキストまたはソース形式、および DOM Level 2 Core [DOM Level 2 Core] の導入セクションに記述されるものと同様のツリー表現について言及する。

Range は、 Range の開始位置と終了位置に該当する 2 個の 境界点 で構成される。Document または DocumentFragment ツリー内における 境界点の位置は、ノードとオフセットによって特徴付けられる。 ノードは、境界点とその位置の コンテナ (container) と呼ばれる。 コンテナとその祖先は、境界点とその位置の 祖先コンテナ (ancestor container) である。 ノード内部のオフセットは境界点とその位置の オフセット (offset) と呼ばれる。コンテナが Attr, Document, DocumentFragment, Element, EntityReference のどれかのノードであるならば、オフセットはその ノード同士の間である。コンテナが CharacterData, Comment, ProcessingInstruction ノードのどれかであるならば、オフセットはそれに含まれる UTF-16 符号化文字列の 16-bit unit 同士の間である。

Range の 境界点 同士は共通の 祖先コンテナ を持たなければならず、それは Document, DocumentFragment, Attr ノードのどれかである。 つまり、 Range の内容は単一の Document, DocumentFragment, Attr ノードをルートとするサブツリー内部全体でなければならない。 この共通の 祖先コンテナ はその Range の ルートコンテナ (root container) とされる。 ルートコンテナ をルートとするツリーは、 Range の コンテキストツリー (context tree) とされる。

Range の 境界点コンテナ は、 Element, Comment, ProcessingInstruction, EntityReference, CDATASection, Document, DocumentFragment, Attr, Text node のどれかでなければならない。 DocumentType, Entity, Notation ノードは、 Range の 境界点祖先コンテナ にはならない。

文書のテキスト表現において、 Range の 境界点 はトークンの境界上にのみ存在する。つまり、テキスト範囲の 境界点 は、要素の開始また終了タグの中間、またエンティティや文字参照の内部には存在できない。 Range は、構造モデル内容の接触する部分に位置する。

文書のテキスト表現内の場所と DOM の Node ツリーインターフェイス内の場所の関係は、下図で説明される:


Range Example
Range Example

この図において、 4 個の異なる Ranges が説明される。それぞれの Range の 境界点 は、 s# (Range の開始位置) および e# (Range の終了位置) でラベル付けされる。 # のところは Range の番号である。 Range 2 については、開始位置は BODY 要素で、かつ H1 要素の直後と P 要素の直前であり、したがってその位置は BODY の 子 H1 と P の間である。 コンテナ が CharacterData ノードでない 境界点オフセット は、それが最初の子の前ならば 0、 最初の子と 2 番目の子の間ならば 1、となり、以下同様に続く。したがって、 Range 2 の開始位置については、 コンテナ は BODY で オフセット は 1 である。コンテナ が CharacterData ノードである 境界点オフセット も同様に取得されるが、代わりに 16-bit unit の位置を使用する。例えば、 Range 1 の s1 という 境界点 はその コンテナ として Text ノード ('Title' という内容のもの) を持ち、 2 番目と 3 番目の 16-bit unit の間であるから オフセット は 2 である。

Ranges 3 と 4 の 境界点 が、テキスト表現内の同じ位置に該当することに注目。 Range の重要な機能は、 Range の 境界点 が文書ツリー内部の各位置を曖昧になることなく表現できることである。

コンテナ境界点オフセット は、次の読み取り専用 Range アトリビュートを通して取得できる:

  readonly attribute Node startContainer; 
  readonly attribute long startOffset;
  readonly attribute Node endContainer; 
  readonly attribute long endOffset;

Range の 境界点 が同じ コンテナオフセット を持つ場合、 Range は 折りたたまれた Range と呼ばれる。 (これはユーザーエージェント内で挿入位置としてしばしば参照される。)

2.2.2. 選択と部分的選択

ノードまたは 16-bit unit unit は、それが Range の 2 個の 境界点 の間にある場合、 選択 されたと呼ばれる。つまりノードまたは 16-bit unit の直前の位置が Range の終了位置よりも前にあり、かつノードまたは 16-bit unit の直後の位置が Range の開始位置よりも後にある場合である。例えば、文書のテキスト表現内部において、要素は、その該当する開始タグが Range の開始位置より後に位置し、その終了タグが Range の終了位置よりも前に位置する場合、 Range よって 選択 される。上図の例文において、 Range 2 は P ノードを 選択 し、 Range 3 はテキスト "Blah xyz" で構成されるテキストノードを 選択 する。

ノードが Range の厳密な一方の 境界点祖先コンテナ である場合、それは Range によって 部分的に選択 されたと呼ばれる。例えば、上図の Range 1 を考えよう。要素 H1 は、Range の開始点がその子のうちの一つの内部にあるから、 Range に 部分的に選択 されている。

2.2.3. 表記法

本章における多くの例文が、文書のテキスト表現を用いて説明される。 Range の 境界点 は、太字の 2 個の 境界点 の間の文字 (マークアップ文字あるいはデータ文字) の表示で示される。

    <FOO>ABC<BAR>DEF</BAR></FOO>

双方の 境界点 が同じ位置にあるとき、それらは太字のキャレット ('^') であらわされる。

    <FOO>A^BC<BAR>DEF</BAR></FOO>

2.3. Range の生成

Range は、 DocumentRange インターフェイスの createRange() メソッド呼出しで生成される。このインターフェイスは、バインディングで規定されるキャスティングメソッドを使用する Document インターフェイスを実装しているオブジェクトから取得されうる。

  interface DocumentRange {
    Range createRange();
  }

このメソッドから返される Range の初期状態では、その両方の 境界点 が、当該 Document のどんな内容よりも前の開始地点に位置する。言い換えると、 境界点 それぞれの コンテナ は Document ノードであり、そのノード内部のオフセットは 0 である。

Document インターフェイス内のメソッドを使用して生成される (Nodes や DocumentFragments のような) オブジェクトのように、個々の文書インスタンス経由で生成される Range は、 Document, また Document が ownerDocument である DocumentFragment と Attr に関連する内容だけを選択できる。そのような Range は、他の Document インスタンスと共に使用されることは出来ない。

2.4. Range の位置変更

Range の位置は、 setStart メソッドと setEnd メソッドで、各境界点の コンテナオフセット を設定することで指定できる。

  void setStart(in Node parent, in long offset)
                        raises(RangeException);
  void setEnd(in Node parent, in long offset)
                raises(RangeException);

Range の一方の境界点が、 Range に現在のものとは違う ルートコンテナ を持つように設定されるならば、 Range は新しい位置まで 折りたたまれる。これは Range の両境界点が同じ ルートコンテナ をもたなければならないという制限を強制する。

Range の開始位置は、終了位置より後にならないことを保証される。この制限を強制するため、開始位置が終了位置よりも後に設定された場合は、 Range はその位置まで 折りたたまれる。同様に、終了位置が開始位置よりも前に設定された場合は、 Range はその位置まで 折りたたまれる

ツリー内のノードとの関係を示して Range の位置を設定することもできる:

  void setStartBefore(in Node node);
                              raises(RangeException);
  void setStartAfter(in Node node);
                       raises(RangeException);
  void setEndBefore(in Node node);
                      raises(RangeException);
  void setEndAfter(in Node node);
                     raises(RangeException);

ノードの 境界点コンテナ になり、 Range は上記の setStart()setEnd() の説明内に与えられるのと同じ制限を受ける。

Range はどちらかの境界点まで 折りたたまれ うる:

  void collapse(in boolean toStart);

パラメータ toStartTRUE を渡すと、 Range をその開始位置まで 折りたたみFALSE ならその終了位置まで collapse する。

Range が 折りたたまれているかどうかの検査は、 collapsed アトリビュートの検査で行える:

  readonly attribute boolean collapsed;

次のメソッドは、ノードの内容あるいはノード自身を選択する Range の生成に使用できる。

  void selectNode(in Node n);
  void selectNodeContents(in Node n);

次の例は、 selectNodeselectNodeContents メソッドの操作を説明する:

Before:
  ^<BAR><FOO>A<MOO>B</MOO>C</FOO></BAR>
After Range.selectNodeContents(FOO):
  <BAR><FOO>A<MOO>B</MOO>C</FOO></BAR>
(In this case, FOO is the parent of both 境界点s)
After Range.selectNode(FOO):
<BAR><FOO>A<MOO>B</MOO>C</FOO></BAR>

2.5. Range 境界点の比較

その境界点の比較によって、 2 個の Range を比較することができる:

  short compareBoundaryPoints(in CompareHow how, in Range sourceRange) raises(RangeException);

CompareHow のところは 4 個の値: START_TO_START, START_TO_END, END_TO_END and END_TO_START のうちの一つである。 Range の対応する境界点が sourceRange の対応する境界点の前、等位置、後のどれであるかによって、戻り値は -1, 0, 1 になる。 2 個の Range が異なる ルートコンテナ を持つ場合、例外が投げられる。

2 個の境界点 (あるいは位置) の比較の結果は、後に規定する。非公式であり常に正確な仕様でもないが、テキスト表現において境界点が他方の対応する位置の前、等位置、後にある位置に相当する場合、その境界点はもう一方の前、等位置、後にある.

A および B を、2 個の境界点あるいは位置とする。次のうちの一つが維持される: A は B の 、または A は B と 等しい、または A は B の B。一方が維持するものは 4 つのケースの検査によって次のように指定される:

第 1 のケースは、境界点同士は同じ コンテナ を持つ。 A の オフセット が B の オフセット よりも小さければ A は B の にあり、 A の オフセット が B の オフセット と等しければ A は B の 等位置 にあり、 A の オフセット が B の オフセット よりも大きければ A は B の にある。

第 2 のケースは、 A の コンテナ 子ノード C が、 B の 祖先 コンテナ である。このケースでは、 A の オフセット が子ノード C のインデックスより小さいか等しいならば A は B の であり、そうでなければ A は B の である。

第 3 のケースは、 B の コンテナ の子ノード C が、 A の 祖先 コンテナ である。このケースでは、子ノード C のインデックスが B の オフセット より小さいならば A は B の であり、そうでなければ、 A は B の である。

第 4 のケースは、他の 3 つのケースのどれでもない状態である: A および B のコンテナ同士が 兄弟 あるいは兄弟ノードの 子孫 である。このケースでは、 コンテキストツリー の前順序トラバーサルにおいて A の コンテナ が B の コンテナ の前 ならば A は B の であり、そうでなければ A は B の である。

文書のテキスト表現における同じロケーションが DOM ツリーにおける 2 個の異なる位置に対応できるため、 2 個の境界点について、それがテキスト表現において等しいものであっても等しく比較しないことが可能であることに注意。この理由のために、上記の非公式の定義は時として不正確である。

2.6. Range で内容を削除

Range に選択された内容は、次を使って削除できる:

  void deleteContents();

deleteContents() は、 Range に選択されたノードと文字の全てを削除する。 他の全ノードと全文字は Range の コンテキストツリー 内に残る。この削除操作の例をいくつか示す:

(1) <FOO>AB<MOO>CD</MOO>CD</FOO>  -->
<FOO>A^CD</FOO>
(2) <FOO>A<MOO>BC</MOO>DE</FOO>  -->
<FOO>A<MOO>B</MOO>^E</FOO>
(3) <FOO>XY<BAR>ZW</BAR>Q</FOO>  -->
<FOO>X^<BAR>W</BAR>Q</FOO>
(4) <FOO><BAR1>AB</BAR1><BAR2/><BAR3>CD</BAR3></FOO>
-->  <FOO><BAR1>A</BAR1>^<BAR3>D</BAR3>

Range の deleteContents() が呼出された後、その Range は 折りたたまれる。その Range に 部分的に選択 されたノードがなければ、それは例 (1) のようにもとの開始地点まで 折りたたまれる。ノードが Range に 部分的に選択 されていて、かつ Range の開始位置の 祖先コンテナ であり、ノードのどの 祖先 もこれら 2 個の条件を満たさない場合は、例 (2) と (4) のように Range はノードの直後の位置まで collapsed される。 ノードが Range に 部分的に選択 されていて、かつ Range の終了位置の 祖先コンテナ であり、ノードのどの祖先もこれら 2 この条件を満たさないが場合、例 (3) と (4) のように Range はノードの直前の位置まで collapsed される。

Note that if Range の削除が Text ノード同士を隣接させる場合、それらは自動的には結合されず、空の Text ノードは自動的に除去されないことに注意、 2 個の Text ノードが連結されるのは、それぞれが内容の削除される Range の境界点の一方のコンテナである場合のみである。隣接する Text ノード同士の結合、また空のテキストノードの除去については、 Node インターフェイスの normalize() メソッドが使用されるべきである。

2.7. 内容の抽出

Range の内容を削除ではなく抽出する必要がある場合、次のメソッドを使用できる:

  DocumentFragment extractContents();

extractContents() メソッドは、 deleteContents() メソッドと同様に Range の コンテキストツリー からノードを除去する。加えて新規 DocumentFragment 内に削除した内容を配置する。次の例は、返される DocumentFragment の内容を説明する:

(1) <FOO>AB<MOO>CD</MOO>CD</FOO>  -->
B<MOO>CD</MOO>
(2) <FOO>A<MOO>BC</MOO>DE</FOO>  -->
<MOO>C<MOO>D
(3) <FOO>XY<BAR>ZW</BAR>Q</FOO>  -->
Y<BAR>Z</BAR>
(4)
<FOO><BAR1>AB</BAR1><BAR2/><BAR3>CD</BAR3></FOO> -->
<BAR1>B</BAR1><BAR2/><BAR3>C</BAR3>

Range によって 部分的に選択 されるノードが複製されることに注意することが重要である。 Since そういうノードのの内容の一部は Range の コンテキストツリー 内に残されなければならず、内容の一部は新規 DocumentFragment に移動されなければならないため、 部分的に選択 されたノードの複製は、新規 DocumentFragment 内に含められる。 選択 された要素のためには複製が発生しないことに注意; これらのノードは 新規 DocumentFragment に移動される。

2.8. 内容の複製

Range の内容は、次のメソッドで複製することができる:

  DocumentFragment cloneContents();

このメソッドは、 extractContents() メソッドが返すものと同様の DocumentFragment を返す。だが、この場合は Range 内の元のノードと文字データは Range の コンテキストツリー から除去されない。代わりに、返される DocumentFragment 内部のノードとテキスト内容全ては複製されたものである。

2.9. 内容の挿入

次のメソッドを使用して、ノードを Range に挿入できる:

  void insertNode(in Node n) raises(RangeException);

insertNode() メソッドは、 Range の コンテキストツリー 内に指定されたノードを挿入する。 ノードは Range の開始 境界点 に挿入され、その位置を変更することはない。

Range の開始境界点が Text ノード内部にある場合、 insertNode 操作はその境界点で Text ノードを分割する。挿入されるノードもまた Text ノードである場合、結果の隣接した Text ノード同士は自動で正規化 (normalize) されない; この操作はアプリケーションに任される。

このメソッドに渡されるノードは DocumentFragment でもよい。この場合、 DocumentFragment の内容が Range の開始 境界点 に挿入され、 DocumentFragment 自身は挿入されない。 Node が下位ツリーのルートを表す場合、下位ツリー全体が挿入されることに注意。

ここでは、 Node インターフェイスの insertBefore() メソッドに適用するのと同じ規則を適用する。具体的には、渡された Node は、それが既に親を持っている場合、その存在している位置から除去される。

2.10. 内容を囲む

Range により選択された内容を包含する、単一のノードの挿入は次で行う:

  void surroundContents(in Node newParent);

surroundContents() メソッドは、 Range で選択された内容全部が 指定のノードがルートになるようにする。ノードは Attr, Entity, DocumentType, Notation, Document, DocumentFragment ノードであってはならない。次の例における Element ノード FOO を伴う surroundContents() 呼出しはこのようになる:

     Before:
       <BAR>AB<MOO>C</MOO>DE</BAR>
     After surroundContents(FOO):
<BAR>A<FOO>B<MOO>C</MOO>D</FOO>E</BAR>

Range の コンテキストツリー 上でのこのメソッドの効果を説明するもう一つの方法は、他の操作の中でこれを分解することである:

  1. extractContents() 呼出しで、 Range に選択される内容を除去する。
  2. insertNode() で、 Range が (抽出の後に) collapsed されたところにノード newParent を挿入する。
  3. 抽出された DocumentFragment の完全な内容を newParent 内に挿入する。具体的には、 newParentappendChild() を呼出し extractContents() 呼出しの結果として返された DocumentFragment の中を渡す。
  4. selectNode()newParent とその内容全てを選択する。

Range が Text ノード以外を 部分的に選択 している場合、 surroundContents() は例外を発生させる。 surroundContents() が例外を発生させる Range の例は:

     <FOO>AB<BAR>CD</BAR>E</FOO>

ノード newParent が任意の子を持つ場合、それの子は挿入前に除去される。また、ノード newParent が既に親を持つ場合、それは元の親の childNodes リストから取り除かれる。

2.11. その他のメンバ

Range を複製できるもの:

  Range cloneRange();

これは新規 Range を生成し、その cloneRange を呼出す Range が選択するるものと厳密に同じ内容を選択する。この操作は内容に影響を与えない。

Range の 境界点同士が同じ コンテナ を持つ必要がないため、次のアトリビュート:

  readonly attribute Node commonAncestorContainer;

を使用して、 Range の ルートコンテナ から最も遠くに下がった両境界点の 祖先コンテナ を取得する。

Range によって 選択、また部分的に選択された全文字データのコピーを取得するには:

  DOMString toString();

これは、 Range に選択された全文字データを単に連結し、それ以上のことはしない。これは TextCDATASection ノードの両方の文字データを含む。

2.12. 文書変更下の Range の修正

文書が修正されると、文書内の Range を更新する必要がある。例えば、 Range のある境界点がノードの内部にあり、文書からそのノードが除去された場合、その Range は何らかの方法で修正されなければ無効になる。本セクションでは、文書変更下においてどのように Range を有効であるように修正するかについて述べる。

文書変更下で Range に適用する一般的な原理が 2 個ある: 第一に文書内の Range 全てが任意の変更操作後も依然として有効であること、第二に、可能なだけ多く、 Range 全ては変更操作後の文書の同じ成分を選択することである。

Range に影響を与える文書ツリーの任意の変更は、基本的な削除と挿入の操作の組み合わせであると考えられる。実際、それらの操作は deleteContents()insertNode() Range メソッド、 Text 変更の場合は、 splitText()normalize() メソッドを使用して成し遂げられるものとして考えることができる。

2.12.1. 挿入

挿入は、文書内の 1 点、挿入点で発生する。文書ツリー内の任意の Range について、各境界点を考えよう。境界点が挿入後に変更される唯一のケースは、境界点と挿入点が同じ コンテナ を持ち、かつ挿入点の オフセット が Range の境界点の オフセット よりも厳密に小さいときである。この場合 Range の境界点 の オフセット は、挿入前と同じノード間または文字間になるように増加される。

内容が境界点に挿入されるとき、その関連する位置が維持されることになるならば、それは境界点が再配置されるべき所として曖昧になることに注意。 2 つの可能性がある: 新規に挿入される内容の開始位置、または終了位置である。この場合は コンテナ も境界点の オフセット も変更されないことにする。結果として、境界点は 新規に挿入される内容の開始位置に置かれる。

例:

次を選択する Range を想定しよう:

<P>Abcd efgh XY blah ijkl</P>

次の位置へのテキスト "inserted text" の挿入を考える:

1. 'X' の前:
<P>Abcd efgh inserted textXY blah ijkl</P>
2. 'X' の後:
<P>Abcd efgh Xinserted textY blah ijkl</P>
3. 'Y' の後:
<P>Abcd efgh XYinserted text blah ijkl</P>
4. 'h' in "Y blah" 内の 'h' の後:
<P>Abcd efgh XY blahinserted text ijkl</P>

2.12.2. 削除

文書ツリーからの削除は、解体する Range の最小集合に適用する deleteContents() 操作の連続と考えられる。削除における Range の修正法を定義するため、他の Range の deleteContents() 操作一回で、 Range に何が起こるかだけをを考える必要がある。そして、両方の境界点が同じアルゴリズムで変更されるので、実は、考える必要があるのは Range の境界点一つに何が起こるかだけである。

元の Range の境界点が削除されている内容の中にある場合、削除後に 内容の削除に使用された Range (折りたたまれている) の結果の境界点と同じ位置になる。

境界点 が削除されている内容の後にある場合、その コンテナ が削除されている Range の一方の境界点の コンテナ でもなければ、削除による影響はない。そういう共通の コンテナ が存在する場合は、境界点のインデックスは、境界点が コンテナ の内容に関係する位置を維持するように修正される。

境界点が削除される内容より前にある場合は、削除による影響は全くない。

例:

この例では、 deleteContents() が呼出される Range は下線で示される。

例 1.

削除前:

<P>Abcd efgh The Range ijkl</P>

削除後:

<P>Abcd Range ijkl</P>

例 2.

削除前:

<p>Abcd efgh The Range ijkl</p>

削除後:

<p>Abcd ^kl</p>

例 3.

削除前:

<P>ABCD efgh The <EM>Range</EM> ijkl</P>

削除後:

<P>ABCD <EM>ange</EM> ijkl</P>

この例では、削除後の開始境界点のコンテナが文字列 "ange" を持つ Text ノードである。

例 4.

削除前:

<P>Abcd efgh The Range ijkl</P>

削除後:

<P>Abcd he Range ijkl</P>

例 5.

削除前:

<P>Abcd <EM>efgh The Range ij</EM>kl</P>

削除後:

<P>Abcd ^kl</P>

2.13. Range インターフェイスの公式解説

要約として、完璧な、公式な Range インターフェイスの説明を以下に述べる:


Issued: / Revised: / All rights reserved. © 2002-2016 TAKI