openbriefing 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cli.js +8 -0
- package/dist/obs.d.ts +20 -1
- package/dist/obs.js +130 -20
- package/fixtures/expected.json +261 -2
- package/fixtures/feed-rss.xml +2 -0
- package/fixtures/highlight-label-orphan.html +15 -0
- package/fixtures/highlight-limits.html +20 -0
- package/fixtures/highlight-not-in-text.html +17 -0
- package/fixtures/highlight-reserved.html +16 -0
- package/fixtures/highlights.html +18 -0
- package/fixtures/valid-full.html +3 -0
- package/package.json +24 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This package is the reference parser and checker for the [Open Briefing Standard (OBS)](https://openbriefingstandard.org). OBS is how a publisher says how their content should be summarized and presented when software consumes it.
|
|
4
4
|
|
|
5
|
-
**Version 0.
|
|
5
|
+
**Version 0.2.** It contains the parser and `openbriefing check`, and reads spec version 1.1, which adds `obs:highlight`: passages the publisher marks, word for word from the content, for quoting on their own with credit. `check` lists each highlight with its word count and label, and warns when one is not found in the page's text. Later versions add `generate`, `preview`, `backfill`, `review` and a Ghost integration.
|
|
6
6
|
|
|
7
7
|
## Check a page or a feed
|
|
8
8
|
|
|
@@ -22,6 +22,7 @@ import { parseObs } from "openbriefing";
|
|
|
22
22
|
|
|
23
23
|
const dom = new JSDOM(html, { url });
|
|
24
24
|
const { brief, errors, warnings } = parseObs(dom.window.document, url);
|
|
25
|
+
// brief.summaries, brief.keep, brief.highlights (each { text, words, label?, inContent? }), …
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
| Export | What it does |
|
|
@@ -30,7 +31,7 @@ const { brief, errors, warnings } = parseObs(dom.window.document, url);
|
|
|
30
31
|
| `parseFeedObs(itemElement, itemUrl?)` | Reads one RSS `<item>` or Atom `<entry>` |
|
|
31
32
|
| `validateObs(object)` | Checks an object already in the `ObsBrief` shape |
|
|
32
33
|
| `mergeObs(page, feed)` | Applies the feed-against-page rule: the later `obs:modified` stands, and the page wins a tie |
|
|
33
|
-
| `tierFor(words)`, `SIZES`, `normalizeUrl` | Helpers that use the spec's numbers |
|
|
34
|
+
| `tierFor(words)`, `SIZES`, `normalizeUrl`, `looseText` | Helpers that use the spec's numbers and its loose text comparison |
|
|
34
35
|
|
|
35
36
|
The parser has no runtime dependencies. It takes any DOM `Document` (jsdom, linkedom, or a browser's `DOMParser`), makes no network requests, and never throws.
|
|
36
37
|
|
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,14 @@ function describe(label, r) {
|
|
|
61
61
|
row(`summary`, `${s.tier.padEnd(6)} ${String(s.words).padStart(5)} words (target ${lo}–${hi}) ${s.from}`);
|
|
62
62
|
}
|
|
63
63
|
b.keep.forEach((k, i) => row(i === 0 ? "keep (core)" : `keep ${i + 1}`, cut(k)));
|
|
64
|
+
b.highlights.forEach((h, i) => {
|
|
65
|
+
const notes = [`${h.words}/${SIZES.highlight} words`];
|
|
66
|
+
if (h.label !== undefined)
|
|
67
|
+
notes.push(cut(h.label, 30));
|
|
68
|
+
if (h.inContent === false)
|
|
69
|
+
notes.push("not in the text");
|
|
70
|
+
row(`highlight ${i + 1}`, `${cut(h.text)} (${notes.join(", ")})`);
|
|
71
|
+
});
|
|
64
72
|
row("derive", `${b.derive.mode}; translate ${b.derive.translate ? "yes" : "no"}; combine ${b.derive.combine ? "yes" : "no"}`);
|
|
65
73
|
if (b.attribution !== undefined)
|
|
66
74
|
row("attribution", cut(b.attribution));
|
package/dist/obs.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ export interface ObsSummary {
|
|
|
18
18
|
/** "tiered" for obs:summary:{tier}; "inferred" for a bare obs:summary (§4). */
|
|
19
19
|
from: "tiered" | "inferred";
|
|
20
20
|
}
|
|
21
|
+
export interface ObsHighlight {
|
|
22
|
+
/** The passage, as written. */
|
|
23
|
+
text: string;
|
|
24
|
+
words: number;
|
|
25
|
+
/** obs:highlight:label, 6 words or fewer, when given. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** False when the page's text does not contain the highlight (§7). Absent for feed items and empty pages. */
|
|
28
|
+
inContent?: boolean;
|
|
29
|
+
}
|
|
21
30
|
export interface ObsBrief {
|
|
22
31
|
version: number;
|
|
23
32
|
/** obs:modified, as given (ISO 8601). */
|
|
@@ -32,6 +41,8 @@ export interface ObsBrief {
|
|
|
32
41
|
summaries: ObsSummary[];
|
|
33
42
|
/** Priority order, at most 3. keep[0] is the core message (§6). */
|
|
34
43
|
keep: string[];
|
|
44
|
+
/** Priority order, at most 5 (§7). Empty when none. */
|
|
45
|
+
highlights: ObsHighlight[];
|
|
35
46
|
derive: {
|
|
36
47
|
mode: DeriveMode;
|
|
37
48
|
translate: boolean;
|
|
@@ -56,6 +67,9 @@ export declare const SIZES: {
|
|
|
56
67
|
readonly keep: 40;
|
|
57
68
|
readonly attribution: 12;
|
|
58
69
|
readonly keepMax: 3;
|
|
70
|
+
readonly highlight: 60;
|
|
71
|
+
readonly highlightLabel: 6;
|
|
72
|
+
readonly highlightMax: 5;
|
|
59
73
|
readonly tiers: Record<ObsTier, {
|
|
60
74
|
target: [number, number];
|
|
61
75
|
}>;
|
|
@@ -68,8 +82,13 @@ export declare const SIZES: {
|
|
|
68
82
|
export declare function tierFor(words: number): ObsTier;
|
|
69
83
|
/** Words are runs of characters between white space (§4). */
|
|
70
84
|
export declare function countWords(text: string): number;
|
|
71
|
-
/** URL comparison of §
|
|
85
|
+
/** URL comparison of §12: scheme and host case, trailing slash, tracking parameters. */
|
|
72
86
|
export declare function normalizeUrl(input: string, base?: string): string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The loose comparison of §7: compatibility forms, straight quotes for curly,
|
|
89
|
+
* one kind of dash, one kind of space, one case. Words themselves must match.
|
|
90
|
+
*/
|
|
91
|
+
export declare function looseText(s: string): string;
|
|
73
92
|
/**
|
|
74
93
|
* Reads a page's obs: meta tags. `pageUrl` is where the page was fetched
|
|
75
94
|
* from; the page's <link rel="canonical"> takes precedence for §11.
|
package/dist/obs.js
CHANGED
|
@@ -15,6 +15,9 @@ export const SIZES = {
|
|
|
15
15
|
keep: 40,
|
|
16
16
|
attribution: 12,
|
|
17
17
|
keepMax: 3,
|
|
18
|
+
highlight: 60,
|
|
19
|
+
highlightLabel: 6,
|
|
20
|
+
highlightMax: 5,
|
|
18
21
|
tiers: {
|
|
19
22
|
short: { target: [40, 80] },
|
|
20
23
|
medium: { target: [200, 350] },
|
|
@@ -36,6 +39,7 @@ export function countWords(text) {
|
|
|
36
39
|
const t = text.trim();
|
|
37
40
|
return t === "" ? 0 : t.split(/\s+/u).length;
|
|
38
41
|
}
|
|
42
|
+
const newBag = () => ({ props: new Map(), highlights: [] });
|
|
39
43
|
const KNOWN = new Set([
|
|
40
44
|
"obs:modified",
|
|
41
45
|
"obs:summary",
|
|
@@ -45,6 +49,8 @@ const KNOWN = new Set([
|
|
|
45
49
|
"obs:title",
|
|
46
50
|
"obs:headline",
|
|
47
51
|
"obs:keep",
|
|
52
|
+
"obs:highlight",
|
|
53
|
+
"obs:highlight:label",
|
|
48
54
|
"obs:derive",
|
|
49
55
|
"obs:derive:translate",
|
|
50
56
|
"obs:derive:combine",
|
|
@@ -55,14 +61,33 @@ const KNOWN = new Set([
|
|
|
55
61
|
"obs:version",
|
|
56
62
|
]);
|
|
57
63
|
function put(bag, key, value) {
|
|
58
|
-
const list = bag.get(key);
|
|
64
|
+
const list = bag.props.get(key);
|
|
59
65
|
if (list)
|
|
60
66
|
list.push(value);
|
|
61
67
|
else
|
|
62
|
-
bag.set(key, [value]);
|
|
68
|
+
bag.props.set(key, [value]);
|
|
63
69
|
}
|
|
70
|
+
/** Files one property, routing highlights and their labels to the ordered list (§3.1). */
|
|
71
|
+
function collect(bag, key, value, warnings) {
|
|
72
|
+
if (key === "obs:highlight") {
|
|
73
|
+
bag.highlights.push({ text: value });
|
|
74
|
+
}
|
|
75
|
+
else if (key === "obs:highlight:label") {
|
|
76
|
+
const last = bag.highlights[bag.highlights.length - 1];
|
|
77
|
+
if (last === undefined)
|
|
78
|
+
warnings.push("obs:highlight:label before any obs:highlight; it is ignored (§3.1)");
|
|
79
|
+
else if (last.label === undefined)
|
|
80
|
+
last.label = value;
|
|
81
|
+
else
|
|
82
|
+
warnings.push(`obs:highlight #${bag.highlights.length} has two labels; the first is used (§3.1)`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
put(bag, key, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const isEmpty = (bag) => bag.props.size === 0 && bag.highlights.length === 0;
|
|
64
89
|
function first(bag, key, warnings) {
|
|
65
|
-
const list = bag.get(key);
|
|
90
|
+
const list = bag.props.get(key);
|
|
66
91
|
if (!list || list.length === 0)
|
|
67
92
|
return undefined;
|
|
68
93
|
if (list.length > 1 && key !== "obs:keep") {
|
|
@@ -77,14 +102,14 @@ function cleanText(key, raw, warnings) {
|
|
|
77
102
|
let text = raw;
|
|
78
103
|
if (TAG_RE.test(text)) {
|
|
79
104
|
text = text.replace(TAG_RE, "");
|
|
80
|
-
warnings.push(`${key}: HTML removed; values are plain text (§
|
|
105
|
+
warnings.push(`${key}: HTML removed; values are plain text (§9)`);
|
|
81
106
|
}
|
|
82
107
|
TAG_RE.lastIndex = 0;
|
|
83
108
|
text = text.replace(/\r\n?/gu, "\n").trim();
|
|
84
109
|
if (text === "")
|
|
85
110
|
return undefined;
|
|
86
111
|
if (URL_RE.test(text))
|
|
87
|
-
warnings.push(`${key}: contains a URL; summaries should not (§
|
|
112
|
+
warnings.push(`${key}: contains a URL; summaries should not (§9)`);
|
|
88
113
|
return text;
|
|
89
114
|
}
|
|
90
115
|
function enumValue(key, raw, allowed, fallback, warnings) {
|
|
@@ -102,7 +127,7 @@ function validModified(v) {
|
|
|
102
127
|
return ISO_RE.test(v) && !Number.isNaN(Date.parse(v));
|
|
103
128
|
}
|
|
104
129
|
const TRACKING = /^(?:utm_.*|fbclid|gclid)$/iu;
|
|
105
|
-
/** URL comparison of §
|
|
130
|
+
/** URL comparison of §12: scheme and host case, trailing slash, tracking parameters. */
|
|
106
131
|
export function normalizeUrl(input, base) {
|
|
107
132
|
let u;
|
|
108
133
|
try {
|
|
@@ -130,11 +155,13 @@ function sizeWarning(key, words, max, warnings) {
|
|
|
130
155
|
function build(bag, ctx, extraWarnings = []) {
|
|
131
156
|
const errors = [];
|
|
132
157
|
const warnings = [...extraWarnings];
|
|
133
|
-
for (const key of bag.keys()) {
|
|
158
|
+
for (const key of bag.props.keys()) {
|
|
134
159
|
if (KNOWN.has(key))
|
|
135
160
|
continue;
|
|
136
161
|
if (key.startsWith("obs:audio:") || key === "obs:audio")
|
|
137
|
-
continue; // reserved, §
|
|
162
|
+
continue; // reserved, §15
|
|
163
|
+
if (key === "obs:highlight:start" || key === "obs:highlight:end")
|
|
164
|
+
continue; // reserved, §15
|
|
138
165
|
warnings.push(`${key} is not an OBS v1 property and is ignored (§3.1)`);
|
|
139
166
|
}
|
|
140
167
|
// Text values, cleaned once.
|
|
@@ -199,7 +226,7 @@ function build(bag, ctx, extraWarnings = []) {
|
|
|
199
226
|
if (headline !== undefined)
|
|
200
227
|
sizeWarning("obs:headline", countWords(headline), SIZES.headline, warnings);
|
|
201
228
|
// Keep lines (§6)
|
|
202
|
-
const keepAll = (bag.get("obs:keep") ?? [])
|
|
229
|
+
const keepAll = (bag.props.get("obs:keep") ?? [])
|
|
203
230
|
.map((raw) => cleanText("obs:keep", raw, warnings))
|
|
204
231
|
.filter((v) => v !== undefined);
|
|
205
232
|
if (keepAll.length > SIZES.keepMax) {
|
|
@@ -207,6 +234,31 @@ function build(bag, ctx, extraWarnings = []) {
|
|
|
207
234
|
}
|
|
208
235
|
const keep = keepAll.slice(0, SIZES.keepMax);
|
|
209
236
|
keep.forEach((k, i) => sizeWarning(`obs:keep #${i + 1}`, countWords(k), SIZES.keep, warnings));
|
|
237
|
+
// Highlights (§7)
|
|
238
|
+
const highlightsAll = [];
|
|
239
|
+
for (const raw of bag.highlights) {
|
|
240
|
+
const t = cleanText("obs:highlight", raw.text, warnings);
|
|
241
|
+
if (t === undefined)
|
|
242
|
+
continue;
|
|
243
|
+
const h = { text: t, words: countWords(t) };
|
|
244
|
+
if (raw.label !== undefined) {
|
|
245
|
+
const label = cleanText("obs:highlight:label", raw.label, warnings);
|
|
246
|
+
if (label !== undefined)
|
|
247
|
+
h.label = label;
|
|
248
|
+
}
|
|
249
|
+
if (typeof raw.inContent === "boolean")
|
|
250
|
+
h.inContent = raw.inContent;
|
|
251
|
+
highlightsAll.push(h);
|
|
252
|
+
}
|
|
253
|
+
if (highlightsAll.length > SIZES.highlightMax) {
|
|
254
|
+
warnings.push(`obs:highlight appears ${highlightsAll.length} times; only the first ${SIZES.highlightMax} are used (§3)`);
|
|
255
|
+
}
|
|
256
|
+
const highlights = highlightsAll.slice(0, SIZES.highlightMax);
|
|
257
|
+
highlights.forEach((h, i) => {
|
|
258
|
+
sizeWarning(`obs:highlight #${i + 1}`, h.words, SIZES.highlight, warnings);
|
|
259
|
+
if (h.label !== undefined)
|
|
260
|
+
sizeWarning(`obs:highlight:label #${i + 1}`, countWords(h.label), SIZES.highlightLabel, warnings);
|
|
261
|
+
});
|
|
210
262
|
// Derivation (§5)
|
|
211
263
|
const mode = enumValue("obs:derive", first(bag, "obs:derive", warnings), ["verbatim", "anchor", "open"], "anchor", warnings);
|
|
212
264
|
const translate = enumValue("obs:derive:translate", first(bag, "obs:derive:translate", warnings), ["yes", "no"], "yes", warnings) === "yes";
|
|
@@ -216,7 +268,7 @@ function build(bag, ctx, extraWarnings = []) {
|
|
|
216
268
|
sizeWarning("obs:attribution", countWords(attribution), SIZES.attribution, warnings);
|
|
217
269
|
const preference = enumValue("obs:preference", first(bag, "obs:preference", warnings), ["summary", "full"], "summary", warnings);
|
|
218
270
|
const lang = first(bag, "obs:lang", warnings)?.trim() || ctx.lang || undefined;
|
|
219
|
-
// Identity (§
|
|
271
|
+
// Identity (§12)
|
|
220
272
|
let url;
|
|
221
273
|
let describesThisPage = true;
|
|
222
274
|
const rawUrl = first(bag, "obs:url", warnings)?.trim();
|
|
@@ -235,6 +287,7 @@ function build(bag, ctx, extraWarnings = []) {
|
|
|
235
287
|
describesThisPage,
|
|
236
288
|
summaries,
|
|
237
289
|
keep,
|
|
290
|
+
highlights,
|
|
238
291
|
derive: { mode, translate, combine },
|
|
239
292
|
preference,
|
|
240
293
|
};
|
|
@@ -250,6 +303,34 @@ function build(bag, ctx, extraWarnings = []) {
|
|
|
250
303
|
brief.lang = lang;
|
|
251
304
|
return { brief, errors, warnings };
|
|
252
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* The loose comparison of §7: compatibility forms, straight quotes for curly,
|
|
308
|
+
* one kind of dash, one kind of space, one case. Words themselves must match.
|
|
309
|
+
*/
|
|
310
|
+
export function looseText(s) {
|
|
311
|
+
return s
|
|
312
|
+
.normalize("NFKC")
|
|
313
|
+
.replace(/[\u2018\u2019\u201a\u201b\u2032]/gu, "'")
|
|
314
|
+
.replace(/[\u201c\u201d\u201e\u201f\u2033]/gu, '"')
|
|
315
|
+
.replace(/[\u2010-\u2015\u2212]/gu, "-")
|
|
316
|
+
.replace(/\s*-\s*/gu, "-")
|
|
317
|
+
.replace(/\s+/gu, " ")
|
|
318
|
+
.trim()
|
|
319
|
+
.toLowerCase();
|
|
320
|
+
}
|
|
321
|
+
/** Sets inContent on each highlight from the page's text; skipped when the body is empty (§7). */
|
|
322
|
+
function checkContent(brief, bodyText, warnings) {
|
|
323
|
+
const body = bodyText === undefined ? "" : looseText(bodyText);
|
|
324
|
+
if (body === "")
|
|
325
|
+
return;
|
|
326
|
+
for (const h of brief.highlights) {
|
|
327
|
+
h.inContent = body.includes(looseText(h.text));
|
|
328
|
+
if (!h.inContent) {
|
|
329
|
+
const head = h.text.split(/\s+/u).slice(0, 6).join(" ");
|
|
330
|
+
warnings.push(`obs:highlight "${head}…" is not in the page's text (§7)`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
253
334
|
function guard(fn) {
|
|
254
335
|
try {
|
|
255
336
|
return fn();
|
|
@@ -266,7 +347,7 @@ function guard(fn) {
|
|
|
266
347
|
*/
|
|
267
348
|
export function parseObs(doc, pageUrl) {
|
|
268
349
|
return guard(() => {
|
|
269
|
-
const bag =
|
|
350
|
+
const bag = newBag();
|
|
270
351
|
const warnings = [];
|
|
271
352
|
let inBody = false;
|
|
272
353
|
for (const meta of Array.from(doc.getElementsByTagName("meta"))) {
|
|
@@ -280,9 +361,9 @@ export function parseObs(doc, pageUrl) {
|
|
|
280
361
|
continue;
|
|
281
362
|
if (!inBody && doc.body && doc.body.contains(meta))
|
|
282
363
|
inBody = true;
|
|
283
|
-
|
|
364
|
+
collect(bag, key, content, warnings);
|
|
284
365
|
}
|
|
285
|
-
if (bag
|
|
366
|
+
if (isEmpty(bag))
|
|
286
367
|
return { errors: ["No obs: tags on the page"], warnings };
|
|
287
368
|
if (inBody)
|
|
288
369
|
warnings.push("Some obs: tags are in <body>; they belong in <head> (§3.1)");
|
|
@@ -294,11 +375,14 @@ export function parseObs(doc, pageUrl) {
|
|
|
294
375
|
ctx.canonical = canonical;
|
|
295
376
|
if (lang !== undefined)
|
|
296
377
|
ctx.lang = lang;
|
|
297
|
-
|
|
378
|
+
const result = build(bag, ctx, warnings);
|
|
379
|
+
if (result.brief)
|
|
380
|
+
checkContent(result.brief, doc.body?.textContent ?? undefined, result.warnings);
|
|
381
|
+
return result;
|
|
298
382
|
});
|
|
299
383
|
}
|
|
300
384
|
// ---------------------------------------------------------------------------
|
|
301
|
-
// Feeds (§
|
|
385
|
+
// Feeds (§14)
|
|
302
386
|
function obsChildren(item) {
|
|
303
387
|
return Array.from(item.children).filter((el) => {
|
|
304
388
|
if (el.namespaceURI === OBS_NS)
|
|
@@ -335,12 +419,18 @@ function itemLink(item) {
|
|
|
335
419
|
*/
|
|
336
420
|
export function parseFeedObs(item, itemUrl) {
|
|
337
421
|
return guard(() => {
|
|
338
|
-
const bag =
|
|
422
|
+
const bag = newBag();
|
|
339
423
|
const warnings = [];
|
|
340
424
|
for (const el of obsChildren(item)) {
|
|
341
425
|
const name = localName(el);
|
|
342
426
|
const value = el.textContent ?? "";
|
|
343
|
-
if (name === "
|
|
427
|
+
if (name === "highlight") {
|
|
428
|
+
collect(bag, "obs:highlight", value, warnings);
|
|
429
|
+
const label = el.getAttribute("label");
|
|
430
|
+
if (label !== null)
|
|
431
|
+
collect(bag, "obs:highlight:label", label, warnings);
|
|
432
|
+
}
|
|
433
|
+
else if (name === "summary") {
|
|
344
434
|
const length = el.getAttribute("length")?.trim().toLowerCase();
|
|
345
435
|
if (length === undefined || length === "")
|
|
346
436
|
put(bag, "obs:summary", value);
|
|
@@ -363,7 +453,7 @@ export function parseFeedObs(item, itemUrl) {
|
|
|
363
453
|
put(bag, `obs:${name}`, value);
|
|
364
454
|
}
|
|
365
455
|
}
|
|
366
|
-
if (bag
|
|
456
|
+
if (isEmpty(bag))
|
|
367
457
|
return { errors: ["No OBS elements on the item"], warnings };
|
|
368
458
|
const link = itemUrl ?? itemLink(item);
|
|
369
459
|
const canonical = link ? normalizeUrl(link) : undefined;
|
|
@@ -382,7 +472,7 @@ export function validateObs(input) {
|
|
|
382
472
|
return { errors: ["Expected an object in the ObsBrief shape"], warnings: [] };
|
|
383
473
|
}
|
|
384
474
|
const o = input;
|
|
385
|
-
const bag =
|
|
475
|
+
const bag = newBag();
|
|
386
476
|
const warnings = [];
|
|
387
477
|
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
388
478
|
const set = (key, v) => {
|
|
@@ -422,6 +512,26 @@ export function validateObs(input) {
|
|
|
422
512
|
if (Array.isArray(o.keep))
|
|
423
513
|
for (const k of o.keep)
|
|
424
514
|
set("obs:keep", k);
|
|
515
|
+
if (Array.isArray(o.highlights)) {
|
|
516
|
+
for (const h of o.highlights) {
|
|
517
|
+
if (!h || typeof h !== "object")
|
|
518
|
+
continue;
|
|
519
|
+
const rec = h;
|
|
520
|
+
const t = str(rec.text);
|
|
521
|
+
if (t === undefined) {
|
|
522
|
+
warnings.push("A highlight has no text; it is ignored");
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const raw = { text: t };
|
|
526
|
+
const label = str(rec.label);
|
|
527
|
+
if (label !== undefined)
|
|
528
|
+
raw.label = label;
|
|
529
|
+
// The producer saw the page; its verdict is carried through.
|
|
530
|
+
if (typeof rec.inContent === "boolean")
|
|
531
|
+
raw.inContent = rec.inContent;
|
|
532
|
+
bag.highlights.push(raw);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
425
535
|
if (o.derive && typeof o.derive === "object") {
|
|
426
536
|
const d = o.derive;
|
|
427
537
|
set("obs:derive", d.mode);
|
|
@@ -438,7 +548,7 @@ export function validateObs(input) {
|
|
|
438
548
|
});
|
|
439
549
|
}
|
|
440
550
|
// ---------------------------------------------------------------------------
|
|
441
|
-
// Feed against page (§
|
|
551
|
+
// Feed against page (§14)
|
|
442
552
|
/** The later obs:modified stands; on a tie, the page stands. */
|
|
443
553
|
export function mergeObs(page, feed) {
|
|
444
554
|
if (!page)
|
package/fixtures/expected.json
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"keep": [],
|
|
22
|
+
"highlights": [],
|
|
22
23
|
"derive": {
|
|
23
24
|
"mode": "anchor",
|
|
24
25
|
"translate": true,
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
}
|
|
47
48
|
],
|
|
48
49
|
"keep": [],
|
|
50
|
+
"highlights": [],
|
|
49
51
|
"derive": {
|
|
50
52
|
"mode": "anchor",
|
|
51
53
|
"translate": true,
|
|
@@ -71,6 +73,7 @@
|
|
|
71
73
|
}
|
|
72
74
|
],
|
|
73
75
|
"keep": [],
|
|
76
|
+
"highlights": [],
|
|
74
77
|
"derive": {
|
|
75
78
|
"mode": "anchor",
|
|
76
79
|
"translate": true,
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
}
|
|
99
102
|
],
|
|
100
103
|
"keep": [],
|
|
104
|
+
"highlights": [],
|
|
101
105
|
"derive": {
|
|
102
106
|
"mode": "anchor",
|
|
103
107
|
"translate": true,
|
|
@@ -123,6 +127,7 @@
|
|
|
123
127
|
}
|
|
124
128
|
],
|
|
125
129
|
"keep": [],
|
|
130
|
+
"highlights": [],
|
|
126
131
|
"derive": {
|
|
127
132
|
"mode": "anchor",
|
|
128
133
|
"translate": true,
|
|
@@ -150,6 +155,7 @@
|
|
|
150
155
|
}
|
|
151
156
|
],
|
|
152
157
|
"keep": [],
|
|
158
|
+
"highlights": [],
|
|
153
159
|
"derive": {
|
|
154
160
|
"mode": "anchor",
|
|
155
161
|
"translate": true,
|
|
@@ -178,6 +184,7 @@
|
|
|
178
184
|
}
|
|
179
185
|
],
|
|
180
186
|
"keep": [],
|
|
187
|
+
"highlights": [],
|
|
181
188
|
"derive": {
|
|
182
189
|
"mode": "verbatim",
|
|
183
190
|
"translate": true,
|
|
@@ -203,6 +210,7 @@
|
|
|
203
210
|
}
|
|
204
211
|
],
|
|
205
212
|
"keep": [],
|
|
213
|
+
"highlights": [],
|
|
206
214
|
"derive": {
|
|
207
215
|
"mode": "anchor",
|
|
208
216
|
"translate": true,
|
|
@@ -234,6 +242,7 @@
|
|
|
234
242
|
"keep": [
|
|
235
243
|
"Anyone may take vegetables home once the harvest begins."
|
|
236
244
|
],
|
|
245
|
+
"highlights": [],
|
|
237
246
|
"derive": {
|
|
238
247
|
"mode": "anchor",
|
|
239
248
|
"translate": true,
|
|
@@ -270,6 +279,17 @@
|
|
|
270
279
|
"keep": [
|
|
271
280
|
"Anyone may take vegetables home once the harvest begins."
|
|
272
281
|
],
|
|
282
|
+
"highlights": [
|
|
283
|
+
{
|
|
284
|
+
"text": "Anyone may take vegetables home once the harvest begins.",
|
|
285
|
+
"words": 9,
|
|
286
|
+
"label": "On the harvest"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"text": "Children from the school will help on the first Saturday.",
|
|
290
|
+
"words": 10
|
|
291
|
+
}
|
|
292
|
+
],
|
|
273
293
|
"derive": {
|
|
274
294
|
"mode": "anchor",
|
|
275
295
|
"translate": false,
|
|
@@ -296,6 +316,7 @@
|
|
|
296
316
|
}
|
|
297
317
|
],
|
|
298
318
|
"keep": [],
|
|
319
|
+
"highlights": [],
|
|
299
320
|
"derive": {
|
|
300
321
|
"mode": "anchor",
|
|
301
322
|
"translate": true,
|
|
@@ -323,6 +344,216 @@
|
|
|
323
344
|
}
|
|
324
345
|
],
|
|
325
346
|
"keep": [],
|
|
347
|
+
"highlights": [],
|
|
348
|
+
"derive": {
|
|
349
|
+
"mode": "anchor",
|
|
350
|
+
"translate": true,
|
|
351
|
+
"combine": true
|
|
352
|
+
},
|
|
353
|
+
"preference": "summary",
|
|
354
|
+
"lang": "en"
|
|
355
|
+
},
|
|
356
|
+
"errors": [],
|
|
357
|
+
"warnings": []
|
|
358
|
+
},
|
|
359
|
+
"highlight-label-orphan.html": {
|
|
360
|
+
"brief": {
|
|
361
|
+
"version": 1,
|
|
362
|
+
"modified": "2026-11-08T14:00:00Z",
|
|
363
|
+
"describesThisPage": true,
|
|
364
|
+
"summaries": [
|
|
365
|
+
{
|
|
366
|
+
"tier": "medium",
|
|
367
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.",
|
|
368
|
+
"words": 280,
|
|
369
|
+
"from": "inferred"
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
"keep": [],
|
|
373
|
+
"highlights": [
|
|
374
|
+
{
|
|
375
|
+
"text": "Anyone may take vegetables home once the harvest begins.",
|
|
376
|
+
"words": 9,
|
|
377
|
+
"inContent": true
|
|
378
|
+
}
|
|
379
|
+
],
|
|
380
|
+
"derive": {
|
|
381
|
+
"mode": "anchor",
|
|
382
|
+
"translate": true,
|
|
383
|
+
"combine": true
|
|
384
|
+
},
|
|
385
|
+
"preference": "summary",
|
|
386
|
+
"lang": "en"
|
|
387
|
+
},
|
|
388
|
+
"errors": [],
|
|
389
|
+
"warnings": [
|
|
390
|
+
"obs:highlight:label before any obs:highlight; it is ignored (§3.1)"
|
|
391
|
+
]
|
|
392
|
+
},
|
|
393
|
+
"highlight-limits.html": {
|
|
394
|
+
"brief": {
|
|
395
|
+
"version": 1,
|
|
396
|
+
"modified": "2026-11-08T14:00:00Z",
|
|
397
|
+
"describesThisPage": true,
|
|
398
|
+
"summaries": [
|
|
399
|
+
{
|
|
400
|
+
"tier": "medium",
|
|
401
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.",
|
|
402
|
+
"words": 280,
|
|
403
|
+
"from": "inferred"
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
"keep": [],
|
|
407
|
+
"highlights": [
|
|
408
|
+
{
|
|
409
|
+
"text": "The garden club met on Tuesday to plan the spring planting.",
|
|
410
|
+
"words": 11,
|
|
411
|
+
"inContent": true
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools.",
|
|
415
|
+
"words": 69,
|
|
416
|
+
"inContent": true
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"text": "Most of the seeds came from last year's harvest.",
|
|
420
|
+
"words": 9,
|
|
421
|
+
"inContent": true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"text": "The committee chose tomatoes, beans and three kinds of squash.",
|
|
425
|
+
"words": 10,
|
|
426
|
+
"inContent": true
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"text": "Members who cannot dig offered to label the rows instead.",
|
|
430
|
+
"words": 10,
|
|
431
|
+
"inContent": true
|
|
432
|
+
}
|
|
433
|
+
],
|
|
434
|
+
"derive": {
|
|
435
|
+
"mode": "anchor",
|
|
436
|
+
"translate": true,
|
|
437
|
+
"combine": true
|
|
438
|
+
},
|
|
439
|
+
"preference": "summary",
|
|
440
|
+
"lang": "en"
|
|
441
|
+
},
|
|
442
|
+
"errors": [],
|
|
443
|
+
"warnings": [
|
|
444
|
+
"obs:highlight appears 7 times; only the first 5 are used (§3)",
|
|
445
|
+
"obs:highlight #2: 69 words, over its size of 60 (§4)"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
"highlight-not-in-text.html": {
|
|
449
|
+
"brief": {
|
|
450
|
+
"version": 1,
|
|
451
|
+
"modified": "2026-11-08T14:00:00Z",
|
|
452
|
+
"describesThisPage": true,
|
|
453
|
+
"summaries": [
|
|
454
|
+
{
|
|
455
|
+
"tier": "medium",
|
|
456
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.",
|
|
457
|
+
"words": 280,
|
|
458
|
+
"from": "inferred"
|
|
459
|
+
}
|
|
460
|
+
],
|
|
461
|
+
"keep": [],
|
|
462
|
+
"highlights": [
|
|
463
|
+
{
|
|
464
|
+
"text": "Most of the seeds came from last year’s harvest.",
|
|
465
|
+
"words": 9,
|
|
466
|
+
"inContent": true
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"text": "The fence — a new one — keeps the rabbits out.",
|
|
470
|
+
"words": 11,
|
|
471
|
+
"inContent": true
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"text": "The club bought a tractor for the heavy digging.",
|
|
475
|
+
"words": 9,
|
|
476
|
+
"inContent": false
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
"derive": {
|
|
480
|
+
"mode": "anchor",
|
|
481
|
+
"translate": true,
|
|
482
|
+
"combine": true
|
|
483
|
+
},
|
|
484
|
+
"preference": "summary",
|
|
485
|
+
"lang": "en"
|
|
486
|
+
},
|
|
487
|
+
"errors": [],
|
|
488
|
+
"warnings": [
|
|
489
|
+
"obs:highlight \"The club bought a tractor for…\" is not in the page's text (§7)"
|
|
490
|
+
]
|
|
491
|
+
},
|
|
492
|
+
"highlight-reserved.html": {
|
|
493
|
+
"brief": {
|
|
494
|
+
"version": 1,
|
|
495
|
+
"modified": "2026-11-08T14:00:00Z",
|
|
496
|
+
"describesThisPage": true,
|
|
497
|
+
"summaries": [
|
|
498
|
+
{
|
|
499
|
+
"tier": "medium",
|
|
500
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.",
|
|
501
|
+
"words": 280,
|
|
502
|
+
"from": "inferred"
|
|
503
|
+
}
|
|
504
|
+
],
|
|
505
|
+
"keep": [],
|
|
506
|
+
"highlights": [
|
|
507
|
+
{
|
|
508
|
+
"text": "Anyone may take vegetables home once the harvest begins.",
|
|
509
|
+
"words": 9,
|
|
510
|
+
"inContent": true
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
"derive": {
|
|
514
|
+
"mode": "anchor",
|
|
515
|
+
"translate": true,
|
|
516
|
+
"combine": true
|
|
517
|
+
},
|
|
518
|
+
"preference": "summary",
|
|
519
|
+
"lang": "en"
|
|
520
|
+
},
|
|
521
|
+
"errors": [],
|
|
522
|
+
"warnings": []
|
|
523
|
+
},
|
|
524
|
+
"highlights.html": {
|
|
525
|
+
"brief": {
|
|
526
|
+
"version": 1,
|
|
527
|
+
"modified": "2026-11-08T14:00:00Z",
|
|
528
|
+
"describesThisPage": true,
|
|
529
|
+
"summaries": [
|
|
530
|
+
{
|
|
531
|
+
"tier": "medium",
|
|
532
|
+
"text": "The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.",
|
|
533
|
+
"words": 280,
|
|
534
|
+
"from": "inferred"
|
|
535
|
+
}
|
|
536
|
+
],
|
|
537
|
+
"keep": [],
|
|
538
|
+
"highlights": [
|
|
539
|
+
{
|
|
540
|
+
"text": "Anyone may take vegetables home once the harvest begins.",
|
|
541
|
+
"words": 9,
|
|
542
|
+
"label": "On the harvest",
|
|
543
|
+
"inContent": true
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
"text": "Volunteers agreed to water the beds every second morning.",
|
|
547
|
+
"words": 9,
|
|
548
|
+
"inContent": true
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"text": "A new fence will keep the rabbits away from the lettuce.",
|
|
552
|
+
"words": 11,
|
|
553
|
+
"label": "On rabbits",
|
|
554
|
+
"inContent": true
|
|
555
|
+
}
|
|
556
|
+
],
|
|
326
557
|
"derive": {
|
|
327
558
|
"mode": "anchor",
|
|
328
559
|
"translate": true,
|
|
@@ -348,6 +579,7 @@
|
|
|
348
579
|
}
|
|
349
580
|
],
|
|
350
581
|
"keep": [],
|
|
582
|
+
"highlights": [],
|
|
351
583
|
"derive": {
|
|
352
584
|
"mode": "anchor",
|
|
353
585
|
"translate": true,
|
|
@@ -377,6 +609,7 @@
|
|
|
377
609
|
"The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered.",
|
|
378
610
|
"Children from the school help on the first Saturday."
|
|
379
611
|
],
|
|
612
|
+
"highlights": [],
|
|
380
613
|
"derive": {
|
|
381
614
|
"mode": "anchor",
|
|
382
615
|
"translate": true,
|
|
@@ -405,6 +638,7 @@
|
|
|
405
638
|
}
|
|
406
639
|
],
|
|
407
640
|
"keep": [],
|
|
641
|
+
"highlights": [],
|
|
408
642
|
"derive": {
|
|
409
643
|
"mode": "anchor",
|
|
410
644
|
"translate": true,
|
|
@@ -430,6 +664,7 @@
|
|
|
430
664
|
}
|
|
431
665
|
],
|
|
432
666
|
"keep": [],
|
|
667
|
+
"highlights": [],
|
|
433
668
|
"derive": {
|
|
434
669
|
"mode": "anchor",
|
|
435
670
|
"translate": true,
|
|
@@ -440,8 +675,8 @@
|
|
|
440
675
|
},
|
|
441
676
|
"errors": [],
|
|
442
677
|
"warnings": [
|
|
443
|
-
"obs:summary: HTML removed; values are plain text (§
|
|
444
|
-
"obs:summary: contains a URL; summaries should not (§
|
|
678
|
+
"obs:summary: HTML removed; values are plain text (§9)",
|
|
679
|
+
"obs:summary: contains a URL; summaries should not (§9)"
|
|
445
680
|
]
|
|
446
681
|
},
|
|
447
682
|
"missing-modified.html": {
|
|
@@ -470,6 +705,7 @@
|
|
|
470
705
|
}
|
|
471
706
|
],
|
|
472
707
|
"keep": [],
|
|
708
|
+
"highlights": [],
|
|
473
709
|
"derive": {
|
|
474
710
|
"mode": "anchor",
|
|
475
711
|
"translate": true,
|
|
@@ -495,6 +731,7 @@
|
|
|
495
731
|
}
|
|
496
732
|
],
|
|
497
733
|
"keep": [],
|
|
734
|
+
"highlights": [],
|
|
498
735
|
"derive": {
|
|
499
736
|
"mode": "anchor",
|
|
500
737
|
"translate": true,
|
|
@@ -528,6 +765,7 @@
|
|
|
528
765
|
}
|
|
529
766
|
],
|
|
530
767
|
"keep": [],
|
|
768
|
+
"highlights": [],
|
|
531
769
|
"derive": {
|
|
532
770
|
"mode": "anchor",
|
|
533
771
|
"translate": true,
|
|
@@ -559,6 +797,7 @@
|
|
|
559
797
|
}
|
|
560
798
|
],
|
|
561
799
|
"keep": [],
|
|
800
|
+
"highlights": [],
|
|
562
801
|
"derive": {
|
|
563
802
|
"mode": "anchor",
|
|
564
803
|
"translate": true,
|
|
@@ -584,6 +823,7 @@
|
|
|
584
823
|
}
|
|
585
824
|
],
|
|
586
825
|
"keep": [],
|
|
826
|
+
"highlights": [],
|
|
587
827
|
"derive": {
|
|
588
828
|
"mode": "anchor",
|
|
589
829
|
"translate": true,
|
|
@@ -609,6 +849,7 @@
|
|
|
609
849
|
}
|
|
610
850
|
],
|
|
611
851
|
"keep": [],
|
|
852
|
+
"highlights": [],
|
|
612
853
|
"derive": {
|
|
613
854
|
"mode": "anchor",
|
|
614
855
|
"translate": true,
|
|
@@ -642,6 +883,7 @@
|
|
|
642
883
|
}
|
|
643
884
|
],
|
|
644
885
|
"keep": [],
|
|
886
|
+
"highlights": [],
|
|
645
887
|
"derive": {
|
|
646
888
|
"mode": "anchor",
|
|
647
889
|
"translate": true,
|
|
@@ -667,6 +909,7 @@
|
|
|
667
909
|
}
|
|
668
910
|
],
|
|
669
911
|
"keep": [],
|
|
912
|
+
"highlights": [],
|
|
670
913
|
"derive": {
|
|
671
914
|
"mode": "anchor",
|
|
672
915
|
"translate": true,
|
|
@@ -694,6 +937,7 @@
|
|
|
694
937
|
}
|
|
695
938
|
],
|
|
696
939
|
"keep": [],
|
|
940
|
+
"highlights": [],
|
|
697
941
|
"derive": {
|
|
698
942
|
"mode": "anchor",
|
|
699
943
|
"translate": true,
|
|
@@ -720,6 +964,7 @@
|
|
|
720
964
|
}
|
|
721
965
|
],
|
|
722
966
|
"keep": [],
|
|
967
|
+
"highlights": [],
|
|
723
968
|
"derive": {
|
|
724
969
|
"mode": "anchor",
|
|
725
970
|
"translate": true,
|
|
@@ -762,6 +1007,19 @@
|
|
|
762
1007
|
"Volunteers water the beds every second morning.",
|
|
763
1008
|
"Children from the school help on the first Saturday."
|
|
764
1009
|
],
|
|
1010
|
+
"highlights": [
|
|
1011
|
+
{
|
|
1012
|
+
"text": "Anyone may take vegetables home once the harvest begins.",
|
|
1013
|
+
"words": 9,
|
|
1014
|
+
"label": "On the harvest",
|
|
1015
|
+
"inContent": true
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
"text": "Children from the school will help on the first Saturday.",
|
|
1019
|
+
"words": 10,
|
|
1020
|
+
"inContent": true
|
|
1021
|
+
}
|
|
1022
|
+
],
|
|
765
1023
|
"derive": {
|
|
766
1024
|
"mode": "anchor",
|
|
767
1025
|
"translate": false,
|
|
@@ -790,6 +1048,7 @@
|
|
|
790
1048
|
}
|
|
791
1049
|
],
|
|
792
1050
|
"keep": [],
|
|
1051
|
+
"highlights": [],
|
|
793
1052
|
"derive": {
|
|
794
1053
|
"mode": "anchor",
|
|
795
1054
|
"translate": true,
|
package/fixtures/feed-rss.xml
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
<obs:summary>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.</obs:summary>
|
|
13
13
|
<obs:summary length="short">The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead.</obs:summary>
|
|
14
14
|
<obs:keep>Anyone may take vegetables home once the harvest begins.</obs:keep>
|
|
15
|
+
<obs:highlight label="On the harvest">Anyone may take vegetables home once the harvest begins.</obs:highlight>
|
|
16
|
+
<obs:highlight>Children from the school will help on the first Saturday.</obs:highlight>
|
|
15
17
|
<obs:derive translate="no">anchor</obs:derive>
|
|
16
18
|
<obs:attribution>From the Garden Club newsletter</obs:attribution>
|
|
17
19
|
</item>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head prefix="obs: https://openbriefingstandard.org/ns#">
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Garden Club Notes: highlight-label-orphan</title>
|
|
6
|
+
<link rel="canonical" href="https://example.test/highlight-label-orphan/">
|
|
7
|
+
<meta property="obs:modified" content="2026-11-08T14:00:00Z">
|
|
8
|
+
<meta property="obs:summary" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.">
|
|
9
|
+
<meta property="obs:highlight:label" content="A label with nothing before it">
|
|
10
|
+
<meta property="obs:highlight" content="Anyone may take vegetables home once the harvest begins.">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<article><h1>Garden Club Notes</h1><p>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month.</p></article>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head prefix="obs: https://openbriefingstandard.org/ns#">
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Garden Club Notes: highlight-limits</title>
|
|
6
|
+
<link rel="canonical" href="https://example.test/highlight-limits/">
|
|
7
|
+
<meta property="obs:modified" content="2026-11-08T14:00:00Z">
|
|
8
|
+
<meta property="obs:summary" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.">
|
|
9
|
+
<meta property="obs:highlight" content="The garden club met on Tuesday to plan the spring planting.">
|
|
10
|
+
<meta property="obs:highlight" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools.">
|
|
11
|
+
<meta property="obs:highlight" content="Most of the seeds came from last year's harvest.">
|
|
12
|
+
<meta property="obs:highlight" content="The committee chose tomatoes, beans and three kinds of squash.">
|
|
13
|
+
<meta property="obs:highlight" content="Members who cannot dig offered to label the rows instead.">
|
|
14
|
+
<meta property="obs:highlight" content="A sixth highlight that is dropped.">
|
|
15
|
+
<meta property="obs:highlight" content="A seventh highlight that is dropped.">
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<article><h1>Garden Club Notes</h1><p>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month.</p></article>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head prefix="obs: https://openbriefingstandard.org/ns#">
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Garden Club Notes: highlight-not-in-text</title>
|
|
6
|
+
<link rel="canonical" href="https://example.test/highlight-not-in-text/">
|
|
7
|
+
<meta property="obs:modified" content="2026-11-08T14:00:00Z">
|
|
8
|
+
<meta property="obs:summary" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.">
|
|
9
|
+
<meta property="obs:highlight" content="Most of the seeds came from last year’s harvest.">
|
|
10
|
+
<meta property="obs:highlight" content="The fence — a new one — keeps the rabbits out.">
|
|
11
|
+
<meta property="obs:highlight" content="The club bought a tractor for the heavy digging.">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<article><h1>Garden Club Notes</h1><p>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month.</p></article>
|
|
15
|
+
<p>The fence - a new one - keeps the rabbits out.</p>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head prefix="obs: https://openbriefingstandard.org/ns#">
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Garden Club Notes: highlight-reserved</title>
|
|
6
|
+
<link rel="canonical" href="https://example.test/highlight-reserved/">
|
|
7
|
+
<meta property="obs:modified" content="2026-11-08T14:00:00Z">
|
|
8
|
+
<meta property="obs:summary" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.">
|
|
9
|
+
<meta property="obs:highlight" content="Anyone may take vegetables home once the harvest begins.">
|
|
10
|
+
<meta property="obs:highlight:start" content="00:01:12">
|
|
11
|
+
<meta property="obs:highlight:end" content="00:01:30">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<article><h1>Garden Club Notes</h1><p>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month.</p></article>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head prefix="obs: https://openbriefingstandard.org/ns#">
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Garden Club Notes: highlights</title>
|
|
6
|
+
<link rel="canonical" href="https://example.test/highlights/">
|
|
7
|
+
<meta property="obs:modified" content="2026-11-08T14:00:00Z">
|
|
8
|
+
<meta property="obs:summary" content="The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month. The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone.">
|
|
9
|
+
<meta property="obs:highlight" content="Anyone may take vegetables home once the harvest begins.">
|
|
10
|
+
<meta property="obs:highlight:label" content="On the harvest">
|
|
11
|
+
<meta property="obs:highlight" content="Volunteers agreed to water the beds every second morning.">
|
|
12
|
+
<meta property="obs:highlight" content="A new fence will keep the rabbits away from the lettuce.">
|
|
13
|
+
<meta property="obs:highlight:label" content="On rabbits">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<article><h1>Garden Club Notes</h1><p>The garden club met on Tuesday to plan the spring planting. Volunteers agreed to water the beds every second morning. Most of the seeds came from last year's harvest. The committee chose tomatoes, beans and three kinds of squash. A new fence will keep the rabbits away from the lettuce. Members who cannot dig offered to label the rows instead. The treasurer reported enough money for compost and tools. Children from the school will help on the first Saturday. Anyone may take vegetables home once the harvest begins. The next meeting is set for the first week of the month.</p></article>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/fixtures/valid-full.html
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
<meta property="obs:keep" content="Anyone may take vegetables home once the harvest begins.">
|
|
14
14
|
<meta property="obs:keep" content="Volunteers water the beds every second morning.">
|
|
15
15
|
<meta property="obs:keep" content="Children from the school help on the first Saturday.">
|
|
16
|
+
<meta property="obs:highlight" content="Anyone may take vegetables home once the harvest begins.">
|
|
17
|
+
<meta property="obs:highlight:label" content="On the harvest">
|
|
18
|
+
<meta property="obs:highlight" content="Children from the school will help on the first Saturday.">
|
|
16
19
|
<meta property="obs:attribution" content="From the Garden Club newsletter">
|
|
17
20
|
<meta property="obs:derive" content="anchor">
|
|
18
21
|
<meta property="obs:derive:translate" content="no">
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbriefing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Open Briefing Standard (OBS): the reference parser and checker for publishers' own summaries.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"open-briefing-standard",
|
|
7
|
+
"obs",
|
|
8
|
+
"summary",
|
|
9
|
+
"metadata",
|
|
10
|
+
"opengraph",
|
|
11
|
+
"publishing"
|
|
12
|
+
],
|
|
6
13
|
"homepage": "https://openbriefingstandard.org",
|
|
7
14
|
"repository": {
|
|
8
15
|
"type": "git",
|
|
@@ -15,12 +22,23 @@
|
|
|
15
22
|
"main": "./dist/index.js",
|
|
16
23
|
"types": "./dist/index.d.ts",
|
|
17
24
|
"exports": {
|
|
18
|
-
".": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
19
29
|
"./fixtures/*": "./fixtures/*"
|
|
20
30
|
},
|
|
21
|
-
"bin": {
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
"bin": {
|
|
32
|
+
"openbriefing": "dist/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"fixtures",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
24
42
|
"scripts": {
|
|
25
43
|
"build": "tsc -p tsconfig.build.json",
|
|
26
44
|
"typecheck": "tsc --noEmit",
|