@concordance-wiki/nlp 0.1.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.
Files changed (121) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +7 -0
  3. package/dist/dictionary/build.d.ts +19 -0
  4. package/dist/dictionary/build.d.ts.map +1 -0
  5. package/dist/dictionary/build.js +72 -0
  6. package/dist/dictionary/build.js.map +1 -0
  7. package/dist/dictionary/stopwords.d.ts +16 -0
  8. package/dist/dictionary/stopwords.d.ts.map +1 -0
  9. package/dist/dictionary/stopwords.js +27 -0
  10. package/dist/dictionary/stopwords.js.map +1 -0
  11. package/dist/dictionary/types.d.ts +34 -0
  12. package/dist/dictionary/types.d.ts.map +1 -0
  13. package/dist/dictionary/types.js +2 -0
  14. package/dist/dictionary/types.js.map +1 -0
  15. package/dist/index.d.ts +26 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +24 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/keywords/confidence.d.ts +56 -0
  20. package/dist/keywords/confidence.d.ts.map +1 -0
  21. package/dist/keywords/confidence.js +59 -0
  22. package/dist/keywords/confidence.js.map +1 -0
  23. package/dist/keywords/defined.d.ts +26 -0
  24. package/dist/keywords/defined.d.ts.map +1 -0
  25. package/dist/keywords/defined.js +33 -0
  26. package/dist/keywords/defined.js.map +1 -0
  27. package/dist/keywords/findings.d.ts +13 -0
  28. package/dist/keywords/findings.d.ts.map +1 -0
  29. package/dist/keywords/findings.js +35 -0
  30. package/dist/keywords/findings.js.map +1 -0
  31. package/dist/keywords/ngrams.d.ts +47 -0
  32. package/dist/keywords/ngrams.d.ts.map +1 -0
  33. package/dist/keywords/ngrams.js +64 -0
  34. package/dist/keywords/ngrams.js.map +1 -0
  35. package/dist/keywords/options.d.ts +22 -0
  36. package/dist/keywords/options.d.ts.map +1 -0
  37. package/dist/keywords/options.js +22 -0
  38. package/dist/keywords/options.js.map +1 -0
  39. package/dist/keywords/publish.d.ts +50 -0
  40. package/dist/keywords/publish.d.ts.map +1 -0
  41. package/dist/keywords/publish.js +96 -0
  42. package/dist/keywords/publish.js.map +1 -0
  43. package/dist/keywords/score.d.ts +60 -0
  44. package/dist/keywords/score.d.ts.map +1 -0
  45. package/dist/keywords/score.js +0 -0
  46. package/dist/keywords/score.js.map +1 -0
  47. package/dist/keywords/similar.d.ts +19 -0
  48. package/dist/keywords/similar.d.ts.map +1 -0
  49. package/dist/keywords/similar.js +52 -0
  50. package/dist/keywords/similar.js.map +1 -0
  51. package/dist/locale/load-pack.d.ts +11 -0
  52. package/dist/locale/load-pack.d.ts.map +1 -0
  53. package/dist/locale/load-pack.js +82 -0
  54. package/dist/locale/load-pack.js.map +1 -0
  55. package/dist/locale/normalize.d.ts +3 -0
  56. package/dist/locale/normalize.d.ts.map +1 -0
  57. package/dist/locale/normalize.js +14 -0
  58. package/dist/locale/normalize.js.map +1 -0
  59. package/dist/locale/pack.d.ts +34 -0
  60. package/dist/locale/pack.d.ts.map +1 -0
  61. package/dist/locale/pack.js +2 -0
  62. package/dist/locale/pack.js.map +1 -0
  63. package/dist/locale/registry.d.ts +16 -0
  64. package/dist/locale/registry.d.ts.map +1 -0
  65. package/dist/locale/registry.js +35 -0
  66. package/dist/locale/registry.js.map +1 -0
  67. package/dist/locale/stopwords.d.ts +3 -0
  68. package/dist/locale/stopwords.d.ts.map +1 -0
  69. package/dist/locale/stopwords.js +17 -0
  70. package/dist/locale/stopwords.js.map +1 -0
  71. package/dist/locale/suffixes.d.ts +7 -0
  72. package/dist/locale/suffixes.d.ts.map +1 -0
  73. package/dist/locale/suffixes.js +21 -0
  74. package/dist/locale/suffixes.js.map +1 -0
  75. package/dist/locale/tag.d.ts +4 -0
  76. package/dist/locale/tag.d.ts.map +1 -0
  77. package/dist/locale/tag.js +5 -0
  78. package/dist/locale/tag.js.map +1 -0
  79. package/dist/scan/automaton.d.ts +46 -0
  80. package/dist/scan/automaton.d.ts.map +1 -0
  81. package/dist/scan/automaton.js +92 -0
  82. package/dist/scan/automaton.js.map +1 -0
  83. package/dist/scan/context.d.ts +20 -0
  84. package/dist/scan/context.d.ts.map +1 -0
  85. package/dist/scan/context.js +26 -0
  86. package/dist/scan/context.js.map +1 -0
  87. package/dist/scan/occurrences.d.ts +64 -0
  88. package/dist/scan/occurrences.d.ts.map +1 -0
  89. package/dist/scan/occurrences.js +109 -0
  90. package/dist/scan/occurrences.js.map +1 -0
  91. package/dist/scan/tokens.d.ts +12 -0
  92. package/dist/scan/tokens.d.ts.map +1 -0
  93. package/dist/scan/tokens.js +34 -0
  94. package/dist/scan/tokens.js.map +1 -0
  95. package/dist/text/boundaries.d.ts +12 -0
  96. package/dist/text/boundaries.d.ts.map +1 -0
  97. package/dist/text/boundaries.js +13 -0
  98. package/dist/text/boundaries.js.map +1 -0
  99. package/dist/text/comparison-form.d.ts +9 -0
  100. package/dist/text/comparison-form.d.ts.map +1 -0
  101. package/dist/text/comparison-form.js +23 -0
  102. package/dist/text/comparison-form.js.map +1 -0
  103. package/dist/text/context.d.ts +6 -0
  104. package/dist/text/context.d.ts.map +1 -0
  105. package/dist/text/context.js +12 -0
  106. package/dist/text/context.js.map +1 -0
  107. package/dist/text/search-tokens.d.ts +10 -0
  108. package/dist/text/search-tokens.d.ts.map +1 -0
  109. package/dist/text/search-tokens.js +29 -0
  110. package/dist/text/search-tokens.js.map +1 -0
  111. package/dist/text/singular.d.ts +7 -0
  112. package/dist/text/singular.d.ts.map +1 -0
  113. package/dist/text/singular.js +12 -0
  114. package/dist/text/singular.js.map +1 -0
  115. package/locales/en/pack.yaml +18 -0
  116. package/locales/en/stopwords.txt +536 -0
  117. package/locales/en/suffixes.txt +7 -0
  118. package/locales/fr/pack.yaml +15 -0
  119. package/locales/fr/stopwords.txt +1021 -0
  120. package/locales/fr/suffixes.txt +12 -0
  121. package/package.json +44 -0
@@ -0,0 +1,64 @@
1
+ import { occurrenceContext } from "../scan/context.js";
2
+ import { tokenize } from "../scan/tokens.js";
3
+ const contextWidth = 160;
4
+ const digitsOnly = /^\p{N}+$/u;
5
+ /** The comparison form of a term as the n-grams carry it: its words, tokenised like the texts. */
6
+ export function keywordForm(text, pack) {
7
+ return tokenize(text, pack)
8
+ .map((token) => token.word)
9
+ .join(" ");
10
+ }
11
+ /** The comparison forms of a list of terms. */
12
+ export function keywordForms(forms, pack) {
13
+ return new Set([...forms].map((form) => keywordForm(form, pack)));
14
+ }
15
+ /** Whether the words so far form a candidate: enough of them, the last not a stopword, not digits alone, long enough. */
16
+ function isCandidate(words, last, rules) {
17
+ if (words.length < rules.minWords || rules.stopwords.has(last))
18
+ return false;
19
+ if (words.every((word) => digitsOnly.test(word)))
20
+ return false;
21
+ return words.join(" ").length >= rules.minLength;
22
+ }
23
+ /** The n-grams of one unit starting at each token that is not a stopword, in text order. */
24
+ function ngramsOf(unit, tokens, rules) {
25
+ const occurrences = [];
26
+ for (const [start, first] of tokens.entries()) {
27
+ // A candidate never starts with a stopword, whatever its length.
28
+ if (rules.stopwords.has(first.word))
29
+ continue;
30
+ const words = [];
31
+ for (const last of tokens.slice(start, start + rules.maxWords)) {
32
+ words.push(last.word);
33
+ if (!isCandidate(words, last.word, rules))
34
+ continue;
35
+ occurrences.push({
36
+ key: words.join(" "),
37
+ surface: unit.text.slice(first.start, last.end),
38
+ ...(unit.source === undefined ? {} : { source: unit.source }),
39
+ path: unit.path,
40
+ line: unit.line,
41
+ position: first.start,
42
+ context: occurrenceContext(unit, first.start, last.end, contextWidth),
43
+ });
44
+ }
45
+ }
46
+ return occurrences;
47
+ }
48
+ /**
49
+ * Every n-gram of `minWords` to `maxWords` words in the units, in unit then text order,
50
+ * except those starting or ending with a stopword, those made only of digits and those
51
+ * whose key is shorter than `minLength`. Words are the tokens of the text: normalised by
52
+ * the pack and singularised, so that "Build summaries" and "build summary"
53
+ * share a key while each keeps its surface form.
54
+ */
55
+ export function extractNgrams(units, pack, options) {
56
+ const rules = {
57
+ minWords: options.minWords ?? 1,
58
+ maxWords: options.maxWords,
59
+ minLength: options.minLength,
60
+ stopwords: keywordForms(options.stopwords ?? pack.stopwords, pack),
61
+ };
62
+ return units.flatMap((unit) => ngramsOf(unit, tokenize(unit.text, pack), rules));
63
+ }
64
+ //# sourceMappingURL=ngrams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngrams.js","sourceRoot":"","sources":["../../src/keywords/ngrams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Config } from "@concordance-wiki/core";
2
+ /** The part of `concordance.lock.yaml` the discovery reads. */
3
+ export interface KeywordLock {
4
+ rejected_terms?: readonly string[];
5
+ }
6
+ /** Every knob of the discovery, resolved from the configuration and the lock. */
7
+ export interface KeywordOptions {
8
+ minWords: number;
9
+ maxWords: number;
10
+ /** Below this length of the normalised expression, an n-gram is noise; not configurable. */
11
+ minLength: number;
12
+ minOccurrences: number;
13
+ minDocuments: number;
14
+ /** `inference.candidate_score`: from this score a candidate yields `W-TERM-UNDEFINED`. */
15
+ minScore: number;
16
+ /** The lock's `rejected_terms`, as written. */
17
+ rejected: ReadonlySet<string>;
18
+ }
19
+ export declare const keywordDefaults: Omit<KeywordOptions, "rejected">;
20
+ /** `inference.ngrams`, `inference.candidate_score` and the lock's `rejected_terms`, with their defaults. */
21
+ export declare function keywordOptions(config: Config, lock?: KeywordLock): KeywordOptions;
22
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/keywords/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,eAAO,MAAM,eAAe,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAO5D,CAAC;AAEF,4GAA4G;AAC5G,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,cAAc,CAWjF"}
@@ -0,0 +1,22 @@
1
+ export const keywordDefaults = {
2
+ minWords: 1,
3
+ maxWords: 4,
4
+ minLength: 3,
5
+ minOccurrences: 3,
6
+ minDocuments: 2,
7
+ minScore: 4,
8
+ };
9
+ /** `inference.ngrams`, `inference.candidate_score` and the lock's `rejected_terms`, with their defaults. */
10
+ export function keywordOptions(config, lock) {
11
+ const ngrams = config.inference?.ngrams ?? {};
12
+ return {
13
+ minWords: ngrams.min ?? keywordDefaults.minWords,
14
+ maxWords: ngrams.max ?? keywordDefaults.maxWords,
15
+ minLength: keywordDefaults.minLength,
16
+ minOccurrences: ngrams.min_occurrences ?? keywordDefaults.minOccurrences,
17
+ minDocuments: ngrams.min_documents ?? keywordDefaults.minDocuments,
18
+ minScore: config.inference?.candidate_score ?? keywordDefaults.minScore,
19
+ rejected: new Set(lock?.rejected_terms ?? []),
20
+ };
21
+ }
22
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/keywords/options.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,eAAe,GAAqC;IAC/D,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,CAAC;IACZ,cAAc,EAAE,CAAC;IACjB,YAAY,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF,4GAA4G;AAC5G,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,IAAkB;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE,CAAC;IAC9C,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,QAAQ;QAChD,QAAQ,EAAE,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,QAAQ;QAChD,SAAS,EAAE,eAAe,CAAC,SAAS;QACpC,cAAc,EAAE,MAAM,CAAC,eAAe,IAAI,eAAe,CAAC,cAAc;QACxE,YAAY,EAAE,MAAM,CAAC,aAAa,IAAI,eAAe,CAAC,YAAY;QAClE,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe,IAAI,eAAe,CAAC,QAAQ;QACvE,QAAQ,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,cAAc,IAAI,EAAE,CAAC;KAC9C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { type Config, type Entity } from "@concordance-wiki/core";
2
+ import type { KeywordCandidate, KeywordMention } from "./score.js";
3
+ /** A candidate above the publication threshold: the page the site generates for it. */
4
+ export interface KeywordPage {
5
+ /** `keywords/<slug of the key>`, the stable address of the page. */
6
+ id: string;
7
+ key: string;
8
+ display: string;
9
+ occurrences: number;
10
+ documents: number;
11
+ score: number;
12
+ confidence: number;
13
+ mentions: KeywordMention[];
14
+ }
15
+ export interface KeywordPublicationOptions {
16
+ minOccurrences: number;
17
+ /** Distinct files the expression must appear in. */
18
+ minFiles: number;
19
+ /** Confidence, in [0, 1], from which an expression at the threshold gets a page. */
20
+ minConfidence: number;
21
+ }
22
+ export interface PublishedKeywords {
23
+ /** Sorted by identifier. */
24
+ published: KeywordPage[];
25
+ /** Under the threshold, best score first; searchable, but without a page. */
26
+ discarded: KeywordCandidate[];
27
+ /** At the threshold but under the confidence, best score first: suspected noise, searchable, without a page. */
28
+ withheld: KeywordCandidate[];
29
+ }
30
+ export interface KeywordEntitiesOptions {
31
+ locale: Entity["locale"];
32
+ }
33
+ export declare const keywordPublicationDefaults: KeywordPublicationOptions;
34
+ /** The type of a keyword page: a term nobody has defined yet. */
35
+ export declare const KEYWORD_TYPE = "term";
36
+ /** `inference.keyword_pages`, with its defaults: three occurrences in two distinct files, a confidence of one half. */
37
+ export declare function keywordPublicationOptions(config: Config): KeywordPublicationOptions;
38
+ /**
39
+ * Splits the candidates into the pages to generate, the expressions the threshold
40
+ * discards and those the confidence withholds. Two keys with the same slug would share an
41
+ * address: the best-scored keeps the plain identifier and the next ones take a numeric
42
+ * suffix, so that no page is lost.
43
+ */
44
+ export declare function publishKeywords(candidates: readonly KeywordCandidate[], options: KeywordPublicationOptions): PublishedKeywords;
45
+ /**
46
+ * One entity per page, marked `keyword: true`, of the type of a term, located on the first
47
+ * mention of its expression, with its counts, score and confidence as attributes; in page order.
48
+ */
49
+ export declare function keywordEntities(pages: readonly KeywordPage[], options: KeywordEntitiesOptions): Entity[];
50
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/keywords/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,KAAK,MAAM,EAAqB,MAAM,wBAAwB,CAAC;AAE9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnE,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,6EAA6E;IAC7E,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,gHAAgH;IAChH,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;CAC1B;AAED,eAAO,MAAM,0BAA0B,EAAE,yBAIxC,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,YAAY,SAAS,CAAC;AAanC,uHAAuH;AACvH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,yBAAyB,CAOnF;AASD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,gBAAgB,EAAE,EACvC,OAAO,EAAE,yBAAyB,GACjC,iBAAiB,CA6BnB;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,EAAE,sBAAsB,GAC9B,MAAM,EAAE,CAmBV"}
@@ -0,0 +1,96 @@
1
+ import { slugify } from "@concordance-wiki/core";
2
+ export const keywordPublicationDefaults = {
3
+ minOccurrences: 3,
4
+ minFiles: 2,
5
+ minConfidence: 0.5,
6
+ };
7
+ /** The type of a keyword page: a term nobody has defined yet. */
8
+ export const KEYWORD_TYPE = "term";
9
+ /** The status the profile gives an entity that declares none. */
10
+ const KEYWORD_STATUS = "valid";
11
+ function byCodeUnit(a, b) {
12
+ return Number(a > b) - Number(a < b);
13
+ }
14
+ function compareByScore(a, b) {
15
+ return b.score - a.score || byCodeUnit(a.key, b.key);
16
+ }
17
+ /** `inference.keyword_pages`, with its defaults: three occurrences in two distinct files, a confidence of one half. */
18
+ export function keywordPublicationOptions(config) {
19
+ const pages = config.inference?.keyword_pages ?? {};
20
+ return {
21
+ minOccurrences: pages.min_occurrences ?? keywordPublicationDefaults.minOccurrences,
22
+ minFiles: pages.min_files ?? keywordPublicationDefaults.minFiles,
23
+ minConfidence: pages.min_confidence ?? keywordPublicationDefaults.minConfidence,
24
+ };
25
+ }
26
+ function reachesThreshold(candidate, options) {
27
+ return candidate.occurrences >= options.minOccurrences && candidate.documents >= options.minFiles;
28
+ }
29
+ /**
30
+ * Splits the candidates into the pages to generate, the expressions the threshold
31
+ * discards and those the confidence withholds. Two keys with the same slug would share an
32
+ * address: the best-scored keeps the plain identifier and the next ones take a numeric
33
+ * suffix, so that no page is lost.
34
+ */
35
+ export function publishKeywords(candidates, options) {
36
+ const published = [];
37
+ const discarded = [];
38
+ const withheld = [];
39
+ const taken = new Map();
40
+ for (const candidate of [...candidates].sort(compareByScore)) {
41
+ if (!reachesThreshold(candidate, options)) {
42
+ discarded.push(candidate);
43
+ continue;
44
+ }
45
+ if (candidate.confidence < options.minConfidence) {
46
+ withheld.push(candidate);
47
+ continue;
48
+ }
49
+ const base = `keywords/${slugify(candidate.key)}`;
50
+ const rank = (taken.get(base) ?? 0) + 1;
51
+ taken.set(base, rank);
52
+ published.push({
53
+ id: rank === 1 ? base : `${base}-${String(rank)}`,
54
+ key: candidate.key,
55
+ display: candidate.display,
56
+ occurrences: candidate.occurrences,
57
+ documents: candidate.documents,
58
+ score: candidate.score,
59
+ confidence: candidate.confidence,
60
+ mentions: candidate.mentions,
61
+ });
62
+ }
63
+ return { published: published.toSorted((a, b) => byCodeUnit(a.id, b.id)), discarded, withheld };
64
+ }
65
+ function locationOf(page) {
66
+ const [first] = page.mentions;
67
+ if (first === undefined) {
68
+ throw new Error(`keyword page ${page.id} has no mention to locate it`);
69
+ }
70
+ return { name: first.source ?? "", path: first.path, line: first.line };
71
+ }
72
+ /**
73
+ * One entity per page, marked `keyword: true`, of the type of a term, located on the first
74
+ * mention of its expression, with its counts, score and confidence as attributes; in page order.
75
+ */
76
+ export function keywordEntities(pages, options) {
77
+ return pages.map((page) => ({
78
+ id: page.id,
79
+ type: KEYWORD_TYPE,
80
+ title: page.display,
81
+ aliases: [],
82
+ locale: options.locale,
83
+ status: KEYWORD_STATUS,
84
+ type_origin: "default",
85
+ graph: "full",
86
+ attributes: {
87
+ documents: page.documents,
88
+ occurrences: page.occurrences,
89
+ score: page.score,
90
+ confidence: page.confidence,
91
+ },
92
+ source: locationOf(page),
93
+ keyword: true,
94
+ }));
95
+ }
96
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/keywords/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA+C,MAAM,wBAAwB,CAAC;AAsC9F,MAAM,CAAC,MAAM,0BAA0B,GAA8B;IACnE,cAAc,EAAE,CAAC;IACjB,QAAQ,EAAE,CAAC;IACX,aAAa,EAAE,GAAG;CACnB,CAAC;AAEF,iEAAiE;AACjE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC,iEAAiE;AACjE,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B,SAAS,UAAU,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,cAAc,CAAC,CAAmB,EAAE,CAAmB;IAC9D,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,uHAAuH;AACvH,MAAM,UAAU,yBAAyB,CAAC,MAAc;IACtD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,aAAa,IAAI,EAAE,CAAC;IACpD,OAAO;QACL,cAAc,EAAE,KAAK,CAAC,eAAe,IAAI,0BAA0B,CAAC,cAAc;QAClF,QAAQ,EAAE,KAAK,CAAC,SAAS,IAAI,0BAA0B,CAAC,QAAQ;QAChE,aAAa,EAAE,KAAK,CAAC,cAAc,IAAI,0BAA0B,CAAC,aAAa;KAChF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,SAA2B,EAC3B,OAAkC;IAElC,OAAO,SAAS,CAAC,WAAW,IAAI,OAAO,CAAC,cAAc,IAAI,SAAS,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC;AACpG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAuC,EACvC,OAAkC;IAElC,MAAM,SAAS,GAAkB,EAAE,CAAC;IACpC,MAAM,SAAS,GAAuB,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,SAAS,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7D,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAC1C,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;YACjD,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACxC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtB,SAAS,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE;YACjD,GAAG,EAAE,SAAS,CAAC,GAAG;YAClB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,QAAQ,EAAE,SAAS,CAAC,QAAQ;SAC7B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAClG,CAAC;AAED,SAAS,UAAU,CAAC,IAAiB;IACnC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,8BAA8B,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,KAA6B,EAC7B,OAA+B;IAE/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,IAAI,CAAC,OAAO;QACnB,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,cAAc;QACtB,WAAW,EAAE,SAAS;QACtB,KAAK,EAAE,MAAM;QACb,UAAU,EAAE;YACV,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B;QACD,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC;QACxB,OAAO,EAAE,IAAI;KACd,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { LanguagePack } from "../locale/pack.js";
2
+ import { type ConfidencePenalty, type ConfidenceSignals } from "./confidence.js";
3
+ import { type NgramOccurrence } from "./ngrams.js";
4
+ export interface KeywordMention {
5
+ source?: string;
6
+ path: string;
7
+ line: number;
8
+ position: number;
9
+ /** The span as written in the text, which the context holds. */
10
+ surface: string;
11
+ context: string;
12
+ }
13
+ export interface KeywordCandidate {
14
+ key: string;
15
+ /** The most frequent surface form, the first mention deciding a tie. */
16
+ display: string;
17
+ words: number;
18
+ occurrences: number;
19
+ /** Distinct files the expression appears in. */
20
+ documents: number;
21
+ /** C-value × IDF, rounded to four decimals. */
22
+ score: number;
23
+ /** The product of the confidence signals, in [0, 1], rounded to four decimals. */
24
+ confidence: number;
25
+ /** What the confidence was read from, the shares rounded to four decimals. */
26
+ signals: ConfidenceSignals;
27
+ /** The signals that lowered the confidence, in the order of the formula. */
28
+ penalties: ConfidencePenalty[];
29
+ /** Sorted by source, path, line, then position. */
30
+ mentions: KeywordMention[];
31
+ }
32
+ export interface ScoreCandidatesOptions {
33
+ pack: LanguagePack;
34
+ /** Comparison forms of the dictionary entries: an expression already defined is no candidate. */
35
+ dictionaryKeys: ReadonlySet<string>;
36
+ /** The lock's `rejected_terms`, as written. */
37
+ rejected: ReadonlySet<string>;
38
+ minOccurrences: number;
39
+ minDocuments: number;
40
+ /**
41
+ * The n-grams of the prominent texts, headings, written links and frontmatter, extracted
42
+ * like the units: an expression put forward there gains confidence. None by default.
43
+ */
44
+ prominent?: readonly NgramOccurrence[];
45
+ /** Share of the files beyond which an expression belongs to the language; 0.5 by default. */
46
+ maxSpread?: number;
47
+ }
48
+ /** Canonical order of mentions: source, path, line, position. */
49
+ export declare function compareMentions(a: Pick<NgramOccurrence, "source" | "path" | "line" | "position">, b: Pick<NgramOccurrence, "source" | "path" | "line" | "position">): number;
50
+ /**
51
+ * The expressions worth a page: the n-grams grouped by key, scored by C-value × IDF, kept
52
+ * from `minOccurrences` occurrences in `minDocuments` distinct files, best score first,
53
+ * each with its confidence of being a term of the subject rather than a word of the
54
+ * language, read in the same pass from the shape of its distribution. An expression
55
+ * defined in the dictionary or rejected in the lock is never a candidate, but still
56
+ * penalises the n-grams it contains, so that the words of a defined term do not surface
57
+ * on their own.
58
+ */
59
+ export declare function scoreCandidates(occurrences: readonly NgramOccurrence[], options: ScoreCandidatesOptions): KeywordCandidate[];
60
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/keywords/score.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,OAAO,EAAE,iBAAiB,CAAC;IAC3B,4EAA4E;IAC5E,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,mDAAmD;IACnD,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,YAAY,CAAC;IACnB,iGAAiG;IACjG,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,+CAA+C;IAC/C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iEAAiE;AACjE,wBAAgB,eAAe,CAC7B,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,EACjE,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,GAChE,MAAM,CAOR;AAkJD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,SAAS,eAAe,EAAE,EACvC,OAAO,EAAE,sBAAsB,GAC9B,gBAAgB,EAAE,CA6BpB"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/keywords/score.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** How many similar expressions a page offers as a lead. */
2
+ export declare const SIMILAR_EXPRESSIONS_LIMIT = 5;
3
+ /** What the comparison needs of an expression: its key, the words in comparison form joined by a space. */
4
+ export interface KeyedExpression {
5
+ key: string;
6
+ }
7
+ /**
8
+ * Whether two expressions have a similar form: one is a contiguous part of the other, or they
9
+ * share at least half of the distinct words of the longer one. Keys are compared word by word
10
+ * in their comparison form, so that a plural and its singular already read alike.
11
+ */
12
+ export declare function similarForm(a: string, b: string): boolean;
13
+ /**
14
+ * The expressions of a similar form to `key`, the closest first: by decreasing share of common
15
+ * words, then by key; at most `limit` of them, the expression itself never among them and each
16
+ * key once. The result depends on the set of candidates alone.
17
+ */
18
+ export declare function similarExpressions<T extends KeyedExpression>(key: string, candidates: readonly T[], limit?: number): T[];
19
+ //# sourceMappingURL=similar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar.d.ts","sourceRoot":"","sources":["../../src/keywords/similar.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,2GAA2G;AAC3G,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb;AAkBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAQzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAC1D,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,CAAC,EAAE,EACxB,KAAK,GAAE,MAAkC,GACxC,CAAC,EAAE,CAgBL"}
@@ -0,0 +1,52 @@
1
+ /** How many similar expressions a page offers as a lead. */
2
+ export const SIMILAR_EXPRESSIONS_LIMIT = 5;
3
+ function wordsOf(key) {
4
+ return key.split(" ");
5
+ }
6
+ /** Whether `part` is a contiguous run of the words of `whole`: a prefix, a suffix or an inner expression. */
7
+ function contains(whole, part) {
8
+ return whole.some((_, start) => part.every((word, index) => whole[start + index] === word));
9
+ }
10
+ /** The share of the distinct words of both expressions they have in common. */
11
+ function overlap(a, b) {
12
+ const words = new Set(a);
13
+ const shared = new Set(b.filter((word) => words.has(word))).size;
14
+ return shared / new Set([...a, ...b]).size;
15
+ }
16
+ /**
17
+ * Whether two expressions have a similar form: one is a contiguous part of the other, or they
18
+ * share at least half of the distinct words of the longer one. Keys are compared word by word
19
+ * in their comparison form, so that a plural and its singular already read alike.
20
+ */
21
+ export function similarForm(a, b) {
22
+ const wordsA = wordsOf(a);
23
+ const wordsB = wordsOf(b);
24
+ if (contains(wordsA, wordsB) || contains(wordsB, wordsA))
25
+ return true;
26
+ const setA = new Set(wordsA);
27
+ const setB = new Set(wordsB);
28
+ const shared = [...setB].filter((word) => setA.has(word)).length;
29
+ return 2 * shared >= Math.max(setA.size, setB.size);
30
+ }
31
+ /**
32
+ * The expressions of a similar form to `key`, the closest first: by decreasing share of common
33
+ * words, then by key; at most `limit` of them, the expression itself never among them and each
34
+ * key once. The result depends on the set of candidates alone.
35
+ */
36
+ export function similarExpressions(key, candidates, limit = SIMILAR_EXPRESSIONS_LIMIT) {
37
+ const words = wordsOf(key);
38
+ const seen = new Set([key]);
39
+ const similar = [];
40
+ for (const candidate of candidates) {
41
+ if (seen.has(candidate.key) || !similarForm(key, candidate.key))
42
+ continue;
43
+ seen.add(candidate.key);
44
+ similar.push({ candidate, overlap: overlap(words, wordsOf(candidate.key)) });
45
+ }
46
+ return (similar
47
+ // Keys are distinct here, so the tie-break never meets two equal ones.
48
+ .toSorted((a, b) => b.overlap - a.overlap || (a.candidate.key < b.candidate.key ? -1 : 1))
49
+ .slice(0, limit)
50
+ .map((item) => item.candidate));
51
+ }
52
+ //# sourceMappingURL=similar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar.js","sourceRoot":"","sources":["../../src/keywords/similar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { LanguagePack } from "./pack.js";
2
+ export declare class LanguagePackError extends Error {
3
+ constructor(directory: string, detail: string);
4
+ }
5
+ /**
6
+ * Reads a language pack from a folder holding `pack.yaml` and `stopwords.txt`, and
7
+ * `suffixes.txt` when the pack lists inflected-form suffixes. The core packs and the ones
8
+ * shipped by plugins are read the same way.
9
+ */
10
+ export declare function loadLanguagePack(directory: URL): LanguagePack;
11
+ //# sourceMappingURL=load-pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-pack.d.ts","sourceRoot":"","sources":["../../src/locale/load-pack.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,WAAW,CAAC;AAahE,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI9C;AAoCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,GAAG,YAAY,CA4B7D"}
@@ -0,0 +1,82 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { describeSchemaError, formatIssue, readSchema } from "@concordance-wiki/core";
3
+ import { Ajv2020 } from "ajv/dist/2020.js";
4
+ import { parse } from "yaml";
5
+ import { normalizer } from "./normalize.js";
6
+ import { canonicalLocale } from "./tag.js";
7
+ import { loadStopwords } from "./stopwords.js";
8
+ import { loadSuffixes } from "./suffixes.js";
9
+ export class LanguagePackError extends Error {
10
+ constructor(directory, detail) {
11
+ super(`language pack at ${directory}: ${detail}`);
12
+ this.name = "LanguagePackError";
13
+ }
14
+ }
15
+ function readDocument(directory) {
16
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
17
+ const validate = ajv.compile(readSchema("language-pack"));
18
+ const document = parse(readFileSync(new URL("pack.yaml", directory), "utf8"));
19
+ if (!validate(document)) {
20
+ // The validator fills `errors` whenever it returns false.
21
+ const detail = validate.errors
22
+ .map((error) => formatIssue(describeSchemaError(error, document), "pack.yaml"))
23
+ .join("; ");
24
+ throw new LanguagePackError(directory.pathname, detail);
25
+ }
26
+ // Validated against the schema the type mirrors.
27
+ return document;
28
+ }
29
+ function canonical(tag, directory) {
30
+ try {
31
+ return canonicalLocale(tag);
32
+ }
33
+ catch {
34
+ throw new LanguagePackError(directory.pathname, `"${tag}" is not a valid language tag`);
35
+ }
36
+ }
37
+ /** The text of an optional file of the pack: an absent file reads as empty, any other failure is raised. */
38
+ function optionalText(url) {
39
+ try {
40
+ return readFileSync(url, "utf8");
41
+ }
42
+ catch (error) {
43
+ // A node file system error carries its code; any other value has none and is rethrown.
44
+ if (error.code === "ENOENT")
45
+ return "";
46
+ throw error;
47
+ }
48
+ }
49
+ /**
50
+ * Reads a language pack from a folder holding `pack.yaml` and `stopwords.txt`, and
51
+ * `suffixes.txt` when the pack lists inflected-form suffixes. The core packs and the ones
52
+ * shipped by plugins are read the same way.
53
+ */
54
+ export function loadLanguagePack(directory) {
55
+ const base = directory.href.endsWith("/") ? directory : new URL(`${directory.href}/`);
56
+ const document = readDocument(base);
57
+ const locale = canonical(document.locale, base);
58
+ const collator = new Intl.Collator(locale, document.collation);
59
+ const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
60
+ const normalize = normalizer(document.apostrophes ?? []);
61
+ const plural = document.plural.map((rule) => ({
62
+ ending: rule.ending,
63
+ singular: rule.singular,
64
+ minLength: rule.min_length ?? rule.ending.length + 1,
65
+ }));
66
+ return {
67
+ locale,
68
+ language: document.language,
69
+ normalize,
70
+ segment: (text) => [...segmenter.segment(text)].map((part) => ({
71
+ text: part.segment,
72
+ index: part.index,
73
+ isWordLike: part.isWordLike === true,
74
+ })),
75
+ stopwords: new Set(loadStopwords(readFileSync(new URL("stopwords.txt", base), "utf8"))),
76
+ suffixes: new Set(loadSuffixes(optionalText(new URL("suffixes.txt", base)), normalize)),
77
+ plural,
78
+ collator,
79
+ compare: (a, b) => collator.compare(a, b),
80
+ };
81
+ }
82
+ //# sourceMappingURL=load-pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-pack.js","sourceRoot":"","sources":["../../src/locale/load-pack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Builds the normaliser of a locale from the apostrophe characters its pack unifies. */
2
+ export declare function normalizer(apostrophes: readonly string[]): (text: string) => string;
3
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/locale/normalize.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAUnF"}
@@ -0,0 +1,14 @@
1
+ const combiningMarks = /\p{M}+/gu;
2
+ const whitespace = /\s+/g;
3
+ /** Builds the normaliser of a locale from the apostrophe characters its pack unifies. */
4
+ export function normalizer(apostrophes) {
5
+ const variants = new Set(apostrophes);
6
+ return (text) => text
7
+ .normalize("NFD")
8
+ .replace(combiningMarks, "")
9
+ .toLowerCase()
10
+ .replace(/./gu, (character) => (variants.has(character) ? "'" : character))
11
+ .replace(whitespace, " ")
12
+ .trim();
13
+ }
14
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/locale/normalize.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,UAAU,CAAC;AAClC,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,yFAAyF;AACzF,MAAM,UAAU,UAAU,CAAC,WAA8B;IACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,EAAE,EAAE,CACd,IAAI;SACD,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,WAAW,EAAE;SACb,OAAO,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;SAC1E,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;SACxB,IAAI,EAAE,CAAC;AACd,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { Locale } from "@concordance-wiki/core";
2
+ /** A suffix rule bringing a plural form back to its singular. */
3
+ export interface PluralRule {
4
+ ending: string;
5
+ singular: string;
6
+ minLength: number;
7
+ }
8
+ /** A word of a text as cut by the locale's segmenter. */
9
+ export interface Word {
10
+ text: string;
11
+ index: number;
12
+ /** False for punctuation and spaces. */
13
+ isWordLike: boolean;
14
+ }
15
+ /** What the engine knows about a language. Built from a `pack.yaml` and a stopword list, never from code. */
16
+ export interface LanguagePack {
17
+ /** Canonical BCP 47 tag. */
18
+ locale: Locale;
19
+ /** Name of the language, in that language. */
20
+ language: string;
21
+ /** Lower-cases, strips combining marks, unifies the apostrophes and collapses whitespace. */
22
+ normalize: (text: string) => string;
23
+ /** Cuts a text into words with the locale's rules (Unicode segmentation). */
24
+ segment: (text: string) => Word[];
25
+ stopwords: ReadonlySet<string>;
26
+ /** Endings of the inflected forms of the language, in comparison form; empty when the pack lists none. */
27
+ suffixes: ReadonlySet<string>;
28
+ plural: readonly PluralRule[];
29
+ /** Locale-aware, accent-insensitive and numeric, for every alphabetical index. */
30
+ collator: Intl.Collator;
31
+ /** Usable directly as a sort comparator. */
32
+ compare: (a: string, b: string) => number;
33
+ }
34
+ //# sourceMappingURL=pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/locale/pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yDAAyD;AACzD,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,6GAA6G;AAC7G,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,6EAA6E;IAC7E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;IAClC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC;IACxB,4CAA4C;IAC5C,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3C"}