|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Licensed to the Apache Software Foundation (ASF) under one |
| 4 | + * or more contributor license agreements. See the NOTICE file |
| 5 | + * distributed with this work for additional information |
| 6 | + * regarding copyright ownership. The ASF licenses this file |
| 7 | + * to you under the Apache License, Version 2.0 (the |
| 8 | + * "License"); you may not use this file except in compliance |
| 9 | + * with the License. You may obtain a copy of the License at |
| 10 | + * |
| 11 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | + * |
| 13 | + * Unless required by applicable law or agreed to in writing, |
| 14 | + * software distributed under the License is distributed on an |
| 15 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 16 | + * KIND, either express or implied. See the License for the |
| 17 | + * specific language governing permissions and limitations |
| 18 | + * under the License. |
| 19 | + * |
| 20 | + * SPDX-FileCopyrightText: The Apache Software Foundation |
| 21 | + * SPDX-License-Identifier: Apache-2.0 |
| 22 | + */ |
| 23 | + |
| 24 | +import type { Matcher, Selector } from './types.js'; |
| 25 | + |
| 26 | +/** |
| 27 | + * A Refinable selector can have the `refinedBy` attribute, whose value must be |
| 28 | + * of the same type (possibly again refined, recursively). |
| 29 | + * |
| 30 | + * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection |
| 31 | + * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model. |
| 32 | + * |
| 33 | + * @example |
| 34 | + * Example value of type `Refinable<CssSelector, TextQuoteSelector>`: |
| 35 | + * |
| 36 | + * { |
| 37 | + * type: "CssSelector", |
| 38 | + * …, |
| 39 | + * refinedBy: { |
| 40 | + * type: "TextQuoteSelector", |
| 41 | + * …, |
| 42 | + * refinedBy: { … }, // again either a CssSelector or TextQuoteSelector |
| 43 | + * } |
| 44 | + * } |
| 45 | + */ |
| 46 | +export type Refinable<T extends Selector> = T & { refinedBy?: Refinable<T> }; |
| 47 | + |
| 48 | +/** |
| 49 | + * Wrap a matcher creation function so that it supports refinement of selection. |
| 50 | + * |
| 51 | + * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection |
| 52 | + * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model. |
| 53 | + * |
| 54 | + * @param matcherCreator - The function to wrap; it will be executed both for |
| 55 | + * {@link Selector}s passed to the returned wrapper function, and for any |
| 56 | + * refining Selector those might contain (and any refinement of that, etc.). |
| 57 | + * |
| 58 | + * @public |
| 59 | + */ |
| 60 | +export function makeRefinable< |
| 61 | + TSelector extends Selector, |
| 62 | + TScope, |
| 63 | + // To enable refinement, the implementation’s Match object must be usable as a |
| 64 | + // Scope object itself. |
| 65 | + TMatch extends TScope |
| 66 | +>( |
| 67 | + matcherCreator: (selector: Refinable<TSelector>) => Matcher<TScope, TMatch>, |
| 68 | +): (selector: Refinable<TSelector>) => Matcher<TScope, TMatch> { |
| 69 | + return function createMatcherWithRefinement( |
| 70 | + sourceSelector: Refinable<TSelector>, |
| 71 | + ): Matcher<TScope, TMatch> { |
| 72 | + const matcher = matcherCreator(sourceSelector); |
| 73 | + |
| 74 | + if (sourceSelector.refinedBy) { |
| 75 | + const refiningSelector = createMatcherWithRefinement( |
| 76 | + sourceSelector.refinedBy, |
| 77 | + ); |
| 78 | + |
| 79 | + return async function* matchAll(scope) { |
| 80 | + for await (const match of matcher(scope)) { |
| 81 | + yield* refiningSelector(match); |
| 82 | + } |
| 83 | + }; |
| 84 | + } |
| 85 | + |
| 86 | + return matcher; |
| 87 | + }; |
| 88 | +} |
0 commit comments