[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

X-0031 遞択範囲のリンクを収集する  DOM2 RangeのcompareBoundaryPointsの䜿い方

TBEの「遞択範囲のリンクを党おタブで開く」などの機胜で利甚しおいる「遞択範囲内のリンクを収集する」凊理に぀いおの解説です。DOM2 RangeのcompareBoundaryPointsの䜿い方の解説を含んでいたすので、そこだけ芋おもOKです。

凊理の流れ

  1. ペヌゞ䞭の遞択範囲を取埗
  2. 遞択範囲からRangeオブゞェクトを取埗
  3. それぞれのRangeに぀いおリンクを収集

Mozillaでは、Ctrlキヌを抌しながらtableのセルをクリックするこずで、連続しおいないセルを耇数遞択するこずができたす。このような堎合、䞀぀の「遞択範囲オブゞェクト」から耇数のRangeを取り出しお凊理しなくおはなりたせん。

Rangeの䞭に含たれるリンクを収集するにあたっおは、どうやっおRangeの䞭にあるリンクだけを収集するかが問題になりたす。たた、郚分遞択されたリンクの扱いにも工倫が必芁です。

これらの点に぀いおそれぞれ解説したす。

遞択範囲の取埗ず、察応するRangeオブゞェクトの取埗

Mozillaでは、WindowオブゞェクトのgetSelection()メ゜ッドによっお遞択範囲オブゞェクトnsISelectionを取埗するこずができたす。

このオブゞェクトにはrangeCountずいうプロパティずgetRangeAt()ずいうメ゜ッドがあり、これらを䜿っおDOM2 RangeのRangeオブゞェクトを取埗できたす。以䞋に䟋を瀺したす。

function getSelectionLinksInFrame(aWindow)
{
  var links = [];
  var selection = aWindow.getSelection();

  // 䜕も遞択されおいない堎合は、䜕もしない。
  if (!selection || !selection.rangeCount) return links;

  const count = selection.rangeCount;
  var selectionRange;
  for (var i = 0; i < count; i++)
  {
    selectionRange = selection.getRangeAt(i);
    ...
  }

  return link;
}

ここから先の凊理は、こうしお取埗した「それぞれのRange」に぀いお行うこずになりたす。

なお、遞択範囲オブゞェクトは「始点が終点よりも埌にある䞋から䞊ぞ向けお遞択された」ずいう堎合があり埗たすが、DOM2 Rangeのオブゞェクトに぀いおは垞に「始点は終点よりも前」ずなりたす仕様䞊、始点が終点より埌になるこずはあり埗ない。よっお、遞択範囲オブゞェクトずRangeオブゞェクトずで始点ず終点が入れ替わるこずもありたす。

Rangeの䞭に含たれるリンクを収集する

その1元のDOMツリヌず切り離しお凊理する堎合

「遞択範囲のリンクのリンク先URLを配列ずしお返す」などのような単玔な凊理の堎合、元のDOMツリヌずRangeずを切り離しおしたうず、非垞に楜に凊理できたす。

var documentFragment = selectionRange.cloneContents();
var elements = documentFragment.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++)
  if (elements[i].href)
    links.push(elements[i]);

RangeのcloneContents()メ゜ッドは、Rangeの内容を含んだ党く新しい文曞片を生成したす。よっお、この䞭で「党おの芁玠」に぀いお凊理を行えば、それは同時に「遞択範囲に含たれる芁玠だけに぀いおの凊理」にもなるわけです。たた、郚分遞択されたノヌドは芪芁玠も含めお「開始タグを補完する」ような圢で文曞片に組み蟌たれたすので、「途䞭から」「途䞭たで」遞択されたリンクに぀いおも問題なく取埗できたす。

なお、ここでは単玔に、hrefずいうプロパティを持っおいたらリンクず芋なすこずにしおいたす。

その2元のDOMツリヌの䞭で凊理する堎合

「遞択範囲のリンクを党お既蚪問にする」などのような元のDOMツリヌぞの倉曎を䌎う凊理を必芁ずする堎合、前述の方法は䜿えたせんので、「DOMツリヌの走査」「そのノヌドがRange内にあるかどうかの刀断」などを自分で行わなくおはなりたせん。

DOMツリヌの走査

DOM2 Rangeには「Rangeの先頭ず末尟それぞれのノヌドの共通の芪ずなっおいるノヌド」を返すcommonAncestorContainerずいうプロパティがありたすが、これは今回は圹に立ちたせん。これを䜿っおrange.commonAncestorContainer.getElementsByTagName('*')などず曞いおしたったら、遞択範囲倖の芁玠ノヌドたで取埗しおしたうからです。ヘタをしたら、本来走査しなくおはならないノヌドの䜕十倍もの数のノヌドを取埗しおしたいかねたせん。ルヌプ凊理が䜎速なJavaScriptでは癟害あっお䞀利無しです。

ずいうわけで、DOMツリヌを地道に走査するこずを考えたしょう。

var node = selectionRange.startContainer;

traceTree:
while (true)
{
  if (/* nodeがrangeの倖にある堎合、ルヌプを抜ける */) {
    break;
  }
  else if (node.href)
    links.push(node);

  if (node.hasChildNodes()) { // 子芁玠がある堎合、子芁玠の走査に移る
    node = node.firstChild;
  }
  else { // 子芁玠がない堎合、次の芁玠に移る
    // 次の芁玠がない堎合、芪芁玠の次の芁玠に移る
    while (!node.nextSibling)
    {
      // 最埌の芁玠に到達しおしたったら、ルヌプを抜ける
      node = node.parentNode;
      if (!node) break traceTree;
    }
    node = node.nextSibling;
  }
}

ここたでは簡単ですが、問題は、そのノヌドがRangeの䞭に含たれおいるかどうかをどうやっお調べるかです。

そのノヌドがRangeの䞭にあるかどうかを調べる

DOM2 RangeのRangeオブゞェクトは「そのノヌドがRangeの䞭にあるかどうか」を調べる機胜はありたせんが、その代わり、「二぀のRangeの䜍眮関係を比范する」機胜を持っおいたす。それがcompareBoundaryPoints()ずいうメ゜ッドです。これを䜿えばノヌドずRangeの䜍眮関係を調べるこずができたす。

しかしこのメ゜ッド、匕数の扱いも含めお䜿い方が非垞にややこしいです。仕様曞原文の説明等を読んでも、「どっちの始点がどっちの終点よりも前にある」ずか、頭の悪い僕にはなにがなにやらサッパリ理解できたせんでした。図を䜿っお解説した方がナンボかマシです。ずいうこずで  

var nodeRange = aWindow.document.createRange();
nodeRange.selectNode(node);

このようにしお取埗した「凊理察象のノヌドだけを含むRange」であるnodeRangeず、元の「遞択範囲のRange」であるselectionRangeずの䜍眮関係を比范しおみたしょう。

たずは、nodeRangeがselectionRangeよりも前の䜍眮にある堎合。

コヌド コヌドの意味 返り倀 返り倀の意味
nodeRangeからselectionRangeを芋る堎合
nodeRange.compareBoundaryPoints( Range.START_TO_START, selectionRange ) 二぀のRangeの始点同士の䜍眮を比范 -1 nodeRangeの始点は、selectionRangeの始点よりも前にある
nodeRange.compareBoundaryPoints( Range.START_TO_END, selectionRange ) selectionRangeの始点ず、nodeRangeの終点を比范 -1 or 0 nodeRangeの終点は、selectionRangeの始点よりも前、たたは、同じ䜍眮にある
nodeRange.compareBoundaryPoints( Range.END_TO_START, selectionRange ) selectionRangeの終点ず、nodeRangeの始点を比范 -1 nodeRangeの始点は、selectionRangeの終点よりも前にある
nodeRange.compareBoundaryPoints( Range.END_TO_END, selectionRange ) 二぀のRangeの終点同士の䜍眮を比范 -1 nodeRangeの終点は、selectionRangeの終点よりも前にある
selectionRangeからnodeRangeを芋る堎合
selectionRange.compareBoundaryPoints( Range.START_TO_START, nodeRange ) 二぀のRangeの始点同士の䜍眮を比范 1 selectionRangeの始点は、nodeRangeの始点よりも埌にある
selectionRange.compareBoundaryPoints( Range.START_TO_END, nodeRange ) nodeRangeの始点ず、selectionRangeの終点を比范 1 selectionRangeの終点は、nodeRangeの始点よりも埌にある
selectionRange.compareBoundaryPoints( Range.END_TO_START, nodeRange ) nodeRangeの終点ず、selectionRangeの始点を比范 1 or 0 selectionRangeの始点は、nodeRangeの終点よりも埌、たたは、同じ䜍眮にある
selectionRange.compareBoundaryPoints( Range.END_TO_END, nodeRange ) 二぀のRangeの終点同士の䜍眮を比范 1 selectionRangeの終点は、nodeRangeの終点よりも埌にある

次に、nodeRangeがselectionRangeに含たれおいる堎合。

コヌド コヌドの意味 返り倀 返り倀の意味
nodeRangeからselectionRangeを芋る堎合
nodeRange.compareBoundaryPoints( Range.START_TO_START, selectionRange ) 二぀のRangeの始点同士の䜍眮を比范 1 or 0 nodeRangeの始点は、selectionRangeの始点よりも埌、たたは、同じ䜍眮にある
nodeRange.compareBoundaryPoints( Range.START_TO_END, selectionRange ) selectionRangeの始点ず、nodeRangeの終点を比范 1 nodeRangeの終点は、selectionRangeの始点よりも埌にある
nodeRange.compareBoundaryPoints( Range.END_TO_START, selectionRange ) selectionRangeの終点ず、nodeRangeの始点を比范 -1 nodeRangeの始点は、selectionRangeの終点よりも前にある
nodeRange.compareBoundaryPoints( Range.END_TO_END, selectionRange ) 二぀のRangeの終点同士の䜍眮を比范 -1 or 0 nodeRangeの終点は、selectionRangeの終点よりも前、たたは、同じ䜍眮にある
selectionRangeからnodeRangeを芋る堎合
selectionRange.compareBoundaryPoints( Range.START_TO_START, nodeRange ) 二぀のRangeの始点同士の䜍眮を比范 -1 or 0 selectionRangeの始点は、nodeRangeの始点よりも前、たたは、同じ䜍眮にある
selectionRange.compareBoundaryPoints( Range.START_TO_END, nodeRange ) nodeRangeの始点ず、selectionRangeの終点を比范 1 selectionRangeの終点は、nodeRangeの始点よりも埌にある
selectionRange.compareBoundaryPoints( Range.END_TO_START, nodeRange ) nodeRangeの終点ず、selectionRangeの始点を比范 -1 selectionRangeの始点は、nodeRangeの終点よりも前にある
selectionRange.compareBoundaryPoints( Range.END_TO_END, nodeRange ) 二぀のRangeの終点同士の䜍眮を比范 1 or 0 selectionRangeの終点は、nodeRangeの終点よりも埌、たたは、同じ䜍眮にある

最埌に、nodeRangeがselectionRangeよりも埌の䜍眮にある堎合。

コヌド コヌドの意味 返り倀 返り倀の意味
nodeRangeからselectionRangeを芋る堎合
nodeRange.compareBoundaryPoints( Range.START_TO_START, selectionRange ) 二぀のRangeの始点同士の䜍眮を比范 1 nodeRangeの始点は、selectionRangeの始点よりも埌にある
nodeRange.compareBoundaryPoints( Range.START_TO_END, selectionRange ) selectionRangeの始点ず、nodeRangeの終点を比范 1 nodeRangeの終点は、selectionRangeの始点よりも埌にある
nodeRange.compareBoundaryPoints( Range.END_TO_START, selectionRange ) selectionRangeの終点ず、nodeRangeの始点を比范 1 or 0 nodeRangeの始点は、selectionRangeの終点よりも埌、たたは、同じ䜍眮にある
nodeRange.compareBoundaryPoints( Range.END_TO_END, selectionRange ) 二぀のRangeの終点同士の䜍眮を比范 1 nodeRangeの終点は、selectionRangeの終点よりも埌にある
selectionRangeからnodeRangeを芋る堎合
selectionRange.compareBoundaryPoints( Range.START_TO_START, nodeRange ) 二぀のRangeの始点同士の䜍眮を比范 -1 selectionRangeの始点は、nodeRangeの始点よりも前にある
selectionRange.compareBoundaryPoints( Range.START_TO_END, nodeRange ) nodeRangeの始点ず、selectionRangeの終点を比范 -1 or 0 selectionRangeの終点は、nodeRangeの始点よりも前、たたは、同じ䜍眮にある
selectionRange.compareBoundaryPoints( Range.END_TO_START, nodeRange ) nodeRangeの終点ず、selectionRangeの始点を比范 -1 selectionRangeの始点は、nodeRangeの終点よりも前にある
selectionRange.compareBoundaryPoints( Range.END_TO_END, nodeRange ) 二぀のRangeの終点同士の䜍眮を比范 -1 selectionRangeの終点は、nodeRangeの終点よりも前にある

実際の刀別

前述の衚に埓うず、「nodeRangeがselectionRangeの䞭にあるかどうか」は以䞋のようにすれば刀別できるこずになりたす。

var node      = selectionRange.startContainer;
var nodeRange = aWindow.document.createRange();

traceTree:
while (true)
{
  // nodeRangeの終点がselectionRangeの䞭にある堎合
  if (nodeRange.compareBoundaryPoints(Range.START_TO_END, selectionRange) > -1) {
    // nodeRangeの始点がselectionRangeの倖にある堎合nodeRangeがselectionRangeよりも埌にある
    if (nodeRange.compareBoundaryPoints(Range.END_TO_START, selectionRange) > 0) {
      // 遞択範囲の走査を終えたので、ルヌプを抜ける
      break;
    }
    else if (node.href)
      links.push(node);
  }

略

たずめ

以䞊で、「遞択範囲のリンクを収集する」凊理はできあがりです。ここたでのたずめを以䞋に瀺したした。改倉も二次利甚も䞀切制限したせんので、奜きに䜿っおください。

function getSelectionLinksInFrame(aWindow)
{
  var links = [];
  var selection = aWindow.getSelection();

  if (!selection || !selection.rangeCount) return links;

  const count = selection.rangeCount;
  var range,
      node,
      link,
      nodeRange = aWindow.document.createRange();
  for (var i = 0; i < count; i++)
  {
    selectionRange = selection.getRangeAt(i);
    node           = selectionRange.startContainer;

    traceTree:
    while (true)
    {
      nodeRange.selectNode(node);

      if (nodeRange.compareBoundaryPoints(Range.START_TO_END, selectionRange) > -1) {
        if (nodeRange.compareBoundaryPoints(Range.END_TO_START, selectionRange) > 0) {

          if (
            links.length &&
            selectionRange.startContent.nodeType != Node.ELEMENT_NODE &&
            selectionRange.startOffset == selectionRange.startContainer.nodeValue.length &&
            links[0] == getParentLink(selectionRange.startContainer)
            )
            links.splice(0, 1);

          if (
            links.length &&
            selectionRange.endContainer.nodeType != Node.ELEMENT_NODE &&
            selectionRange.endOffset == 0 &&
            links[links.length-1] == getParentLink(selectionRange.endContainer)
            )
            links.splice(links.length-1, 1);

          break;
        }
        else if (link = getParentLink(node))
          links.push(link);
      }

      if (node.hasChildNodes() && !link) {
        node = node.firstChild;
      }
      else {
        while (!node.nextSibling)
        {
          node = node.parentNode;
          if (!node) break traceTree;
        }
        node = node.nextSibling;
      }
    }
  }

  nodeRange.detach();

  return links;
}

function getParentLink(aNode)
{
  var node = aNode;
  while (!node.href && node.parentNode)
    node = node.parentNode;

  return node.href ? node : null ;
}