Refactor API
Refactor the API so that creating a selector results in a function that
accepts a scope against which to match the selector.
diff --git a/demo/search.js b/demo/search.js
index c51c1b8..150058f 100644
--- a/demo/search.js
+++ b/demo/search.js
@@ -13,29 +13,34 @@
* the License.
*/
-import { createRangeSelector } from '@annotator/range';
-import { createAnySelectorCreator } from '@annotator/selector';
+import { makeRefinable } from '@annotator/selector';
+import { createRangeSelectorCreator } from '@annotator/range';
import { createTextQuoteSelector } from '@annotator/text';
-const allSelectorTypes = {
- TextQuoteSelector: createTextQuoteSelector,
- RangeSelector: createRangeSelector,
-};
+const createSelector = makeRefinable(selector => {
+ const selectorCreator = {
+ TextQuoteSelector: createTextQuoteSelector,
+ RangeSelector: createRangeSelectorCreator(createSelector),
+ }[selector.type];
-const createSelector = createAnySelectorCreator(allSelectorTypes);
+ if (selectorCreator == null) {
+ throw new Error(`Unsupported selector type: ${selector.type}`);
+ }
+
+ return selectorCreator(selector);
+});
/**
* Locate a selector.
* @param {Node} root node
- * @param {Selector} descriptor
+ * @param {Selector} selector
* @return {Range}
*/
-export async function* search(root, descriptor) {
+export async function* search(root, selector) {
for (const node of nodeIterator(root)) {
if (!node.nodeValue) continue;
- const selector = createSelector(node.nodeValue);
- const matches = selector([descriptor]);
+ const matches = createSelector(selector)(node.nodeValue);
for await (let match of matches) {
const startIndex = match.index;
diff --git a/packages/dom/src/index.js b/packages/dom/src/index.js
index 80b1efd..a1d9cbd 100644
--- a/packages/dom/src/index.js
+++ b/packages/dom/src/index.js
@@ -15,11 +15,8 @@
export * from './text';
-export function createCssSelector(context) {
- async function* exec(descriptors) {
- const cssSelector = descriptors.map(({ value }) => value).join(',');
- yield* context.querySelectorAll(cssSelector);
- }
-
- return exec;
+export function createCssSelector(selector) {
+ return async function* matchAll(scope) {
+ yield* scope.querySelectorAll(selector.value);
+ };
}
diff --git a/packages/dom/src/text/quote.js b/packages/dom/src/text/quote.js
index 13b6add..87c7926 100644
--- a/packages/dom/src/text/quote.js
+++ b/packages/dom/src/text/quote.js
@@ -30,7 +30,7 @@
const contextText = context.cloneContents().textContent;
const exact = range.cloneContents().textContent;
- const descriptor = {
+ const selector = {
type: 'TextQuoteSelector',
exact,
};
@@ -46,11 +46,11 @@
) {
throw new Error(`Context not equal to range's container; not implemented.`);
}
+
const rangeIndex = range.startOffset;
const rangeEndIndex = range.endOffset;
- const selector = createTextQuoteSelector(contextText);
- const matches = selector([descriptor]);
+ const matches = createTextQuoteSelector(selector)(contextText);
const minSuffixes = [];
const minPrefixes = [];
for await (let match of matches) {
@@ -72,18 +72,15 @@
}
const [minSuffix, minPrefix] = minimalSolution(minSuffixes, minPrefixes);
if (minSuffix > 0) {
- descriptor.suffix = contextText.substring(
+ selector.suffix = contextText.substring(
rangeEndIndex,
rangeEndIndex + minSuffix,
);
}
if (minPrefix > 0) {
- descriptor.prefix = contextText.substring(
- rangeIndex - minPrefix,
- rangeIndex,
- );
+ selector.prefix = contextText.substring(rangeIndex - minPrefix, rangeIndex);
}
- return descriptor;
+ return selector;
}
function overlap(text1, text2) {
diff --git a/packages/range/src/index.js b/packages/range/src/index.js
index a9b8b82..4432423 100644
--- a/packages/range/src/index.js
+++ b/packages/range/src/index.js
@@ -15,32 +15,30 @@
import { product } from './cartesian.js';
-export function createRangeSelector(context, createAnySelector) {
- const startSelector = createAnySelector(context);
- const endSelector = createAnySelector(context);
+export function createRangeSelectorCreator(createSelector) {
+ return function createRangeSelector(selector) {
+ const startSelector = createSelector(selector.startSelector);
+ const endSelector = createSelector(selector.endSelector);
- async function* rangeSelector(descriptors) {
- const descriptor = descriptors[0]; // TODO handle multiple descriptors
- const startMatches = startSelector([descriptor.startSelector]);
- const endMatches = endSelector([descriptor.endSelector]);
- const pairs = product(startMatches, endMatches);
- for await (let [start, end] of pairs) {
- if (start.index > end.index) {
- continue;
+ return async function* matchAll(scope) {
+ const startMatches = startSelector(scope);
+ const endMatches = endSelector(scope);
+ const pairs = product(startMatches, endMatches);
+ for await (let [start, end] of pairs) {
+ if (start.index > end.index) {
+ continue;
+ }
+ const text = rangeBetween({ start, end, scope });
+ const result = [text];
+ result.index = start.index;
+ result.input = scope;
+ yield result;
}
- const text = rangeBetween({ start, end, context });
- const result = [text];
- result.index = start.index;
- result.input = context;
- result.descriptor = descriptor;
- yield result;
- }
- }
-
- return rangeSelector;
+ };
+ };
}
-function rangeBetween({ start, end, context }) {
- const range = context.substring(start.index, end.index);
+function rangeBetween({ start, end, scope }) {
+ const range = scope.substring(start.index, end.index);
return range;
}
diff --git a/packages/selector/src/index.js b/packages/selector/src/index.js
index 17b79f1..86c6ef9 100644
--- a/packages/selector/src/index.js
+++ b/packages/selector/src/index.js
@@ -13,52 +13,30 @@
* the License.
*/
-export function createAnySelectorCreator(selectorCreatorsByType) {
- function selectSelector(context, type) {
- const selectorCreatorForType = selectorCreatorsByType[type];
- if (selectorCreatorForType === undefined) {
- throw new Error(`Unsupported selector type: ${type}`);
- }
- let selector = selectorCreatorForType(context, selectorCreator);
- selector = makeRefinable(selector, selectorCreator);
- return selector;
- }
+export function makeRefinable(selectorCreator) {
+ return function createSelector(source) {
+ const selector = selectorCreator(source);
- function selectorCreator(context) {
- async function* anySelector(descriptors) {
- const descriptor = descriptors[0]; // TODO handle multiple descriptors
- const selectorFunc = selectSelector(context, descriptor.type);
- yield* selectorFunc([descriptor]);
- }
- return anySelector;
- }
+ if (source.refinedBy) {
+ const refiningSelector = createSelector(source.refinedBy);
- return selectorCreator;
-}
-
-export function makeRefinable(selector, selectorCreator) {
- async function* refinableSelector(descriptors) {
- const matches = selector(descriptors);
- for await (let match of matches) {
- const refiningDescriptor = match.descriptor.refinedBy;
- if (refiningDescriptor) {
- const refiningContext = matchAsContext(match);
- const refiningSelector = selectorCreator(refiningContext);
- const refiningMatches = refiningSelector([refiningDescriptor]);
- for await (let refiningMatch of refiningMatches) {
- const refinedMatch = composeMatches(refiningMatch, match);
- yield refinedMatch;
+ return async function* matchAll(scope) {
+ const matches = selector(scope);
+ for await (let match of matches) {
+ const refiningScope = matchAsScope(match);
+ const refiningMatches = refiningSelector(refiningScope);
+ for await (let refiningMatch of refiningMatches) {
+ yield composeMatches(refiningMatch, match);
+ }
}
- } else {
- yield match;
- }
+ };
}
- }
- return refinableSelector;
+ return selector;
+ };
}
-function matchAsContext(match) {
+function matchAsScope(match) {
return match[0];
}
@@ -67,7 +45,6 @@
const refinedMatch = [...refiningMatch];
refinedMatch.index = match.index + refiningMatch.index;
refinedMatch.input = match.input;
- refinedMatch.descriptor = match.descriptor;
return refinedMatch;
});
}
diff --git a/packages/text/src/index.js b/packages/text/src/index.js
index fa69321..93e2825 100644
--- a/packages/text/src/index.js
+++ b/packages/text/src/index.js
@@ -13,32 +13,28 @@
* the License.
*/
-export function createTextQuoteSelector(context) {
- async function* exec(descriptors) {
- for (let descriptor of descriptors) {
- const prefix = descriptor.prefix || '';
- const suffix = descriptor.suffix || '';
- const pattern = prefix + descriptor.exact + suffix;
- let lastIndex = 0;
- let next = () => context.indexOf(pattern, lastIndex);
- let match = next();
- while (match !== -1) {
- let result = [descriptor.exact];
- result.index = match + prefix.length;
- result.input = context;
- result.descriptor = descriptor;
- yield result;
- lastIndex = match + 1;
- match = next();
- }
+export function createTextQuoteSelector(selector) {
+ return async function* matchAll(scope) {
+ const prefix = selector.prefix || '';
+ const suffix = selector.suffix || '';
+ const pattern = prefix + selector.exact + suffix;
+ let lastIndex = 0;
+ let next = () => scope.indexOf(pattern, lastIndex);
+ let match = next();
+ while (match !== -1) {
+ let result = [selector.exact];
+ result.index = match + prefix.length;
+ result.input = scope;
+ result.selector = selector;
+ yield result;
+ lastIndex = match + 1;
+ match = next();
}
- }
-
- return exec;
+ };
}
-export function describeTextQuote({ context, startIndex, endIndex }) {
- const exact = context.substring(startIndex, endIndex);
+export function describeTextQuote({ scope, startIndex, endIndex }) {
+ const exact = scope.substring(startIndex, endIndex);
return {
type: 'TextQuoteSelector',
exact,