Make all functions also accept a Node as scope
diff --git a/packages/dom/src/range/match.ts b/packages/dom/src/range/match.ts
index 061eb75..98cf250 100644
--- a/packages/dom/src/range/match.ts
+++ b/packages/dom/src/range/match.ts
@@ -24,11 +24,11 @@
Selector,
} from '@apache-annotator/selector';
import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
import { cartesian } from './cartesian';
/**
- * Find the range(s) corresponding to the given {@link
- * RangeSelector}.
+ * Find the range(s) corresponding to the given {@link RangeSelector}.
*
* As a RangeSelector itself nests two further selectors, one needs to pass a
* `createMatcher` function that will be used to process those nested selectors.
@@ -36,13 +36,12 @@
* The function is curried, taking first the `createMatcher` function, then the
* selector, and then the scope.
*
- * As there may be multiple matches for a given selector, the matcher will
- * return an (async) generator that produces each match in the order they are
- * found in the text. If both its nested selectors produce multiple matches, the
- * RangeSelector matches each possible pair among those in which the order of
- * start and end are respected. *(Note this behaviour is a rather free
- * interpretation — the Web Annotation Data Model spec is silent about multiple
- * matches for RangeSelectors)*
+ * As there may be multiple matches for the start & end selectors, the resulting
+ * matcher will return an (async) iterable, that produces a match for each
+ * possible pair of matches of the nested selectors (except those where its end
+ * would precede its start). *(Note that this behaviour is a rather free
+ * interpretation of the Web Annotation Data Model spec, which is silent about
+ * the possibility of multiple matches for RangeSelectors)*
*
* @example
* By using a matcher for {@link TextQuoteSelector}s, one
@@ -88,15 +87,15 @@
* ```
*
* @param createMatcher - The function used to process nested selectors.
- * @returns A function that, given a RangeSelector, creates a {@link
- * Matcher} function that applies it to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @returns A function that, given a RangeSelector `selector`, creates a {@link
+ * Matcher} function that can apply it to a given `scope`.
*
* @public
*/
export function makeCreateRangeSelectorMatcher(
- createMatcher: <T extends Selector>(selector: T) => Matcher<Range, Range>,
-): (selector: RangeSelector) => Matcher<Range, Range> {
+ createMatcher: <T extends Selector, TMatch extends Node | Range>(selector: T)
+ => Matcher<Node | Range, TMatch>,
+): (selector: RangeSelector) => Matcher<Node | Range, Range> {
return function createRangeSelectorMatcher(selector) {
const startMatcher = createMatcher(selector.startSelector);
const endMatcher = createMatcher(selector.endSelector);
@@ -107,10 +106,14 @@
const pairs = cartesian(startMatches, endMatches);
- for await (const [start, end] of pairs) {
- const result = ownerDocument(scope).createRange();
+ for await (let [start, end] of pairs) {
+ start = toRange(start);
+ end = toRange(end);
+ const result = ownerDocument(scope).createRange();
result.setStart(start.startContainer, start.startOffset);
+ // Note that a RangeSelector’s match *excludes* the endSelector’s match,
+ // hence we take the end’s startContainer & startOffset.
result.setEnd(end.startContainer, end.startOffset);
if (!result.collapsed) yield result;
diff --git a/packages/dom/src/text-node-chunker.ts b/packages/dom/src/text-node-chunker.ts
index dc916a6..6981d1c 100644
--- a/packages/dom/src/text-node-chunker.ts
+++ b/packages/dom/src/text-node-chunker.ts
@@ -21,6 +21,7 @@
import type { Chunk, Chunker, ChunkRange } from '@apache-annotator/selector';
import { normalizeRange } from './normalize-range';
import { ownerDocument } from './owner-document';
+import { toRange } from './range-node-conversion';
export interface PartialTextNode extends Chunk<string> {
readonly node: Text;
@@ -44,6 +45,7 @@
}
export class TextNodeChunker implements Chunker<PartialTextNode> {
+ private scope: Range;
private iter: NodeIterator;
get currentChunk(): PartialTextNode {
@@ -116,13 +118,14 @@
/**
* @param scope A Range that overlaps with at least one text node.
*/
- constructor(private scope: Range) {
+ constructor(scope: Node | Range) {
+ this.scope = toRange(scope);
this.iter = ownerDocument(scope).createNodeIterator(
- scope.commonAncestorContainer,
+ this.scope.commonAncestorContainer,
NodeFilter.SHOW_TEXT,
{
- acceptNode(node: Text) {
- return scope.intersectsNode(node)
+ acceptNode: (node: Text) => {
+ return this.scope.intersectsNode(node)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT;
},
diff --git a/packages/dom/src/text-position/describe.ts b/packages/dom/src/text-position/describe.ts
index bd2f42b..f793129 100644
--- a/packages/dom/src/text-position/describe.ts
+++ b/packages/dom/src/text-position/describe.ts
@@ -21,6 +21,7 @@
import type { TextPositionSelector } from '@apache-annotator/selector';
import { describeTextPosition as abstractDescribeTextPosition } from '@apache-annotator/selector';
import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
import { TextNodeChunker } from '../text-node-chunker';
/**
@@ -45,32 +46,24 @@
* // }
* ```
*
- * @param range - The range of characters that the selector should describe
- * @param maybeScope - A {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range} that serves as the ‘document’ for purposes of finding occurrences
- * and determining prefix and suffix. Defaults to span the full Document
- * containing the range.
+ * @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
+ * | Range} whose text content will be described.
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to span the
+ * full Document that contains the range.
* @returns The selector describing `range` within `scope`.
*
* @public
*/
export async function describeTextPosition(
range: Range,
- maybeScope?: Range,
+ scope?: Node | Range,
): Promise<TextPositionSelector> {
- // Default to search in the whole document.
- let scope: Range;
- if (maybeScope !== undefined) {
- scope = maybeScope;
- } else {
- const document = ownerDocument(range);
- scope = document.createRange();
- scope.selectNodeContents(document);
- }
+ scope = toRange(scope ?? ownerDocument(range))
const textChunks = new TextNodeChunker(scope);
if (textChunks.currentChunk === null)
- throw new RangeError('Range does not contain any Text nodes.');
+ throw new RangeError('Scope does not contain any Text nodes.');
return await abstractDescribeTextPosition(
textChunks.rangeToChunkRange(range),
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
index 089def8..ac26caf 100644
--- a/packages/dom/src/text-position/match.ts
+++ b/packages/dom/src/text-position/match.ts
@@ -39,29 +39,22 @@
* @example
* ```
* const selector = { type: 'TextPositionSelector', start: 702, end: 736 };
- *
- * // Search in the whole document.
- * const scope = document.createRange();
- * scope.selectNodeContents(document);
- *
+ * const scope = document.body;
* const matches = textQuoteSelectorMatcher(selector)(scope);
* const match = (await matches.next()).value;
- *
* // ⇒ Range { startContainer: #text, startOffset: 64, endContainer: #text,
* // endOffset: 98, … }
* ```
*
- * @param selector - The {@link TextPositionSelector}
- * to be anchored
- * @returns A {@link Matcher} function that applies
- * `selector` to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @param selector - The {@link TextPositionSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
*
* @public
*/
export function createTextPositionSelectorMatcher(
selector: TextPositionSelector,
-): Matcher<Range, Range> {
+): Matcher<Node | Range, Range> {
const abstractMatcher = abstractTextPositionSelectorMatcher(selector);
return async function* matchAll(scope) {
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 8eca8bf..d1fa6e3 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -24,6 +24,7 @@
} from '@apache-annotator/selector';
import { describeTextQuote as abstractDescribeTextQuote } from '@apache-annotator/selector';
import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
import { TextNodeChunker } from '../text-node-chunker';
/**
@@ -52,10 +53,9 @@
*
* @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
* | Range} whose text content will be described
- * @param maybeScope - A {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range} that serves as the ‘document’ for purposes of finding occurrences
- * and determining prefix and suffix. Defaults to span the full Document
- * containing the range.
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to span the
+ * full Document that contains the range.
* @param options - Options to fine-tune the function’s behaviour.
* @returns The selector unambiguously describing `range` within `scope`.
*
@@ -63,24 +63,16 @@
*/
export async function describeTextQuote(
range: Range,
- maybeScope?: Range,
+ scope?: Node | Range,
options: DescribeTextQuoteOptions = {},
): Promise<TextQuoteSelector> {
- // Default to search in the whole document.
- let scope: Range;
- if (maybeScope !== undefined) {
- scope = maybeScope;
- } else {
- const document = ownerDocument(range);
- scope = document.createRange();
- scope.selectNodeContents(document);
- }
+ const scopeAsRange = toRange(scope ?? ownerDocument(range));
- const chunker = new TextNodeChunker(scope);
+ const chunker = new TextNodeChunker(scopeAsRange);
return await abstractDescribeTextQuote(
chunker.rangeToChunkRange(range),
- () => new TextNodeChunker(scope),
+ () => new TextNodeChunker(scopeAsRange),
options,
);
}
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index a923586..2b29df6 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -44,10 +44,7 @@
* ```
* // Find the word ‘banana’.
* const selector = { type: 'TextQuoteSelector', exact: 'banana' };
- *
- * // Search in the document body.
- * const scope = document.createRange();
- * scope.selectNodeContents(document.body);
+ * const scope = document.body;
*
* // Read all matches.
* const matches = textQuoteSelectorMatcher(selector)(scope);
@@ -58,17 +55,15 @@
* // endOffset: 637, … }
* ```
*
- * @param selector - The {@link TextQuoteSelector}
- * to be anchored
- * @returns a {@link Matcher} function that applies
- * `selector` to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @param selector - The {@link TextQuoteSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
*
* @public
*/
export function createTextQuoteSelectorMatcher(
selector: TextQuoteSelector,
-): Matcher<Range, Range> {
+): Matcher<Node | Range, Range> {
const abstractMatcher = abstractTextQuoteSelectorMatcher(selector);
return async function* matchAll(scope) {