matth said:
Thank you both for your help.
I'm going to include the full bookmarklet code I'm using.
A specific instance that fails is when I select the links and image
typically present at the top of drudgereport.com (the stuff right
above Drudge's header graphic).
I'll try implementing your code Evertjan, and report back.
[...]
Here's my latest take on the question (another bookmarklet), which you
may find useful. As far as I'm aware, it lists all the nodes beneath a
range's commonAncestorContainer, but the bug is that (so far) it doesn't
indicate whether it's arriving at a node or leaving it. Still, it does
show a couple of things missing in your code:
- child offsets of the startContainer and endContainer when the parent
is an element object or another node having children.
- a slightly different approach to ascending node ancestors in
search of nextSibling (your version quits if there is no
parentNode.nextSibling).
- escaped debug output! This may explain the funny things at Drudge
Report (a couple of <noscript>'s in there).
See what you think...
javascript:
function RangeIterator() {
this.onNode = function (str, node) {}; /* default function: supply
your own */
this.iterate = function (r) {
var me = this, node = r.startContainer, offset = r.startOffset,
finalNode = r.endContainer, finalOffset = r.endOffset;
function visitNode(node, offset) {
var isFinal = (node == finalNode), lastChildIndex, i, c,
text = '';
switch (node.nodeType) {
case 3: /* text, CDATA, processing instruction, comment */
case 4:
case 7:
case 8:
text = node.nodeValue;
if (isFinal) {
text = text.substring(0, finalOffset);
}
if (offset) {
text = text.substring(offset);
}
me.onNode(text, node);
break;
default:
me.onNode(text, node);
lastChildIndex = isFinal ? finalOffset :
node.childNodes.length;
for (i = offset, c = node.childNodes.item(i);
i < lastChildIndex;
c = c.nextSibling, i++) {
if (!visitNode(c, 0)) {
return false;
}
}
}
return !isFinal;
}
/* end function */
while (visitNode(node, offset)) {
if (!node.nextSibling) {
node = node.parentNode;
/* doesn't indicate when it's leaving an incompletely
selected node */
offset = node.childNodes.length;
} else {
node = node.nextSibling;
offset = 0;
}
}
return true;
};
}
/* for testing: */
({
iterator: new RangeIterator(),
visited: [],
toHTML: function (a) {
return ['<', '>', '&']['<>&'.indexOf(a)];},
go: function () {
var timer, w = window, s = w.getSelection(), i, wd, me = this;
this.iterator.onNode = function (str, node) {
me.visited.push(node.nodeName + ': ' + str);};
timer = +new Date();
if (s.rangeCount && this.iterator.iterate(s.getRangeAt(0))) {
w.alert(this.visited.length + ' node(s) visited in ' +
(+new Date() - timer) + ' ms.');
for (i = this.visited.length; i--; ) {
this.visited
= this.visited.replace(/[<>&]/g,
this.toHTML);
}
w = window.open();
wd = w.document;
wd.write(this.visited.join('<br>'));
wd.close();
} else {
w.alert("No selection");
}
}
}).go();