Web ページの選択範囲に含まれるリンクを取得する方法として、Piro さんによる DOM 2 Range の compareBoundaryPoints メソッドを使ったやり方があります。これはリンクを探すのに DOM Core の機能を使って文書ツリーをたどっていますが、今現在ノードを探すといわれて真っ先に思いつくのは XPath でしょう。そこで、XPath を使って選択範囲のリンクを取得する方法を考えてみました。もちろん、選択範囲を扱う以上 DOM 2 Range も利用します。
基本的なアイデアは、選択範囲の終点より前にあるリンクで、選択範囲の始点より前にはないものが求めるリンクというものです。たとえば次に示した状態で、link1 と link2 はいずれも選択範囲終点より前にありますが、link1 は選択範囲始点よりも前にあるので求める結果には入りません。
<p>
<a id="link1" href="...">link1</a>
【選択範囲始点】<a id="link2" href="...">link2</a>【選択範囲終点】
</p>
また、リンク同士は入れ子にならない (a 要素を入れ子にしてはいけない) というのも重要です。もしもこれが入れ子を許す場合はどうなるでしょうか。たとえば、選択範囲に含まれる (選択範囲を一部でも含む) div 要素の取得を考えてみます。
<div id="div1">
<div id="div2">...</div>
【選択範囲始点】<div id="div3">...</div>【選択範囲終点】
</div>
上の状態で、求める div 要素は div1 と div3 となり、文書順で見た場合要素が飛び飛びになっています。入れ子にならないのならこのようなことは起こらず、求める結果は文書順に並んだ対象要素の列中で連続した集合となります。つまり、選択範囲始点より前にあるリンクを具体的に求める必要はなく、その総数がわかればいいのです。
では実際にソースコードを書いていきましょう。ここでは選択範囲に含まれるリンクを配列に収めて返すことにします。まずは選択範囲に対応する Range オブジェクトを取得します。
var links = [];
var selection = getSelection();
if (!selection.rangeCount) return links;
var range = selection.getRangeAt(0);
if (range.collapsed) return links;
links が結果となる配列です。選択範囲が存在しなかったり、選択範囲の幅が 0 だったりした場合には空の配列を返します。
var startNode = range.startContainer;
var startExpr = 'count(preceding::*[@href])';
if (startNode.nodeType == Node.ELEMENT_NODE) {
if (range.startOffset < startNode.childNodes.length)
startNode = startNode.childNodes[range.startOffset];
else
startExpr += ' + count(descendant::*[@href])';
}
var startResult = document.evaluate(startExpr, startNode, null,
XPathResult.NUMBER_TYPE, null);
セコメントをする