You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ge...@apache.org on 2020/10/13 20:59:35 UTC

[incubator-annotator] branch import-dom-seek created (now 526a6ee)

This is an automated email from the ASF dual-hosted git repository.

gerben pushed a change to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git.


      at 526a6ee  Skip empty nodes again when seeking

This branch includes the following new commits:

     new d604bf6  Move dom-seek dependency into this repo
     new 1264ba5  Fix type errors
     new 302ef07  Add semicolons etc
     new c442273  Turn seek into Seeker class
     new 526a6ee  Skip empty nodes again when seeking

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-annotator] 01/05: Move dom-seek dependency into this repo

Posted by ge...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit d604bf6ea38a8a164e5332de7d0b6f3a1c516549
Author: Gerben <ge...@treora.com>
AuthorDate: Fri Oct 9 14:28:19 2020 +0200

    Move dom-seek dependency into this repo
    
    Plain copy-paste; the only modification is adding its TypeScript signature.
---
 packages/dom/package.json               |  3 +-
 packages/dom/src/seek.ts                | 89 +++++++++++++++++++++++++++++++++
 packages/dom/src/text-position/match.ts |  2 +-
 packages/dom/src/text-quote/describe.ts |  2 +-
 packages/dom/src/text-quote/match.ts    |  2 +-
 packages/dom/src/types/dom-seek.d.ts    | 26 ----------
 6 files changed, 93 insertions(+), 31 deletions(-)

diff --git a/packages/dom/package.json b/packages/dom/package.json
index b623c60..5f4add9 100644
--- a/packages/dom/package.json
+++ b/packages/dom/package.json
@@ -18,8 +18,7 @@
   "module": "./lib/index.mjs",
   "types": "./lib/index.d.ts",
   "dependencies": {
-    "@babel/runtime-corejs3": "^7.8.7",
-    "dom-seek": "^5.1.0"
+    "@babel/runtime-corejs3": "^7.8.7"
   },
   "devDependencies": {
     "@annotator/selector": "^0.1.0"
diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
new file mode 100644
index 0000000..dc547da
--- /dev/null
+++ b/packages/dom/src/seek.ts
@@ -0,0 +1,89 @@
+const E_END = 'Iterator exhausted before seek ended.'
+const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
+const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.'
+
+const DOCUMENT_POSITION_PRECEDING = 2
+const SHOW_TEXT = 4
+const TEXT_NODE = 3
+
+
+export default function seek(iter: NodeIterator, where: number | Text): number {
+  if (iter.whatToShow !== SHOW_TEXT) {
+    let error
+
+    // istanbul ignore next
+    try {
+      error = new DOMException(E_SHOW, 'InvalidStateError')
+    } catch {
+      error = new Error(E_SHOW);
+      error.code = 11
+      error.name = 'InvalidStateError'
+      error.toString = () => `InvalidStateError: ${E_SHOW}`
+    }
+
+    throw error
+  }
+
+  let count = 0
+  let node = iter.referenceNode
+  let predicates = null
+
+  if (isInteger(where)) {
+    predicates = {
+      forward: () => count < where,
+      backward: () => count > where || !iter.pointerBeforeReferenceNode,
+    }
+  } else if (isText(where)) {
+    let forward = before(node, where) ? () => false : () => node !== where
+    let backward = () => node !== where || !iter.pointerBeforeReferenceNode
+    predicates = {forward, backward}
+  } else {
+    throw new TypeError(E_WHERE)
+  }
+
+  while (predicates.forward()) {
+    node = iter.nextNode()
+
+    if (node === null) {
+      throw new RangeError(E_END)
+    }
+
+    count += node.nodeValue.length
+  }
+
+  if (iter.nextNode()) {
+    node = iter.previousNode()
+  }
+
+  while (predicates.backward()) {
+    node = iter.previousNode()
+
+    if (node === null) {
+      throw new RangeError(E_END)
+    }
+
+    count -= node.nodeValue.length
+  }
+
+  if (!isText(iter.referenceNode)) {
+    throw new RangeError(E_END);
+  }
+
+  return count
+}
+
+
+function isInteger(n) {
+  if (typeof n !== 'number') return false;
+  return isFinite(n) && Math.floor(n) === n;
+}
+
+
+function isText(node) {
+  return node.nodeType === TEXT_NODE
+}
+
+
+function before(ref, node) {
+  return ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING
+}
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
index a579e94..00acd4c 100644
--- a/packages/dom/src/text-position/match.ts
+++ b/packages/dom/src/text-position/match.ts
@@ -18,9 +18,9 @@
  * under the License.
  */
 
-import seek from 'dom-seek';
 import type { Matcher, TextPositionSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
+import seek from '../seek';
 
 export function createTextPositionSelectorMatcher(
   selector: TextPositionSelector,
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 4e0e976..514208b 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -18,9 +18,9 @@
  * under the License.
  */
 
-import seek from 'dom-seek';
 import type { TextQuoteSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
+import seek from '../seek';
 
 export async function describeTextQuote(
   range: Range,
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index d076f76..c6b769b 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -18,9 +18,9 @@
  * under the License.
  */
 
-import seek from 'dom-seek';
 import type { Matcher, TextQuoteSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
+import seek from '../seek';
 
 export function createTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
diff --git a/packages/dom/src/types/dom-seek.d.ts b/packages/dom/src/types/dom-seek.d.ts
deleted file mode 100644
index bb379b3..0000000
--- a/packages/dom/src/types/dom-seek.d.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @license
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-declare module 'dom-seek' {
-  export default function seek(
-    iter: NodeIterator,
-    where: number | Text,
-  ): number;
-}


[incubator-annotator] 02/05: Fix type errors

Posted by ge...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit 1264ba5cfaaa696147fc161504585602aeda06e0
Author: Gerben <ge...@treora.com>
AuthorDate: Fri Oct 9 21:11:51 2020 +0200

    Fix type errors
---
 packages/dom/src/seek.ts | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
index dc547da..171f3a1 100644
--- a/packages/dom/src/seek.ts
+++ b/packages/dom/src/seek.ts
@@ -9,7 +9,7 @@ const TEXT_NODE = 3
 
 export default function seek(iter: NodeIterator, where: number | Text): number {
   if (iter.whatToShow !== SHOW_TEXT) {
-    let error
+    let error: Error & { code?: number }
 
     // istanbul ignore next
     try {
@@ -25,7 +25,7 @@ export default function seek(iter: NodeIterator, where: number | Text): number {
   }
 
   let count = 0
-  let node = iter.referenceNode
+  let node: Node | null = iter.referenceNode
   let predicates = null
 
   if (isInteger(where)) {
@@ -48,7 +48,7 @@ export default function seek(iter: NodeIterator, where: number | Text): number {
       throw new RangeError(E_END)
     }
 
-    count += node.nodeValue.length
+    count += (node as Text).data.length
   }
 
   if (iter.nextNode()) {
@@ -62,7 +62,7 @@ export default function seek(iter: NodeIterator, where: number | Text): number {
       throw new RangeError(E_END)
     }
 
-    count -= node.nodeValue.length
+    count -= (node as Text).data.length
   }
 
   if (!isText(iter.referenceNode)) {
@@ -73,17 +73,17 @@ export default function seek(iter: NodeIterator, where: number | Text): number {
 }
 
 
-function isInteger(n) {
+function isInteger(n: any): n is number {
   if (typeof n !== 'number') return false;
   return isFinite(n) && Math.floor(n) === n;
 }
 
 
-function isText(node) {
+function isText(node: Node): node is Text {
   return node.nodeType === TEXT_NODE
 }
 
 
-function before(ref, node) {
-  return ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING
+function before(ref: Node, node: Node): boolean {
+  return !!(ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING)
 }


[incubator-annotator] 03/05: Add semicolons etc

Posted by ge...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit 302ef071d1ba19803ddec2f336358ba1d4473c00
Author: Gerben <ge...@treora.com>
AuthorDate: Fri Oct 9 21:18:46 2020 +0200

    Add semicolons etc
---
 packages/dom/src/seek.ts | 65 +++++++++++++++++++++++-------------------------
 1 file changed, 31 insertions(+), 34 deletions(-)

diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
index 171f3a1..3a707ed 100644
--- a/packages/dom/src/seek.ts
+++ b/packages/dom/src/seek.ts
@@ -1,89 +1,86 @@
-const E_END = 'Iterator exhausted before seek ended.'
-const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
-const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.'
-
-const DOCUMENT_POSITION_PRECEDING = 2
-const SHOW_TEXT = 4
-const TEXT_NODE = 3
+const E_END = 'Iterator exhausted before seek ended.';
+const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.';
+const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.';
 
+const DOCUMENT_POSITION_PRECEDING = 2;
+const SHOW_TEXT = 4;
+const TEXT_NODE = 3;
 
 export default function seek(iter: NodeIterator, where: number | Text): number {
   if (iter.whatToShow !== SHOW_TEXT) {
-    let error: Error & { code?: number }
+    let error: Error & { code?: number };
 
     // istanbul ignore next
     try {
-      error = new DOMException(E_SHOW, 'InvalidStateError')
+      error = new DOMException(E_SHOW, 'InvalidStateError');
     } catch {
       error = new Error(E_SHOW);
-      error.code = 11
-      error.name = 'InvalidStateError'
-      error.toString = () => `InvalidStateError: ${E_SHOW}`
+      error.code = 11;
+      error.name = 'InvalidStateError';
+      error.toString = () => `InvalidStateError: ${E_SHOW}`;
     }
 
-    throw error
+    throw error;
   }
 
-  let count = 0
-  let node: Node | null = iter.referenceNode
-  let predicates = null
+  let count = 0;
+  let node: Node | null = iter.referenceNode;
+  let predicates = null;
 
   if (isInteger(where)) {
     predicates = {
       forward: () => count < where,
       backward: () => count > where || !iter.pointerBeforeReferenceNode,
-    }
+    };
   } else if (isText(where)) {
-    let forward = before(node, where) ? () => false : () => node !== where
-    let backward = () => node !== where || !iter.pointerBeforeReferenceNode
-    predicates = {forward, backward}
+    predicates = {
+      forward: before(node, where) ? () => false : () => node !== where,
+      backward: () => node !== where || !iter.pointerBeforeReferenceNode,
+    };
   } else {
-    throw new TypeError(E_WHERE)
+    throw new TypeError(E_WHERE);
   }
 
   while (predicates.forward()) {
-    node = iter.nextNode()
+    node = iter.nextNode();
 
     if (node === null) {
-      throw new RangeError(E_END)
+      throw new RangeError(E_END);
     }
 
-    count += (node as Text).data.length
+    count += (node as Text).data.length;
   }
 
   if (iter.nextNode()) {
-    node = iter.previousNode()
+    node = iter.previousNode();
   }
 
   while (predicates.backward()) {
-    node = iter.previousNode()
+    node = iter.previousNode();
 
     if (node === null) {
-      throw new RangeError(E_END)
+      throw new RangeError(E_END);
     }
 
-    count -= (node as Text).data.length
+    count -= (node as Text).data.length;
   }
 
   if (!isText(iter.referenceNode)) {
     throw new RangeError(E_END);
   }
 
-  return count
+  return count;
 }
 
-
 function isInteger(n: any): n is number {
   if (typeof n !== 'number') return false;
   return isFinite(n) && Math.floor(n) === n;
 }
 
-
 function isText(node: Node): node is Text {
-  return node.nodeType === TEXT_NODE
+  return node.nodeType === TEXT_NODE;
 }
 
-
 function before(ref: Node, node: Node): boolean {
-  return !!(ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING)
+  return !!(ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING);
 }


[incubator-annotator] 05/05: Skip empty nodes again when seeking

Posted by ge...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit 526a6eeac17e602820f6dedcfb06723bd3acc21a
Author: Gerben <ge...@treora.com>
AuthorDate: Tue Oct 13 22:46:12 2020 +0200

    Skip empty nodes again when seeking
---
 packages/dom/src/seek.ts | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
index c8f77ad..1344dc8 100644
--- a/packages/dom/src/seek.ts
+++ b/packages/dom/src/seek.ts
@@ -56,7 +56,15 @@ export class Seeker {
       count += (node as Text).data.length;
     }
 
+    // If there are subsequent nodes, move to ‘before’ the next non-empty
+    // node (or the last node, in case all subsequent nodes are empty).
+    // As this moves from ‘after’ the current node, count is not changed.
     if (iter.nextNode()) {
+      node = iter.referenceNode;
+      while (node !== null && (node as Text).data.length === 0) { // node should always be Text now due to the NodeFilter.
+        node = iter.nextNode();
+      }
+      // Note this direction switch stays within the same node.
       node = iter.previousNode();
     }
 


[incubator-annotator] 04/05: Turn seek into Seeker class

Posted by ge...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit c442273f6eb527ae8fcd84ac8b9eaccabfcbff7a
Author: Gerben <ge...@treora.com>
AuthorDate: Fri Oct 9 22:07:56 2020 +0200

    Turn seek into Seeker class
    
    So it can just contain its NodeIterator.
---
 packages/dom/src/seek.ts                | 124 +++++++++++++++++---------------
 packages/dom/src/text-position/match.ts |  23 ++----
 packages/dom/src/text-quote/describe.ts |  19 ++---
 packages/dom/src/text-quote/match.ts    |  23 ++----
 4 files changed, 81 insertions(+), 108 deletions(-)

diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
index 3a707ed..c8f77ad 100644
--- a/packages/dom/src/seek.ts
+++ b/packages/dom/src/seek.ts
@@ -1,75 +1,81 @@
-const E_END = 'Iterator exhausted before seek ended.';
-const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.';
-const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.';
-
-const DOCUMENT_POSITION_PRECEDING = 2;
-const SHOW_TEXT = 4;
-const TEXT_NODE = 3;
-
-export default function seek(iter: NodeIterator, where: number | Text): number {
-  if (iter.whatToShow !== SHOW_TEXT) {
-    let error: Error & { code?: number };
-
-    // istanbul ignore next
-    try {
-      error = new DOMException(E_SHOW, 'InvalidStateError');
-    } catch {
-      error = new Error(E_SHOW);
-      error.code = 11;
-      error.name = 'InvalidStateError';
-      error.toString = () => `InvalidStateError: ${E_SHOW}`;
-    }
+import { ownerDocument } from "./owner-document";
 
-    throw error;
+const E_END = 'Iterator exhausted before seek ended.';
+const E_WHERE = 'Argument of seek must be an integer or a Text Node.';
+
+export class Seeker {
+  iter: NodeIterator;
+
+  constructor(scope: Range) {
+    const document = ownerDocument(scope);
+    this.iter = document.createNodeIterator(
+      scope.commonAncestorContainer,
+      NodeFilter.SHOW_TEXT,
+      {
+        acceptNode(node: Text) {
+          return scope.intersectsNode(node)
+            ? NodeFilter.FILTER_ACCEPT
+            : NodeFilter.FILTER_REJECT;
+        },
+      },
+    );
   }
 
-  let count = 0;
-  let node: Node | null = iter.referenceNode;
-  let predicates = null;
-
-  if (isInteger(where)) {
-    predicates = {
-      forward: () => count < where,
-      backward: () => count > where || !iter.pointerBeforeReferenceNode,
-    };
-  } else if (isText(where)) {
-    predicates = {
-      forward: before(node, where) ? () => false : () => node !== where,
-      backward: () => node !== where || !iter.pointerBeforeReferenceNode,
-    };
-  } else {
-    throw new TypeError(E_WHERE);
+  getCurrentNode() {
+    return this.iter.referenceNode;
   }
 
-  while (predicates.forward()) {
-    node = iter.nextNode();
+  seek(where: number | Text): number {
+    const iter = this.iter;
+
+    let count = 0;
+    let node: Node | null = iter.referenceNode;
+    let predicates = null;
+
+    if (isInteger(where)) {
+      predicates = {
+        forward: () => count < where,
+        backward: () => count > where || !iter.pointerBeforeReferenceNode,
+      };
+    } else if (isText(where)) {
+      predicates = {
+        forward: before(node, where) ? () => false : () => node !== where,
+        backward: () => node !== where || !iter.pointerBeforeReferenceNode,
+      };
+    } else {
+      throw new TypeError(E_WHERE);
+    }
+
+    while (predicates.forward()) {
+      node = iter.nextNode();
 
-    if (node === null) {
-      throw new RangeError(E_END);
+      if (node === null) {
+        throw new RangeError(E_END);
+      }
+
+      count += (node as Text).data.length;
     }
 
-    count += (node as Text).data.length;
-  }
+    if (iter.nextNode()) {
+      node = iter.previousNode();
+    }
 
-  if (iter.nextNode()) {
-    node = iter.previousNode();
-  }
+    while (predicates.backward()) {
+      node = iter.previousNode();
 
-  while (predicates.backward()) {
-    node = iter.previousNode();
+      if (node === null) {
+        throw new RangeError(E_END);
+      }
 
-    if (node === null) {
-      throw new RangeError(E_END);
+      count -= (node as Text).data.length;
     }
 
-    count -= (node as Text).data.length;
-  }
+    if (!isText(iter.referenceNode)) {
+      throw new RangeError(E_END);
+    }
 
-  if (!isText(iter.referenceNode)) {
-    throw new RangeError(E_END);
+    return count;
   }
-
-  return count;
 }
 
 function isInteger(n: any): n is number {
@@ -78,9 +84,9 @@ function isInteger(n: any): n is number {
 }
 
 function isText(node: Node): node is Text {
-  return node.nodeType === TEXT_NODE;
+  return node.nodeType === Node.TEXT_NODE;
 }
 
 function before(ref: Node, node: Node): boolean {
-  return !!(ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING);
+  return !!(ref.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_PRECEDING);
 }
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
index 00acd4c..7a57226 100644
--- a/packages/dom/src/text-position/match.ts
+++ b/packages/dom/src/text-position/match.ts
@@ -20,7 +20,7 @@
 
 import type { Matcher, TextPositionSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
-import seek from '../seek';
+import { Seeker } from '../seek';
 
 export function createTextPositionSelectorMatcher(
   selector: TextPositionSelector,
@@ -31,18 +31,7 @@ export function createTextPositionSelectorMatcher(
 
     const { start, end } = selector;
 
-    const iter = document.createNodeIterator(
-      scope.commonAncestorContainer,
-      NodeFilter.SHOW_TEXT,
-      {
-        acceptNode(node: Text) {
-          // Only reveal nodes within the range; and skip any empty text nodes.
-          return scope.intersectsNode(node) && node.length > 0
-            ? NodeFilter.FILTER_ACCEPT
-            : NodeFilter.FILTER_REJECT;
-        },
-      },
-    );
+    const seeker = new Seeker(scope);
 
     // The index of the first character of iter.referenceNode inside the text.
     let referenceNodeIndex = isTextNode(scope.startContainer)
@@ -57,12 +46,12 @@ export function createTextPositionSelectorMatcher(
     const match = document.createRange();
 
     // Seek to the start of the match, make the range start there.
-    referenceNodeIndex += seek(iter, matchStartIndex - referenceNodeIndex);
-    match.setStart(iter.referenceNode, matchStartIndex - referenceNodeIndex);
+    referenceNodeIndex += seeker.seek(matchStartIndex - referenceNodeIndex);
+    match.setStart(seeker.getCurrentNode(), matchStartIndex - referenceNodeIndex);
 
     // Seek to the end of the match, make the range end there.
-    referenceNodeIndex += seek(iter, matchEndIndex - referenceNodeIndex);
-    match.setEnd(iter.referenceNode, matchEndIndex - referenceNodeIndex);
+    referenceNodeIndex += seeker.seek(matchEndIndex - referenceNodeIndex);
+    match.setEnd(seeker.getCurrentNode(), matchEndIndex - referenceNodeIndex);
 
     // Yield the match.
     yield match;
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 514208b..e5846bc 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -20,7 +20,7 @@
 
 import type { TextQuoteSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
-import seek from '../seek';
+import { Seeker } from '../seek';
 
 export async function describeTextQuote(
   range: Range,
@@ -120,22 +120,11 @@ function calculateContextForDisambiguation(
 
 // Get the index of the first character of range within the text of scope.
 function getRangeTextPosition(range: Range, scope: Range): number {
-  const iter = ownerDocument(scope).createNodeIterator(
-    scope.commonAncestorContainer,
-    NodeFilter.SHOW_TEXT,
-    {
-      acceptNode(node: Text) {
-        // Only reveal nodes within the range
-        return scope.intersectsNode(node)
-          ? NodeFilter.FILTER_ACCEPT
-          : NodeFilter.FILTER_REJECT;
-      },
-    },
-  );
+  const seeker = new Seeker(scope);
   const scopeOffset = isTextNode(scope.startContainer) ? scope.startOffset : 0;
   if (isTextNode(range.startContainer))
-    return seek(iter, range.startContainer) + range.startOffset - scopeOffset;
-  else return seek(iter, firstTextNodeInRange(range)) - scopeOffset;
+    return seeker.seek(range.startContainer) + range.startOffset - scopeOffset;
+  else return seeker.seek(firstTextNodeInRange(range)) - scopeOffset;
 }
 
 function firstTextNodeInRange(range: Range): Text {
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index c6b769b..6282769 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -20,7 +20,7 @@
 
 import type { Matcher, TextQuoteSelector } from '@annotator/selector';
 import { ownerDocument } from '../owner-document';
-import seek from '../seek';
+import { Seeker } from '../seek';
 
 export function createTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
@@ -34,18 +34,7 @@ export function createTextQuoteSelectorMatcher(
     const suffix = selector.suffix || '';
     const searchPattern = prefix + exact + suffix;
 
-    const iter = document.createNodeIterator(
-      scope.commonAncestorContainer,
-      NodeFilter.SHOW_TEXT,
-      {
-        acceptNode(node: Text) {
-          // Only reveal nodes within the range; and skip any empty text nodes.
-          return scope.intersectsNode(node) && node.length > 0
-            ? NodeFilter.FILTER_ACCEPT
-            : NodeFilter.FILTER_REJECT;
-        },
-      },
-    );
+    const seeker = new Seeker(scope);
 
     // The index of the first character of iter.referenceNode inside the text.
     let referenceNodeIndex = isTextNode(scope.startContainer)
@@ -66,12 +55,12 @@ export function createTextQuoteSelectorMatcher(
       const match = document.createRange();
 
       // Seek to the start of the match, make the range start there.
-      referenceNodeIndex += seek(iter, matchStartIndex - referenceNodeIndex);
-      match.setStart(iter.referenceNode, matchStartIndex - referenceNodeIndex);
+      referenceNodeIndex += seeker.seek(matchStartIndex - referenceNodeIndex);
+      match.setStart(seeker.getCurrentNode(), matchStartIndex - referenceNodeIndex);
 
       // Seek to the end of the match, make the range end there.
-      referenceNodeIndex += seek(iter, matchEndIndex - referenceNodeIndex);
-      match.setEnd(iter.referenceNode, matchEndIndex - referenceNodeIndex);
+      referenceNodeIndex += seeker.seek(matchEndIndex - referenceNodeIndex);
+      match.setEnd(seeker.getCurrentNode(), matchEndIndex - referenceNodeIndex);
 
       // Yield the match.
       yield match;