chaffjs 0.34.0 → 0.35.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/dist/capitals-passage.d.ts +3 -0
- package/dist/capitals-passage.d.ts.map +1 -0
- package/dist/capitals-passage.js +26 -0
- package/dist/capitals-passage.js.map +1 -0
- package/dist/coined-hyphen-name.d.ts +13 -0
- package/dist/coined-hyphen-name.d.ts.map +1 -0
- package/dist/coined-hyphen-name.js +21 -0
- package/dist/coined-hyphen-name.js.map +1 -0
- package/dist/company-names.d.ts +6 -1
- package/dist/company-names.d.ts.map +1 -1
- package/dist/company-names.js +22 -3
- package/dist/company-names.js.map +1 -1
- package/dist/detectors/acronym-context.d.ts +1 -1
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +4 -4
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-definition.d.ts +13 -0
- package/dist/detectors/acronym-definition.d.ts.map +1 -0
- package/dist/detectors/acronym-definition.js +90 -0
- package/dist/detectors/acronym-definition.js.map +1 -0
- package/dist/detectors/acronym-lone.d.ts +13 -0
- package/dist/detectors/acronym-lone.d.ts.map +1 -0
- package/dist/detectors/acronym-lone.js +22 -0
- package/dist/detectors/acronym-lone.js.map +1 -0
- package/dist/detectors/acronym-name.d.ts +55 -0
- package/dist/detectors/acronym-name.d.ts.map +1 -0
- package/dist/detectors/acronym-name.js +75 -0
- package/dist/detectors/acronym-name.js.map +1 -0
- package/dist/detectors/capitalised-term.d.ts +14 -2
- package/dist/detectors/capitalised-term.d.ts.map +1 -1
- package/dist/detectors/capitalised-term.js +115 -4
- package/dist/detectors/capitalised-term.js.map +1 -1
- package/dist/detectors/caps-caption.d.ts +11 -0
- package/dist/detectors/caps-caption.d.ts.map +1 -0
- package/dist/detectors/caps-caption.js +51 -0
- package/dist/detectors/caps-caption.js.map +1 -0
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +47 -18
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/clause-shape.d.ts +1 -1
- package/dist/detectors/clause-shape.d.ts.map +1 -1
- package/dist/detectors/clause-shape.js +9 -4
- package/dist/detectors/clause-shape.js.map +1 -1
- package/dist/detectors/concrete-evidence.d.ts +13 -1
- package/dist/detectors/concrete-evidence.d.ts.map +1 -1
- package/dist/detectors/concrete-evidence.js +59 -0
- package/dist/detectors/concrete-evidence.js.map +1 -1
- package/dist/detectors/contraction-minority.d.ts +11 -0
- package/dist/detectors/contraction-minority.d.ts.map +1 -0
- package/dist/detectors/contraction-minority.js +10 -0
- package/dist/detectors/contraction-minority.js.map +1 -0
- package/dist/detectors/counter-edge.d.ts +5 -0
- package/dist/detectors/counter-edge.d.ts.map +1 -1
- package/dist/detectors/counter-edge.js +23 -0
- package/dist/detectors/counter-edge.js.map +1 -1
- package/dist/detectors/date-any-year.d.ts +6 -3
- package/dist/detectors/date-any-year.d.ts.map +1 -1
- package/dist/detectors/date-any-year.js +47 -10
- package/dist/detectors/date-any-year.js.map +1 -1
- package/dist/detectors/date-heading-year.d.ts +18 -0
- package/dist/detectors/date-heading-year.d.ts.map +1 -0
- package/dist/detectors/date-heading-year.js +38 -0
- package/dist/detectors/date-heading-year.js.map +1 -0
- package/dist/detectors/date-year.d.ts +12 -2
- package/dist/detectors/date-year.d.ts.map +1 -1
- package/dist/detectors/date-year.js +31 -6
- package/dist/detectors/date-year.js.map +1 -1
- package/dist/detectors/defined-term-form.d.ts.map +1 -1
- package/dist/detectors/defined-term-form.js +26 -5
- package/dist/detectors/defined-term-form.js.map +1 -1
- package/dist/detectors/due-date.d.ts.map +1 -1
- package/dist/detectors/due-date.js +6 -0
- package/dist/detectors/due-date.js.map +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +5 -1
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/fact-consistency.d.ts.map +1 -1
- package/dist/detectors/fact-consistency.js +3 -2
- package/dist/detectors/fact-consistency.js.map +1 -1
- package/dist/detectors/gram-passages.d.ts +11 -0
- package/dist/detectors/gram-passages.d.ts.map +1 -0
- package/dist/detectors/gram-passages.js +26 -0
- package/dist/detectors/gram-passages.js.map +1 -0
- package/dist/detectors/heading-end-mark.d.ts +8 -3
- package/dist/detectors/heading-end-mark.d.ts.map +1 -1
- package/dist/detectors/heading-end-mark.js +18 -8
- package/dist/detectors/heading-end-mark.js.map +1 -1
- package/dist/detectors/heading-names.d.ts +7 -0
- package/dist/detectors/heading-names.d.ts.map +1 -0
- package/dist/detectors/heading-names.js +24 -0
- package/dist/detectors/heading-names.js.map +1 -0
- package/dist/detectors/heading-numbering.d.ts.map +1 -1
- package/dist/detectors/heading-numbering.js +7 -2
- package/dist/detectors/heading-numbering.js.map +1 -1
- package/dist/detectors/inch-mark.d.ts +10 -0
- package/dist/detectors/inch-mark.d.ts.map +1 -0
- package/dist/detectors/inch-mark.js +48 -0
- package/dist/detectors/inch-mark.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +0 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +29 -23
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/mark-shape.d.ts +6 -3
- package/dist/detectors/mark-shape.d.ts.map +1 -1
- package/dist/detectors/mark-shape.js +12 -6
- package/dist/detectors/mark-shape.js.map +1 -1
- package/dist/detectors/name-variant-apart.d.ts +27 -0
- package/dist/detectors/name-variant-apart.d.ts.map +1 -0
- package/dist/detectors/name-variant-apart.js +51 -0
- package/dist/detectors/name-variant-apart.js.map +1 -0
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +18 -11
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/numeral-word.d.ts +15 -0
- package/dist/detectors/numeral-word.d.ts.map +1 -0
- package/dist/detectors/numeral-word.js +26 -0
- package/dist/detectors/numeral-word.js.map +1 -0
- package/dist/detectors/organisation-run.d.ts +14 -0
- package/dist/detectors/organisation-run.d.ts.map +1 -0
- package/dist/detectors/organisation-run.js +73 -0
- package/dist/detectors/organisation-run.js.map +1 -0
- package/dist/detectors/orthographic-variant.d.ts.map +1 -1
- package/dist/detectors/orthographic-variant.js +13 -5
- package/dist/detectors/orthographic-variant.js.map +1 -1
- package/dist/detectors/outline-label.d.ts +14 -0
- package/dist/detectors/outline-label.d.ts.map +1 -0
- package/dist/detectors/outline-label.js +32 -0
- package/dist/detectors/outline-label.js.map +1 -0
- package/dist/detectors/page-marker.d.ts +6 -0
- package/dist/detectors/page-marker.d.ts.map +1 -0
- package/dist/detectors/page-marker.js +19 -0
- package/dist/detectors/page-marker.js.map +1 -0
- package/dist/detectors/parallel-definition.d.ts +16 -0
- package/dist/detectors/parallel-definition.d.ts.map +1 -0
- package/dist/detectors/parallel-definition.js +51 -0
- package/dist/detectors/parallel-definition.js.map +1 -0
- package/dist/detectors/reference-text.d.ts.map +1 -1
- package/dist/detectors/reference-text.js +1 -0
- package/dist/detectors/reference-text.js.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.d.ts.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.js +2 -2
- package/dist/detectors/registry/unfilled-placeholder.js.map +1 -1
- package/dist/detectors/registry/vague-density.js +1 -1
- package/dist/detectors/registry/vague-density.js.map +1 -1
- package/dist/detectors/request-owner.d.ts +6 -2
- package/dist/detectors/request-owner.d.ts.map +1 -1
- package/dist/detectors/request-owner.js +57 -7
- package/dist/detectors/request-owner.js.map +1 -1
- package/dist/detectors/requirement-smell.d.ts.map +1 -1
- package/dist/detectors/requirement-smell.js +10 -2
- package/dist/detectors/requirement-smell.js.map +1 -1
- package/dist/detectors/sentence-length.d.ts.map +1 -1
- package/dist/detectors/sentence-length.js +24 -16
- package/dist/detectors/sentence-length.js.map +1 -1
- package/dist/detectors/sentence-rhythm.d.ts.map +1 -1
- package/dist/detectors/sentence-rhythm.js +6 -3
- package/dist/detectors/sentence-rhythm.js.map +1 -1
- package/dist/detectors/series-label.d.ts +2 -1
- package/dist/detectors/series-label.d.ts.map +1 -1
- package/dist/detectors/series-label.js +10 -4
- package/dist/detectors/series-label.js.map +1 -1
- package/dist/detectors/signals.d.ts +4 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +95 -36
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +8 -7
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/template-sections.d.ts +14 -0
- package/dist/detectors/template-sections.d.ts.map +1 -0
- package/dist/detectors/template-sections.js +25 -0
- package/dist/detectors/template-sections.js.map +1 -0
- package/dist/detectors/titled-run.d.ts +22 -0
- package/dist/detectors/titled-run.d.ts.map +1 -0
- package/dist/detectors/titled-run.js +34 -0
- package/dist/detectors/titled-run.js.map +1 -0
- package/dist/detectors/unbalanced-bracket.d.ts.map +1 -1
- package/dist/detectors/unbalanced-bracket.js +4 -0
- package/dist/detectors/unbalanced-bracket.js.map +1 -1
- package/dist/detectors/undefined-term.d.ts.map +1 -1
- package/dist/detectors/undefined-term.js +8 -0
- package/dist/detectors/undefined-term.js.map +1 -1
- package/dist/detectors/unfilled-placeholder.d.ts +4 -0
- package/dist/detectors/unfilled-placeholder.d.ts.map +1 -0
- package/dist/detectors/unfilled-placeholder.js +9 -0
- package/dist/detectors/unfilled-placeholder.js.map +1 -0
- package/dist/detectors/vague-density.d.ts +5 -0
- package/dist/detectors/vague-density.d.ts.map +1 -0
- package/dist/detectors/vague-density.js +52 -0
- package/dist/detectors/vague-density.js.map +1 -0
- package/dist/facts/fact-scope.d.ts +10 -1
- package/dist/facts/fact-scope.d.ts.map +1 -1
- package/dist/facts/fact-scope.js +34 -3
- package/dist/facts/fact-scope.js.map +1 -1
- package/dist/facts/table-facts.d.ts +12 -2
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +22 -4
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/grade-api.d.ts +9 -2
- package/dist/grade-api.d.ts.map +1 -1
- package/dist/grade-api.js +31 -15
- package/dist/grade-api.js.map +1 -1
- package/dist/heading-numbering.d.ts +5 -2
- package/dist/heading-numbering.d.ts.map +1 -1
- package/dist/heading-numbering.js +27 -6
- package/dist/heading-numbering.js.map +1 -1
- package/dist/name-end-letter.d.ts +5 -0
- package/dist/name-end-letter.d.ts.map +1 -1
- package/dist/name-end-letter.js +8 -0
- package/dist/name-end-letter.js.map +1 -1
- package/dist/name-given-before.d.ts +15 -0
- package/dist/name-given-before.d.ts.map +1 -0
- package/dist/name-given-before.js +30 -0
- package/dist/name-given-before.js.map +1 -0
- package/dist/name-separator.d.ts +2 -2
- package/dist/name-separator.d.ts.map +1 -1
- package/dist/name-separator.js +56 -5
- package/dist/name-separator.js.map +1 -1
- package/dist/name-variants.d.ts +3 -1
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +8 -7
- package/dist/name-variants.js.map +1 -1
- package/dist/name-word-order.d.ts +9 -5
- package/dist/name-word-order.d.ts.map +1 -1
- package/dist/name-word-order.js +47 -15
- package/dist/name-word-order.js.map +1 -1
- package/dist/nominal-stem.d.ts +13 -0
- package/dist/nominal-stem.d.ts.map +1 -0
- package/dist/nominal-stem.js +32 -0
- package/dist/nominal-stem.js.map +1 -0
- package/dist/sentence-load.d.ts +2 -1
- package/dist/sentence-load.d.ts.map +1 -1
- package/dist/sentence-load.js +6 -2
- package/dist/sentence-load.js.map +1 -1
- package/dist/structure/cited-document-section.d.ts +7 -0
- package/dist/structure/cited-document-section.d.ts.map +1 -0
- package/dist/structure/cited-document-section.js +33 -0
- package/dist/structure/cited-document-section.js.map +1 -0
- package/dist/structure/html-text-blocks.d.ts +8 -0
- package/dist/structure/html-text-blocks.d.ts.map +1 -0
- package/dist/structure/html-text-blocks.js +47 -0
- package/dist/structure/html-text-blocks.js.map +1 -0
- package/dist/structure/issues.d.ts +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +19 -4
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/labelled-date-order.d.ts +4 -1
- package/dist/structure/labelled-date-order.d.ts.map +1 -1
- package/dist/structure/labelled-date-order.js +2 -2
- package/dist/structure/labelled-date-order.js.map +1 -1
- package/dist/structure/labelled-order.d.ts +3 -0
- package/dist/structure/labelled-order.d.ts.map +1 -1
- package/dist/structure/labelled-order.js +8 -3
- package/dist/structure/labelled-order.js.map +1 -1
- package/dist/structure/list-numbering.d.ts +20 -4
- package/dist/structure/list-numbering.d.ts.map +1 -1
- package/dist/structure/list-numbering.js +65 -9
- package/dist/structure/list-numbering.js.map +1 -1
- package/dist/structure/listed-paragraph.d.ts +16 -0
- package/dist/structure/listed-paragraph.d.ts.map +1 -0
- package/dist/structure/listed-paragraph.js +37 -0
- package/dist/structure/listed-paragraph.js.map +1 -0
- package/dist/structure/numbering.d.ts +3 -2
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +31 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/reference-text.d.ts +9 -1
- package/dist/structure/reference-text.d.ts.map +1 -1
- package/dist/structure/reference-text.js +21 -3
- package/dist/structure/reference-text.js.map +1 -1
- package/dist/structure/section-names.d.ts +12 -0
- package/dist/structure/section-names.d.ts.map +1 -0
- package/dist/structure/section-names.js +26 -0
- package/dist/structure/section-names.js.map +1 -0
- package/dist/structure/versioned-label.d.ts +16 -0
- package/dist/structure/versioned-label.d.ts.map +1 -0
- package/dist/structure/versioned-label.js +48 -0
- package/dist/structure/versioned-label.js.map +1 -0
- package/dist/structure/written-headings.d.ts +31 -0
- package/dist/structure/written-headings.d.ts.map +1 -0
- package/dist/structure/written-headings.js +119 -0
- package/dist/structure/written-headings.js.map +1 -0
- package/dist/structure/written-resolve.d.ts +12 -0
- package/dist/structure/written-resolve.d.ts.map +1 -0
- package/dist/structure/written-resolve.js +49 -0
- package/dist/structure/written-resolve.js.map +1 -0
- package/package.json +3 -3
- package/rules/all-caps-shouting.yaml +2 -2
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +13 -13
- package/rules/dangling-reference.yaml +2 -2
- package/rules/date-without-year.yaml +2 -2
- package/rules/defined-name-repeated.yaml +2 -2
- package/rules/fact-conflict.yaml +2 -2
- package/rules/heading-end-punctuation-mix.yaml +2 -2
- package/rules/heading-numbering-mix.yaml +2 -2
- package/rules/latin-spacing.yaml +2 -2
- package/rules/max-kanji-continuous.yaml +5 -3
- package/rules/max-sentence-length.yaml +3 -2
- package/rules/name-variant.yaml +2 -2
- package/rules/ngram-repetition.yaml +2 -2
- package/rules/numbering-gap.yaml +2 -2
- package/rules/orthographic-variant.yaml +2 -2
- package/rules/reference-title-mismatch.yaml +2 -2
- package/rules/repeated-sentence-ending.yaml +3 -2
- package/rules/request-without-deadline.yaml +2 -2
- package/rules/requirement-smell.yaml +2 -2
- package/rules/sentence-rhythm.yaml +2 -2
- package/rules/title-case-consistency.yaml +2 -2
- package/rules/unbalanced-bracket.yaml +2 -2
- package/rules/undefined-acronym.yaml +11 -2
- package/rules/undefined-term.yaml +2 -2
- package/rules/unfilled-placeholder.yaml +2 -2
- package/rules/vague-word-density.yaml +2 -2
- package/src/capitals-passage.ts +31 -0
- package/src/coined-hyphen-name.ts +27 -0
- package/src/company-names.ts +24 -3
- package/src/detectors/acronym-context.ts +5 -6
- package/src/detectors/acronym-definition.ts +123 -0
- package/src/detectors/acronym-lone.ts +26 -0
- package/src/detectors/acronym-name.ts +117 -0
- package/src/detectors/capitalised-term.ts +154 -4
- package/src/detectors/caps-caption.ts +61 -0
- package/src/detectors/char-shape.ts +74 -22
- package/src/detectors/clause-shape.ts +14 -4
- package/src/detectors/concrete-evidence.ts +77 -1
- package/src/detectors/contraction-minority.ts +11 -0
- package/src/detectors/counter-edge.ts +23 -0
- package/src/detectors/date-any-year.ts +56 -10
- package/src/detectors/date-heading-year.ts +55 -0
- package/src/detectors/date-year.ts +54 -9
- package/src/detectors/defined-term-form.ts +26 -5
- package/src/detectors/due-date.ts +10 -1
- package/src/detectors/en-shape.ts +5 -1
- package/src/detectors/fact-consistency.ts +3 -2
- package/src/detectors/gram-passages.ts +36 -0
- package/src/detectors/heading-end-mark.ts +23 -10
- package/src/detectors/heading-names.ts +31 -0
- package/src/detectors/heading-numbering.ts +7 -2
- package/src/detectors/inch-mark.ts +49 -0
- package/src/detectors/lexicon.ts +32 -21
- package/src/detectors/mark-shape.ts +12 -6
- package/src/detectors/name-variant-apart.ts +63 -0
- package/src/detectors/name-variant.ts +30 -12
- package/src/detectors/numeral-word.ts +33 -0
- package/src/detectors/organisation-run.ts +86 -0
- package/src/detectors/orthographic-variant.ts +22 -6
- package/src/detectors/outline-label.ts +48 -0
- package/src/detectors/page-marker.ts +26 -0
- package/src/detectors/parallel-definition.ts +64 -0
- package/src/detectors/reference-text.ts +1 -0
- package/src/detectors/registry/unfilled-placeholder.ts +2 -2
- package/src/detectors/registry/vague-density.ts +1 -1
- package/src/detectors/request-owner.ts +71 -8
- package/src/detectors/requirement-smell.ts +15 -2
- package/src/detectors/sentence-length.ts +14 -6
- package/src/detectors/sentence-rhythm.ts +8 -4
- package/src/detectors/series-label.ts +13 -4
- package/src/detectors/signals.ts +94 -26
- package/src/detectors/structure-tree.ts +10 -8
- package/src/detectors/template-sections.ts +37 -0
- package/src/detectors/titled-run.ts +43 -0
- package/src/detectors/unbalanced-bracket.ts +3 -0
- package/src/detectors/undefined-term.ts +8 -0
- package/src/detectors/unfilled-placeholder.ts +10 -0
- package/src/detectors/vague-density.ts +68 -0
- package/src/facts/fact-scope.ts +53 -4
- package/src/facts/table-facts.ts +43 -6
- package/src/grade-api.ts +38 -16
- package/src/heading-numbering.ts +35 -8
- package/src/name-end-letter.ts +9 -0
- package/src/name-given-before.ts +40 -0
- package/src/name-separator.ts +66 -5
- package/src/name-variants.ts +21 -11
- package/src/name-word-order.ts +62 -17
- package/src/nominal-stem.ts +36 -0
- package/src/sentence-load.ts +7 -2
- package/src/structure/cited-document-section.ts +42 -0
- package/src/structure/html-text-blocks.ts +45 -0
- package/src/structure/issues.ts +19 -4
- package/src/structure/labelled-date-order.ts +5 -2
- package/src/structure/labelled-order.ts +12 -3
- package/src/structure/list-numbering.ts +100 -11
- package/src/structure/listed-paragraph.ts +45 -0
- package/src/structure/numbering.ts +48 -19
- package/src/structure/reference-text.ts +25 -3
- package/src/structure/section-names.ts +32 -0
- package/src/structure/versioned-label.ts +52 -0
- package/src/structure/written-headings.ts +137 -0
- package/src/structure/written-resolve.ts +57 -0
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// order (dated-pair-label, date-now-word, and dated-span-label for the words whose line states a period, read up to its end,
|
|
5
5
|
// or from its start in the group a dated-span-label entry names;
|
|
6
6
|
// a table whose header names both columns of a pair, from dated-pair-label and the header-only dated-column-label;
|
|
7
|
-
// a signature line giving a pair's later date, from the role words of dated-signer-label
|
|
7
|
+
// a signature line giving a pair's later date, from the role words of dated-signer-label;
|
|
8
|
+
// a pair's label naming a version, End of support for 5.0.0 or 3.0.0 のサポート終了日, from dated-pair-version-word);
|
|
8
9
|
// the dates are the structure tree's, and the lines are read with code masked.
|
|
9
10
|
import type { Detector, Finding, ProseDocument } from "../plugin.ts";
|
|
10
11
|
import { actionDueBeforeMeeting, type ActionWords } from "../structure/action-due.ts";
|
|
@@ -15,6 +16,7 @@ import { inDocumentOrder, type StructureIssue } from "../structure/issues.ts";
|
|
|
15
16
|
import { proseAndTablesOf } from "../table-text.ts";
|
|
16
17
|
import { periodWordsOf } from "./range-words.ts";
|
|
17
18
|
import { quoteAt } from "./structure-tree.ts";
|
|
19
|
+
import type { VersionWords } from "../structure/versioned-label.ts";
|
|
18
20
|
import { readableDates, withReadableDates } from "./readable-dates.ts";
|
|
19
21
|
|
|
20
22
|
/** A record's facts table can sit well below its header date (a listing date above the table holding the move-in date). */
|
|
@@ -37,6 +39,12 @@ const actionWordsOf = (doc: ProseDocument): ActionWords => ({
|
|
|
37
39
|
minutes: patternsOf(doc, "minutes-title-word"),
|
|
38
40
|
});
|
|
39
41
|
|
|
42
|
+
const versionWordsOf = (doc: ProseDocument): VersionWords => {
|
|
43
|
+
const entries = doc.lexicons["dated-pair-version-word"] ?? [];
|
|
44
|
+
const at = (position: "before" | "after"): string[] => entries.filter((entry) => entry.position === position).map((entry) => entry.pattern);
|
|
45
|
+
return { after: at("after"), before: at("before") };
|
|
46
|
+
};
|
|
47
|
+
|
|
40
48
|
const orderAsidesOf = (doc: ProseDocument): string[] => [...patternsOf(doc, "deadline-passed-word"), ...patternsOf(doc, "date-now-word")];
|
|
41
49
|
|
|
42
50
|
const treeDates = (tree: NonNullable<ProseDocument["structure"]>): OrderDate[] =>
|
|
@@ -70,6 +78,7 @@ export const dueDate: Detector = (doc): Finding[] => {
|
|
|
70
78
|
spans,
|
|
71
79
|
whole: doc.source,
|
|
72
80
|
signers: doc.lexicons["dated-signer-label"] ?? [],
|
|
81
|
+
versions: versionWordsOf(doc),
|
|
73
82
|
}).map((issue) => findingOf(doc, issue, variantOf(issue))),
|
|
74
83
|
...columnDatesOutOfOrder(text, dates, [...pairs, ...(doc.lexicons["dated-column-label"] ?? [])], orderAsidesOf(doc)).map((issue) =>
|
|
75
84
|
findingOf(doc, issue, "order"),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lengthOf } from "../measure.ts";
|
|
2
2
|
import { isClosed } from "../sentence-shape.ts";
|
|
3
3
|
import { isTitleCase, minorityCase, pageTitleOf } from "./heading-case.ts";
|
|
4
|
+
import { namesWrittenMidSentence } from "./heading-names.ts";
|
|
4
5
|
import type { Detector, Finding, Lexicon, ProseDocument, Section, Sentence, Token } from "../plugin.ts";
|
|
5
6
|
import { entryOpens } from "./lexicon-match.ts";
|
|
6
7
|
|
|
@@ -137,7 +138,10 @@ const headingsOf = (sections: readonly Section[], fixedCase: ReadonlySet<string>
|
|
|
137
138
|
* 見るのは文書の中で揃っているかだけで、少数派のほうを指摘する。題名は指摘しない(minorityCase)。
|
|
138
139
|
*/
|
|
139
140
|
export const titleCaseMix: Detector = (doc, options): Finding[] => {
|
|
140
|
-
const fixedCase = new Set(
|
|
141
|
+
const fixedCase = new Set([
|
|
142
|
+
...FIXED_CASE_LISTS.flatMap((id) => (doc.lexicons[id] ?? []).map((entry) => entry.pattern)),
|
|
143
|
+
...namesWrittenMidSentence(doc.sentences.map((sentence) => sentence.text)),
|
|
144
|
+
]);
|
|
141
145
|
const pageTitle = pageTitleOf(doc.sections);
|
|
142
146
|
const judged = headingsOf(
|
|
143
147
|
doc.sections.filter((section) => section !== pageTitle),
|
|
@@ -140,9 +140,10 @@ const readFacts = (doc: ProseDocument, tree: StructureNode): ScopedFact[] => {
|
|
|
140
140
|
);
|
|
141
141
|
const read = new Set([...termed, ...labelled.map((fact) => fact.value.start)]);
|
|
142
142
|
const verbs = verbQuantityFacts(doc.source, values, verbQuantityWordsOf(doc)).filter((fact) => !read.has(fact.value.start));
|
|
143
|
-
const tabled = tableFacts(doc.source, values, totalColumnsOf(doc), countLabelWordsOf(doc.lexicons));
|
|
143
|
+
const tabled = tableFacts(doc.source, values, totalColumnsOf(doc), countLabelWordsOf(doc.lexicons), { columnGroups: true });
|
|
144
144
|
const facts = withQualifiedKeys(doc, [...labelled, ...terms, ...verbs, ...tabled]);
|
|
145
|
-
|
|
145
|
+
const scoped = scopedFacts(facts, tree, doc.source, patternsOf(doc, "summary-heading"), { paragraphHeads: true });
|
|
146
|
+
return withCellKeys(scoped, qualifierWordsOf(doc));
|
|
146
147
|
};
|
|
147
148
|
|
|
148
149
|
/** 文書の名前付きの値。二つの rule が読むので、文書ごとに一度だけ読む。 */
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Section, Sentence } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 問いの印で終わる見出し。後ろに括弧の注記((updated Nov. 14, 2024))が付いてもよい。
|
|
5
|
+
* 「Q1」の札だけでは問いと見ない。四半期(Q1 2024)の見出しと見分けられない。
|
|
6
|
+
*/
|
|
7
|
+
const QUESTION_END = /[??]\s*(?:[((][^()()]*[))])?$/u;
|
|
8
|
+
|
|
9
|
+
export const isQuestionHeading = (heading: string): boolean => QUESTION_END.test(heading.trim());
|
|
10
|
+
|
|
11
|
+
/** 範囲を決めるのに見るのは、見出しと深さと文だけ。 */
|
|
12
|
+
type Heading = Pick<Section, "depth" | "heading" | "sentences">;
|
|
13
|
+
|
|
14
|
+
type Answer = { readonly depth: number; readonly sentences: Sentence[] };
|
|
15
|
+
type Passages = { readonly rest: Sentence[]; readonly answers: Answer[]; readonly open: readonly Answer[] };
|
|
16
|
+
|
|
17
|
+
/** 節は、それを囲む一番内側の問いの答えに入る。同じ深さか浅い見出しが来れば、その問いは閉じている。 */
|
|
18
|
+
const place = (passages: Passages, section: Heading): Passages => {
|
|
19
|
+
const open = passages.open.filter((answer) => answer.depth < section.depth);
|
|
20
|
+
if (isQuestionHeading(section.heading)) {
|
|
21
|
+
const answer = { depth: section.depth, sentences: [...section.sentences] };
|
|
22
|
+
return { ...passages, answers: [...passages.answers, answer], open: [...open, answer] };
|
|
23
|
+
}
|
|
24
|
+
const owner = open.at(-1);
|
|
25
|
+
(owner?.sentences ?? passages.rest).push(...section.sentences);
|
|
26
|
+
return { ...passages, open };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 言い回しを数える範囲。問いの見出しの下(より深い見出しの「A1」も含む)はそれぞれ一つの答えで、読み手は答えを一つずつ読む。
|
|
31
|
+
* 別の答えで同じ案内(〜をご覧ください)を繰り返しても、書き手が言い直したのではない。問いでない節は、文書全体で一つの範囲。
|
|
32
|
+
*/
|
|
33
|
+
export const gramPassagesOf = (sections: readonly Heading[]): Sentence[][] => {
|
|
34
|
+
const passages = sections.reduce<Passages>(place, { rest: [], answers: [], open: [] });
|
|
35
|
+
return [passages.rest, ...passages.answers.map((answer) => answer.sentences)].filter((sentences) => sentences.length > 0);
|
|
36
|
+
};
|
|
@@ -39,8 +39,21 @@ export const endMarkOf = (heading: string, words: readonly string[] = []): strin
|
|
|
39
39
|
|
|
40
40
|
type Judged = { readonly heading: MarkupHeading; readonly mark: string };
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const LOWER_CASE = /\p{Ll}/u;
|
|
43
|
+
/** A word of this many capitals is a word set in capitals, not only an acronym (AGENCY, DATES; not FAQ). */
|
|
44
|
+
const CAPITALS_WORD = /\p{Lu}{4,}/u;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a heading is set in capitals ("AGENCY:", "FOR FURTHER INFORMATION CONTACT:"). A notice's captions are one kind of
|
|
48
|
+
* heading and its sections in ordinary case ("Footnotes") another, so the two are compared apart.
|
|
49
|
+
*/
|
|
50
|
+
export const isCapitalsHeading = (heading: string): boolean => !LOWER_CASE.test(heading) && CAPITALS_WORD.test(heading);
|
|
51
|
+
|
|
52
|
+
/** Headings compared with one another: the same depth, and the same case (capitals or ordinary). */
|
|
53
|
+
const kindOf = (entry: Judged): string => `${String(entry.heading.depth)}:${String(isCapitalsHeading(entry.heading.text))}`;
|
|
54
|
+
|
|
55
|
+
/** The odd ones among headings of one kind, each with the usual side's first heading and count. */
|
|
56
|
+
const oddOfKind = (doc: ProseDocument, judged: readonly Judged[]): Finding[] => {
|
|
44
57
|
if (judged.length < MIN_HEADINGS) return [];
|
|
45
58
|
const odd = minorityWithin(judged, (entry) => entry.mark !== "", MINORITY_PERCENT);
|
|
46
59
|
const usual = judged.filter((entry) => !odd.includes(entry));
|
|
@@ -56,9 +69,9 @@ const oddAtDepth = (doc: ProseDocument, judged: readonly Judged[]): Finding[] =>
|
|
|
56
69
|
};
|
|
57
70
|
|
|
58
71
|
/**
|
|
59
|
-
* Headings that end with a colon or a full stop where most headings of the same depth do not, or the other way round.
|
|
60
|
-
* of one depth are one kind of heading; a depth of labels ("Optional:") is its own way
|
|
61
|
-
* that opens the document) is a name and is not compared.
|
|
72
|
+
* Headings that end with a colon or a full stop where most headings of the same depth and case do not, or the other way round.
|
|
73
|
+
* Headings of one depth are one kind of heading; a depth of labels ("Optional:") is its own way, and so are captions set in
|
|
74
|
+
* capitals. The page title (one top-level heading that opens the document) is a name and is not compared.
|
|
62
75
|
*/
|
|
63
76
|
export const headingEndMark: Detector = (doc, options): Finding[] => {
|
|
64
77
|
const words = (options.lexicon ?? []).map((entry) => entry.pattern);
|
|
@@ -70,12 +83,12 @@ export const headingEndMark: Detector = (doc, options): Finding[] => {
|
|
|
70
83
|
const mark = endMarkOf(heading.text, words);
|
|
71
84
|
return mark === undefined ? [] : [{ heading, mark }];
|
|
72
85
|
});
|
|
73
|
-
const
|
|
74
|
-
return
|
|
75
|
-
.flatMap((
|
|
76
|
-
|
|
86
|
+
const kinds = [...new Set(judged.map(kindOf))];
|
|
87
|
+
return kinds
|
|
88
|
+
.flatMap((kind) =>
|
|
89
|
+
oddOfKind(
|
|
77
90
|
doc,
|
|
78
|
-
judged.filter((entry) => entry
|
|
91
|
+
judged.filter((entry) => kindOf(entry) === kind),
|
|
79
92
|
),
|
|
80
93
|
)
|
|
81
94
|
.toSorted((left, right) => Number(left.values["offset"]) - Number(right.values["offset"]));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { straightApostrophes } from "../orthography.ts";
|
|
2
|
+
|
|
3
|
+
const WORD = /[A-Za-z][A-Za-z'-]*/gu;
|
|
4
|
+
const POSSESSIVE = /'s$/u;
|
|
5
|
+
/** 番号が続く語(Example 4、Layer 1)は番号の札で、名前ではない。札の語は流儀によらず大文字で書く。 */
|
|
6
|
+
const NUMBER_FOLLOWS = /^\s+\d/u;
|
|
7
|
+
/** 引用符の中("Wakayama's Food Ingredients and Food Culture")は題名や人の言葉で、題の語は名前でなくても大文字になる。 */
|
|
8
|
+
const QUOTE_MARK = /[“”]/gu;
|
|
9
|
+
|
|
10
|
+
const outsideQuotes = (text: string): string =>
|
|
11
|
+
text
|
|
12
|
+
.replace(QUOTE_MARK, '"')
|
|
13
|
+
.split('"')
|
|
14
|
+
.filter((_, index) => index % 2 === 0)
|
|
15
|
+
.join(" ");
|
|
16
|
+
|
|
17
|
+
const startsUpper = (word: string): boolean => /^[A-Z]/u.test(word);
|
|
18
|
+
|
|
19
|
+
const midSentenceNames = (sentence: string): string[] =>
|
|
20
|
+
[...outsideQuotes(straightApostrophes(sentence)).matchAll(WORD)]
|
|
21
|
+
.slice(1)
|
|
22
|
+
.filter((match) => startsUpper(match[0]) && !NUMBER_FOLLOWS.test(match.input.slice(match.index + match[0].length)))
|
|
23
|
+
.map((match) => match[0].replace(POSSESSIVE, ""));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 文書が文の途中で大文字で書く語(NOAA、Flood Inundation Mapping、Alaska)。固有の名前なので、見出しに出ても
|
|
27
|
+
* どちらの流儀でも大文字のまま。見出しの流儀の証拠に数えないよう、所有格の形(NOAA's)も添えて返す。
|
|
28
|
+
* 文の頭の語は、名前でなくても大文字になるので数えない。
|
|
29
|
+
*/
|
|
30
|
+
export const namesWrittenMidSentence = (sentences: readonly string[]): ReadonlySet<string> =>
|
|
31
|
+
new Set(sentences.flatMap(midSentenceNames).flatMap((name) => [name, `${name}'s`]));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Detector, Finding, LexiconEntry } from "../plugin.ts";
|
|
2
2
|
import { findingAt, writtenHeadings } from "./markup-finding.ts";
|
|
3
|
-
import { numberingMinorities, numberingStyleOf, UNNUMBERED, type NumberingMinority, type NumberLabel } from "../heading-numbering.ts";
|
|
3
|
+
import { numberingMinorities, numberingStyleOf, opensNumbering, UNNUMBERED, type NumberingMinority, type NumberLabel } from "../heading-numbering.ts";
|
|
4
4
|
|
|
5
5
|
const labelsOf = (entries: readonly LexiconEntry[]): NumberLabel[] =>
|
|
6
6
|
entries.map((entry) => ({ word: entry.pattern, position: entry.position === "after" ? "after" : "before" }));
|
|
@@ -28,7 +28,12 @@ export const headingNumbering: Detector = (doc): Finding[] => {
|
|
|
28
28
|
const labels = labelsOf(doc.lexicons["heading-number-label"] ?? []);
|
|
29
29
|
const matter = (doc.lexicons["unnumbered-heading"] ?? []).map((entry) => entry.pattern.toLowerCase());
|
|
30
30
|
const styles = headings.map((heading) => numberingStyleOf(heading.text, labels));
|
|
31
|
-
const read = headings.map((heading, at) => ({
|
|
31
|
+
const read = headings.map((heading, at) => ({
|
|
32
|
+
depth: heading.depth,
|
|
33
|
+
style: styles[at] ?? UNNUMBERED,
|
|
34
|
+
skipped: isFrontOrBackMatter(heading.text, matter),
|
|
35
|
+
opens: opensNumbering(heading.text, labels),
|
|
36
|
+
}));
|
|
32
37
|
return numberingMinorities(read).flatMap((minority) => {
|
|
33
38
|
const heading = headings[minority.at];
|
|
34
39
|
if (heading === undefined) return [];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A curly double quote written as the inch mark: a decimal number, then the mark, then a unit (".015 “Hg/hr",
|
|
3
|
+
* "29.92”Hg"). It quotes nothing, so it opens and closes nothing. text is the section, offset the mark's position.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A number with a decimal part right before the mark, with at most one space between. A year or a count never has one,
|
|
8
|
+
* so "In 1969 “Hurricane Camille struck" stays an unclosed quote.
|
|
9
|
+
*/
|
|
10
|
+
const DECIMAL_BEFORE = /\.\p{Nd}+[ \u00A0]?$/u;
|
|
11
|
+
|
|
12
|
+
/** A short run of letters right after the mark: the unit (Hg, in). */
|
|
13
|
+
const UNIT_AFTER = /^\p{L}{1,3}(?!\p{L})/u;
|
|
14
|
+
|
|
15
|
+
const LOOK_AROUND = 40;
|
|
16
|
+
const OPENING = "\u201C";
|
|
17
|
+
const CLOSING = "\u201D";
|
|
18
|
+
|
|
19
|
+
/** Where the paragraph holding offset ends: the next blank line, or the end of the text. */
|
|
20
|
+
const paragraphEnd = (text: string, offset: number): number => {
|
|
21
|
+
const blank = /\n[ \t\u3000]*\n/u.exec(text.slice(offset));
|
|
22
|
+
return blank === null ? text.length : offset + blank.index;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const betweenNumberAndUnit = (text: string, offset: number): boolean =>
|
|
26
|
+
DECIMAL_BEFORE.test(text.slice(Math.max(0, offset - LOOK_AROUND), offset)) && UNIT_AFTER.test(text.slice(offset + 1, offset + 1 + LOOK_AROUND));
|
|
27
|
+
|
|
28
|
+
/** A closing mark in rest that is not taken by an opening mark written after the start of rest. */
|
|
29
|
+
const closesEarlier = (rest: string): boolean => {
|
|
30
|
+
const depth = { open: 0, closed: false };
|
|
31
|
+
[...rest].forEach((mark) => {
|
|
32
|
+
if (depth.closed) return;
|
|
33
|
+
if (mark === OPENING) depth.open += 1;
|
|
34
|
+
else if (mark === CLOSING && depth.open === 0) depth.closed = true;
|
|
35
|
+
else if (mark === CLOSING) depth.open -= 1;
|
|
36
|
+
});
|
|
37
|
+
return depth.closed;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The mark at offset is an inch mark. An opening mark is one only when nothing in its paragraph closes it ("at 2.5
|
|
42
|
+
* “Hg” quotes something); a closing mark only when no quote is open.
|
|
43
|
+
*/
|
|
44
|
+
export const isInchMark = (text: string, offset: number, quoteOpen: boolean): boolean => {
|
|
45
|
+
const mark = text.charAt(offset);
|
|
46
|
+
if ((mark !== OPENING && mark !== CLOSING) || !betweenNumberAndUnit(text, offset)) return false;
|
|
47
|
+
if (mark === CLOSING) return !quoteOpen;
|
|
48
|
+
return !closesEarlier(text.slice(offset + 1, paragraphEnd(text, offset)));
|
|
49
|
+
};
|
package/src/detectors/lexicon.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { joinWords } from "./word-list.ts";
|
|
2
|
+
import { pairMinority } from "./contraction-minority.ts";
|
|
2
3
|
import { straightApostrophes } from "../orthography.ts";
|
|
3
4
|
import { wordsOf } from "./structure.ts";
|
|
4
5
|
import type { Detector, Finding, Lexicon, LexiconEntry, ProseDocument, Sentence, Span, Token } from "../plugin.ts";
|
|
@@ -68,7 +69,6 @@ const hedgingDensity = densityRule("excessive-hedging");
|
|
|
68
69
|
export const cushionDensity = densityRule("cushion-phrase-density", "at-floor");
|
|
69
70
|
/** 語彙表の語の密度だけを見る rule が共通で使う。rule の id は実行側が入れる。 */
|
|
70
71
|
export const phraseDensity = densityRule("");
|
|
71
|
-
export const vagueDensity = densityRule("vague-word-density");
|
|
72
72
|
|
|
73
73
|
/** 書いたとおりの字。行の折り返しをまたいでいたら空白 1 つに。 */
|
|
74
74
|
const writtenAt = (source: string, span: Span): string => source.slice(span.start, span.end).replace(/\s+/gu, " ").trim();
|
|
@@ -258,40 +258,51 @@ export const aiTell: Detector = (doc, options): Finding[] => {
|
|
|
258
258
|
* **どちらが正しいかは決めない。** 短縮形を使うかどうかは文体の選択で、
|
|
259
259
|
* 立場を取ると方針の違う書き手に rule ごと無視される。spec §12.3。
|
|
260
260
|
*
|
|
261
|
-
* 語彙表は対を持つ(`pattern` と `instead_of
|
|
262
|
-
*
|
|
261
|
+
* 語彙表は対を持つ(`pattern` と `instead_of`)。比べるのは対の中だけで、別の語どうしは比べない。
|
|
262
|
+
* GOV.UK の文体は「you're」を使い否定は「do not」と書く。語ごとに決めた書きかたで、混在ではない。
|
|
263
|
+
* 否定と代名詞+動詞の族にもまとめない。「don't」と「does not」を書き分ける文書もある。
|
|
263
264
|
*/
|
|
264
265
|
/**
|
|
265
266
|
* 語の境界で照合する。部分一致だと「it isn't」が「it is」を含んでしまい、
|
|
266
267
|
* 短縮形を使っている文が「使っていない」側に数えられる。
|
|
267
268
|
*/
|
|
268
|
-
const
|
|
269
|
+
const wordUses = (text: string, word: string): number => [...text.matchAll(new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\b`, "gu"))].length;
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
const containsWord = (text: string, word: string): boolean => wordUses(text, word) > 0;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* アポストロフィの形(don’t と don't)は短縮形かどうかを変えない。違うのは印刷の形で、文体ではない。
|
|
275
|
+
* 折り返した行(「do」で改行して「not」)も、空白 1 つと同じに読む。
|
|
276
|
+
*/
|
|
277
|
+
const comparableText = (sentence: Sentence): string => straightApostrophes(sentence.text).replace(/\s+/gu, " ").toLowerCase();
|
|
278
|
+
|
|
279
|
+
type MixedPair = { readonly minority: string; readonly majority: string; readonly count: number };
|
|
280
|
+
|
|
281
|
+
const mixedPair = (body: string, short: string, long: string): MixedPair | undefined => {
|
|
282
|
+
const uses = { short: wordUses(body, short), long: wordUses(body, long) };
|
|
283
|
+
const side = pairMinority(uses);
|
|
284
|
+
if (side === undefined) return undefined;
|
|
285
|
+
return side === "short" ? { minority: short, majority: long, count: uses.short } : { minority: long, majority: short, count: uses.long };
|
|
286
|
+
};
|
|
272
287
|
|
|
273
288
|
export const contractionMix: Detector = (doc, options): Finding[] => {
|
|
274
|
-
|
|
275
|
-
const body = doc.sentences.map(comparableText).join("
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const minorityIsShort = used.length <= spelled.length;
|
|
281
|
-
const few = minorityIsShort ? used : spelled;
|
|
282
|
-
if (few.length > options.limit) return [];
|
|
283
|
-
const wanted = few.map((pair) => (minorityIsShort ? pair.short : pair.long).toLowerCase());
|
|
289
|
+
// 文は改行でつなぐ。空白でつなぐと、箇条書きの「What we do」と次の項目「not every…」が「do not」と数えられる。
|
|
290
|
+
const body = doc.sentences.map(comparableText).join("\n");
|
|
291
|
+
const mixed = (options.lexicon ?? []).flatMap((entry) => {
|
|
292
|
+
const pair = entry.instead_of === undefined ? undefined : mixedPair(body, entry.pattern.toLowerCase(), entry.instead_of.toLowerCase());
|
|
293
|
+
return pair === undefined || pair.count > options.limit ? [] : [pair];
|
|
294
|
+
});
|
|
284
295
|
const hits = doc.sentences.flatMap((sentence) => {
|
|
285
296
|
const text = comparableText(sentence);
|
|
286
|
-
const
|
|
287
|
-
return
|
|
297
|
+
const pair = mixed.find((candidate) => containsWord(text, candidate.minority));
|
|
298
|
+
return pair === undefined ? [] : [{ sentence, pair }];
|
|
288
299
|
});
|
|
289
|
-
return hits.map((
|
|
300
|
+
return hits.map(({ sentence, pair }) => ({
|
|
290
301
|
rule: "contraction-consistency",
|
|
291
302
|
severity: "info",
|
|
292
303
|
line: 0,
|
|
293
304
|
column: 0,
|
|
294
|
-
quote:
|
|
295
|
-
values: { matched:
|
|
305
|
+
quote: sentence.text.trim(),
|
|
306
|
+
values: { matched: pair.minority, majority: pair.majority, count: pair.count, limit: options.limit, offset: sentence.span.start },
|
|
296
307
|
}));
|
|
297
308
|
};
|
|
@@ -2,6 +2,7 @@ import type { Detector, Finding, Lexicon, ProseDocument, Sentence } from "../plu
|
|
|
2
2
|
import { escapeRegExp } from "../orthography.ts";
|
|
3
3
|
import { minorityReport } from "../spacing-minority.ts";
|
|
4
4
|
import { isWithinAny, quotedSpans } from "../quoted-span.ts";
|
|
5
|
+
import { captionsOf, isCaptionAt } from "./caps-caption.ts";
|
|
5
6
|
|
|
6
7
|
/** The letters a mark must touch to count as part of the document's text, by language. */
|
|
7
8
|
const SCRIPT: Readonly<Record<string, RegExp>> = {
|
|
@@ -41,12 +42,15 @@ const besideLetter = (text: string, at: number, step: number, script: RegExp): b
|
|
|
41
42
|
|
|
42
43
|
const touchesScript = (text: string, at: number, script: RegExp): boolean => besideLetter(text, at, -1, script) || besideLetter(text, at, 1, script);
|
|
43
44
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The marks in a sentence that touch a letter of the document's script, outside 「」『』 quotations. spaced is the lexicon's pattern side.
|
|
47
|
+
* The letters beside a mark are read in the source, so a mark that opens a sentence (。(2)当社) counts wherever the sentence starts.
|
|
48
|
+
*/
|
|
49
|
+
export const marksIn = (source: string, sentence: Sentence, forms: ReadonlyMap<string, Form>, script: RegExp): Mark[] => {
|
|
46
50
|
const quoted = quotedSpans(sentence.text);
|
|
47
51
|
return sentence.text.split("").flatMap((char, at) => {
|
|
48
52
|
const form = forms.get(char);
|
|
49
|
-
if (form === undefined || !touchesScript(sentence.
|
|
53
|
+
if (form === undefined || !touchesScript(source, sentence.span.start + at, script)) return [];
|
|
50
54
|
if (isWithinAny(quoted, { start: at, end: at })) return [];
|
|
51
55
|
return [{ sentence, char, form, offset: sentence.span.start + at, spaced: form.side }];
|
|
52
56
|
});
|
|
@@ -88,7 +92,7 @@ export const markConsistency: Detector = (doc, options): Finding[] => {
|
|
|
88
92
|
const script = SCRIPT[doc.language];
|
|
89
93
|
if (script === undefined) return [];
|
|
90
94
|
const forms = formsOf(options.lexicon ?? []);
|
|
91
|
-
const marks = ownSentences(doc).flatMap((sentence) => marksIn(sentence, forms, script));
|
|
95
|
+
const marks = ownSentences(doc).flatMap((sentence) => marksIn(doc.source, sentence, forms, script));
|
|
92
96
|
const kinds = [...new Set(marks.map((mark) => mark.form.kind))];
|
|
93
97
|
return kinds.flatMap((kind) =>
|
|
94
98
|
minorityFindings(
|
|
@@ -210,10 +214,12 @@ export const isShouting = (run: string, known: ReadonlySet<string>, conventional
|
|
|
210
214
|
return long.length >= MIN_LONG_WORDS && ordinary.length * 2 > words.length;
|
|
211
215
|
};
|
|
212
216
|
|
|
213
|
-
/** Runs of three or more all-capital words that are ordinary words, not acronyms ("DO NOT DELETE THIS"). */
|
|
217
|
+
/** Runs of three or more all-capital words that are ordinary words, not acronyms ("DO NOT DELETE THIS"), and not a caption or a mention of one. */
|
|
214
218
|
export const capsShouting: Detector = (doc, options): Finding[] => {
|
|
215
219
|
const known = lowerCaseWords(doc.prose ?? doc.source);
|
|
216
220
|
const conventional = new Set((options.lexicon ?? []).map((entry) => entry.pattern));
|
|
217
|
-
const
|
|
221
|
+
const captions = captionsOf(doc.source);
|
|
222
|
+
const isCaption = (hit: Hit): boolean => captions.has(hit.matched) || isCaptionAt(doc.source, hit.offset, hit.offset + hit.matched.length);
|
|
223
|
+
const hits = hitsOf(ownSentences(doc), CAPS_RUN).filter((hit) => isShouting(hit.matched, known, conventional) && !isCaption(hit));
|
|
218
224
|
return reportFrom(hits, options.limit);
|
|
219
225
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ProseDocument, Token } from "../plugin.ts";
|
|
2
|
+
import { nameKey, type NameMention } from "../name-variants.ts";
|
|
3
|
+
import { companyGroupBases, type CompanyMention } from "../company-names.ts";
|
|
4
|
+
import type { OrderName } from "../name-word-order.ts";
|
|
5
|
+
import { isInCapitalsPassage } from "../capitals-passage.ts";
|
|
6
|
+
import { givenNameBefore } from "../name-given-before.ts";
|
|
7
|
+
import { definedTerms } from "../structure/definition-use.ts";
|
|
8
|
+
|
|
9
|
+
// name-variant が、似た形でも別のものと見る所。大文字で書いた一節、名の違う二人、会社のグループの一社ずつ、定義した語、同じ文に
|
|
10
|
+
// 並べた二つ。
|
|
11
|
+
|
|
12
|
+
/** 大文字で書いた一節(契約書の免責の段落、大文字の見出し)の中の名前(eGAIN SYSTEM)。その節の大文字で、名前の書き方ではない。 */
|
|
13
|
+
export const outsideCapitals = <Mention extends { readonly offset: number; readonly surface: string }>(
|
|
14
|
+
prose: string,
|
|
15
|
+
mentions: readonly Mention[],
|
|
16
|
+
): Mention[] => mentions.filter((mention) => !isInCapitalsPassage(prose, mention.offset, mention.offset + mention.surface.length));
|
|
17
|
+
|
|
18
|
+
/** 名前に、すぐ前に書いた名(Jeffrey D. Walker の Jeffrey)を付ける。tokens は文書の順の語。 */
|
|
19
|
+
export const withGivenNames = (tokens: readonly Token[], source: string, mentions: readonly NameMention[]): NameMention[] => {
|
|
20
|
+
const startAt = new Map(tokens.map((token, index) => [token.span.start, index]));
|
|
21
|
+
return mentions.map((mention) => {
|
|
22
|
+
const index = startAt.get(mention.offset);
|
|
23
|
+
const givenName = index === undefined ? undefined : givenNameBefore(tokens, index, source);
|
|
24
|
+
return givenName === undefined ? mention : { ...mention, givenName };
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 会社のグループ(companyGroupBases)の一社の名前どうし(GitLab Ltd (UK) と GitLab LTD (Ireland))か。形の語の書き方の違いも、
|
|
30
|
+
* 別の会社を言い分けている。forms は会社の形の語(Inc、Ltd)。
|
|
31
|
+
*/
|
|
32
|
+
export const isGroupPairOf = (
|
|
33
|
+
companies: readonly CompanyMention[],
|
|
34
|
+
forms: readonly string[],
|
|
35
|
+
): ((found: { readonly name: string; readonly usual: string }) => boolean) => {
|
|
36
|
+
const groups = [...companyGroupBases(companies)];
|
|
37
|
+
const formKeys = forms.map(nameKey);
|
|
38
|
+
const isGroupName = (surface: string): boolean => {
|
|
39
|
+
const key = nameKey(surface);
|
|
40
|
+
return groups.some((base) => formKeys.some((form) => key === base + form));
|
|
41
|
+
};
|
|
42
|
+
return ({ name, usual }) => isGroupName(name) && isGroupName(usual);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** 位置を含む文の番号。文の外(見出し、表)は、その前の文の番号。 */
|
|
46
|
+
export const sentenceIndexOf = (doc: ProseDocument): ((offset: number) => number) => {
|
|
47
|
+
const starts = doc.sentences.map((sentence) => sentence.span.start);
|
|
48
|
+
const search = (offset: number, low: number, high: number): number => {
|
|
49
|
+
if (low >= high) return low - 1;
|
|
50
|
+
const middle = Math.floor((low + high) / 2);
|
|
51
|
+
return (starts[middle] ?? 0) <= offset ? search(offset, middle + 1, high) : search(offset, low, middle);
|
|
52
|
+
};
|
|
53
|
+
return (offset) => search(offset, 0, starts.length);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 文書が定義した語(“Company Subsidiary” means …)でない名前。定義した語は文書が作った呼び名で、同じ語を違う順に並べた所
|
|
58
|
+
* (Subsidiary of the Company)はその中身を書いたふつうの言い方。
|
|
59
|
+
*/
|
|
60
|
+
export const notDefinedTerms = (doc: ProseDocument, names: readonly OrderName[]): OrderName[] => {
|
|
61
|
+
const terms = new Set(doc.structure === undefined ? [] : definedTerms(doc.structure).map(({ term }) => term.toLowerCase()));
|
|
62
|
+
return names.filter((name) => !terms.has(name.surface.toLowerCase()));
|
|
63
|
+
};
|
|
@@ -7,7 +7,7 @@ import { lookalikeSlipsIn, type LookalikeChars } from "../name-lookalike-chars.t
|
|
|
7
7
|
import { givenNameReadingOf, readingOfWords, type CharReadings } from "../name-char-reading.ts";
|
|
8
8
|
import { nameCueAt, type NameCues } from "../name-cue.ts";
|
|
9
9
|
import { quoteAt } from "./structure-tree.ts";
|
|
10
|
-
import { companyMentionsIn, companyVariants, type CompanyForm, type IsProper, type PredicateEnds } from "../company-names.ts";
|
|
10
|
+
import { companyMentionsIn, companyVariants, type CompanyForm, type CompanyMention, type IsProper, type PredicateEnds } from "../company-names.ts";
|
|
11
11
|
import { kanaSpelledCompanies, type ReadWord } from "../company-kana-spelling.ts";
|
|
12
12
|
import { proseAndTablesOf } from "../table-text.ts";
|
|
13
13
|
import { tableBodyCells } from "../facts/table-facts.ts";
|
|
@@ -23,6 +23,7 @@ import { spacedPersonNamesIn } from "../spaced-person-name.ts";
|
|
|
23
23
|
import { isOrdinaryToken, withOrdinaryWords } from "../name-ordinary-word.ts";
|
|
24
24
|
import { isInPersonField } from "../person-field.ts";
|
|
25
25
|
import { labelledSpans, orderNamesOf, quotedSpans, stemOf, titleCaseSpans, wordOrderVariants, type OrderWord } from "../name-word-order.ts";
|
|
26
|
+
import { isGroupPairOf, notDefinedTerms, outsideCapitals, sentenceIndexOf, withGivenNames } from "./name-variant-apart.ts";
|
|
26
27
|
|
|
27
28
|
// 人の名前と読ませる敬称(様、さん)は語彙表 person-suffix、人を指す前置き(担当の)は person-lead、名前のすぐ後ろに来る語
|
|
28
29
|
// (です、まで)は name-particle、字体の違う同じ字(斎・斉・齋)は name-variant-char が組(group)ごとに言う。
|
|
@@ -145,20 +146,22 @@ const predicateEndsOf = (doc: ProseDocument): PredicateEnds =>
|
|
|
145
146
|
const readWordsOf = (doc: ProseDocument): ReadWord[] =>
|
|
146
147
|
tokensOf(doc).map((token) => ({ start: token.span.start, end: token.span.end, surface: token.surface, reading: token.reading }));
|
|
147
148
|
|
|
149
|
+
/** 会社の名前の現れ。大文字で書いた一節の中のものは除く。 */
|
|
150
|
+
const companyMentionsOf = (doc: ProseDocument, prose: string): CompanyMention[] =>
|
|
151
|
+
outsideCapitals(prose, companyMentionsIn(prose, [...companyFormsOf(doc), ...foreignFormsOf(doc)], properOf(doc), kanaStopsOf(doc), predicateEndsOf(doc)));
|
|
152
|
+
|
|
148
153
|
/**
|
|
149
154
|
* 会社の名前の書き分けと、名前の漢字一字をその読みのかなで書いた所(みどり野 と みどりの)。名前の見方がすでに指した所と重なる
|
|
150
155
|
* ものは除く。
|
|
151
156
|
*/
|
|
152
|
-
const companyFindings = (doc: ProseDocument, prose: string, reported: readonly Reported[]): Reported[] => {
|
|
153
|
-
const stops = kanaStopsOf(doc);
|
|
154
|
-
const mentions = companyMentionsIn(prose, [...companyFormsOf(doc), ...foreignFormsOf(doc)], properOf(doc), stops, predicateEndsOf(doc));
|
|
157
|
+
const companyFindings = (doc: ProseDocument, prose: string, mentions: readonly CompanyMention[], reported: readonly Reported[]): Reported[] => {
|
|
155
158
|
const variants = companyVariants(mentions).map(({ mention, usual, kind }) => ({
|
|
156
159
|
offset: mention.offset,
|
|
157
160
|
name: mention.surface,
|
|
158
161
|
usual,
|
|
159
162
|
kind: kind === "spelling" ? kind : `company-${kind}`,
|
|
160
163
|
}));
|
|
161
|
-
const kana = kanaSpelledCompanies(prose, mentions, readWordsOf(doc),
|
|
164
|
+
const kana = kanaSpelledCompanies(prose, mentions, readWordsOf(doc), kanaStopsOf(doc).particles)
|
|
162
165
|
.map(({ surface, offset, usual }) => ({ offset, name: surface, usual, kind: "company-kana" }))
|
|
163
166
|
.filter((found) => !overlapsAny(variants, found.offset, found.name.length));
|
|
164
167
|
return [...variants, ...kana].filter(({ offset, name }) => !overlapsAny(reported, offset, name.length));
|
|
@@ -207,6 +210,9 @@ const orderWordsOf = (doc: ProseDocument): OrderWord[] => {
|
|
|
207
210
|
.map((token) => ({ start: token.span.start, end: token.span.end, key: stemOf(token.surface, suffixes) }));
|
|
208
211
|
};
|
|
209
212
|
|
|
213
|
+
/** 語彙表 name-title-joiner の、前置詞の後ろにだけ立つ冠詞(the)の組。 */
|
|
214
|
+
const ARTICLE = "article";
|
|
215
|
+
|
|
210
216
|
/**
|
|
211
217
|
* 語の順を入れ替えて書いた同じ名前(統計学基礎 と 基礎統計学)。名前と読める所は、括弧の中(name-quote)、大文字で始まる語の並び
|
|
212
218
|
* (name-title-joiner を挟んでよい)、名前を書く欄の後ろ(name-label)。大文字の語の並びどうしは、片方が表の升まるごとに
|
|
@@ -214,13 +220,20 @@ const orderWordsOf = (doc: ProseDocument): OrderWord[] => {
|
|
|
214
220
|
*/
|
|
215
221
|
const orderFindings = (doc: ProseDocument, prose: string, reported: readonly Reported[]): Reported[] => {
|
|
216
222
|
const starts = sentenceStartsOf(doc);
|
|
223
|
+
const joiners = doc.lexicons["name-title-joiner"] ?? [];
|
|
217
224
|
const spans = [
|
|
218
225
|
...quotedSpans(prose, patternsOf(doc, "name-quote")),
|
|
219
|
-
...titleCaseSpans(
|
|
226
|
+
...titleCaseSpans(
|
|
227
|
+
prose,
|
|
228
|
+
joiners.filter((entry) => entry.group !== ARTICLE).map((entry) => entry.pattern),
|
|
229
|
+
(offset) => starts.has(offset),
|
|
230
|
+
joiners.filter((entry) => entry.group === ARTICLE).map((entry) => entry.pattern),
|
|
231
|
+
),
|
|
220
232
|
...labelledSpans(prose, patternsOf(doc, "name-label")),
|
|
221
233
|
];
|
|
222
234
|
const cells = new Set(tableBodyCells(proseAndTablesOf(doc)).map((cell) => cell.text.trim()));
|
|
223
|
-
|
|
235
|
+
const names = notDefinedTerms(doc, orderNamesOf(doc.source, spans, orderWordsOf(doc), orderStopsOf(doc)));
|
|
236
|
+
return wordOrderVariants(names, cells, sentenceIndexOf(doc))
|
|
224
237
|
.filter(({ name }) => !overlapsAny(reported, name.offset, name.surface.length))
|
|
225
238
|
.map(({ name, usual }) => ({ offset: name.offset, name: name.surface, usual, kind: "order" }));
|
|
226
239
|
};
|
|
@@ -270,7 +283,7 @@ const tokenRunsOf = (sentence: Sentence): readonly (readonly Token[])[] => {
|
|
|
270
283
|
const nameMentionsOf = (doc: ProseDocument, prose: string, chars: VariantChars): NameMention[] => {
|
|
271
284
|
const cues: NameCues = { leads: patternsOf(doc, "person-lead"), suffixes: patternsOf(doc, "person-suffix"), particles: patternsOf(doc, "name-particle") };
|
|
272
285
|
const charReadings = charReadingsOf(doc);
|
|
273
|
-
const tagged = doc.sentences.flatMap((sentence) => tokenRunsOf(sentence).flatMap((tokens) => mentionsIn(tokens,
|
|
286
|
+
const tagged = doc.sentences.flatMap((sentence) => tokenRunsOf(sentence).flatMap((tokens) => mentionsIn(tokens, prose, cues.suffixes, charReadings)));
|
|
274
287
|
const taggedOrSuffixed = [...tagged, ...suffixedNamesIn(prose, cues.suffixes, chars, tagged)];
|
|
275
288
|
const cued = [...taggedOrSuffixed, ...cuedNamesIn(prose, cues, chars, taggedOrSuffixed)].map((mention): NameMention => {
|
|
276
289
|
const cue = mention.cue ?? nameCueAt(prose, mention.offset, mention.surface, cues);
|
|
@@ -281,7 +294,7 @@ const nameMentionsOf = (doc: ProseDocument, prose: string, chars: VariantChars):
|
|
|
281
294
|
const outside = joined.filter(
|
|
282
295
|
(mention) => !spaced.some((name) => name.offset < mention.offset + mention.surface.length && mention.offset < name.offset + name.surface.length),
|
|
283
296
|
);
|
|
284
|
-
return withKnownNeighbours([...outside, ...spaced], prose).toSorted((left, right) => left.offset - right.offset);
|
|
297
|
+
return outsideCapitals(prose, withKnownNeighbours([...outside, ...spaced], prose)).toSorted((left, right) => left.offset - right.offset);
|
|
285
298
|
};
|
|
286
299
|
|
|
287
300
|
/** 人の名前で形の似た別の字(太 と 大)は語彙表 name-lookalike-char が組(group)ごとに言う。 */
|
|
@@ -352,10 +365,15 @@ const wordUseOf = (doc: ProseDocument): WordUse => {
|
|
|
352
365
|
export const nameVariant: Detector = (doc): Finding[] => {
|
|
353
366
|
const prose = doc.prose ?? doc.source;
|
|
354
367
|
const chars = variantCharsOf(doc);
|
|
355
|
-
const mentions = labelledNamesOf(doc, ordinaryNamesOf(doc, nameMentionsOf(doc, prose, chars)));
|
|
368
|
+
const mentions = labelledNamesOf(doc, ordinaryNamesOf(doc, withGivenNames(tokensOf(doc), doc.source, nameMentionsOf(doc, prose, chars))));
|
|
369
|
+
const companies = companyMentionsOf(doc, prose);
|
|
370
|
+
const inGroup = isGroupPairOf(
|
|
371
|
+
companies,
|
|
372
|
+
companyFormsOf(doc).map((form) => form.pattern),
|
|
373
|
+
);
|
|
356
374
|
const variants = nameVariants(mentions, chars, spellingInputOf(doc), wordUseOf(doc))
|
|
357
375
|
.map(({ mention, usual, kind }): Reported => ({ offset: mention.offset, name: mention.surface, usual, kind }))
|
|
358
|
-
.filter(notSpacingOnly);
|
|
376
|
+
.filter((found) => notSpacingOnly(found) && !inGroup(found));
|
|
359
377
|
const lookalikes = lookalikeSlipsIn(mentions, lookalikeCharsOf(doc)).map(({ mention, usual }) => ({
|
|
360
378
|
offset: mention.offset,
|
|
361
379
|
name: mention.surface,
|
|
@@ -363,7 +381,7 @@ export const nameVariant: Detector = (doc): Finding[] => {
|
|
|
363
381
|
kind: "lookalike",
|
|
364
382
|
}));
|
|
365
383
|
const names = [...variants, ...lookalikes.filter((found) => !overlapsAny(variants, found.offset, found.name.length))];
|
|
366
|
-
const namesAndCompanies = [...names, ...companyFindings(doc, prose, names)];
|
|
384
|
+
const namesAndCompanies = [...names, ...companyFindings(doc, prose, companies, names)];
|
|
367
385
|
const withTables = [...namesAndCompanies, ...tableFindings(doc, prose, mentions, namesAndCompanies).filter(notSpacingOnly)];
|
|
368
386
|
const withPlaces = [...withTables, ...placeFindings(doc, prose, withTables, chars)];
|
|
369
387
|
const withProducts = [...withPlaces, ...productFindings(doc, prose, withPlaces)];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 大文字の並びでも略語として書かれないもの。どれも字の形だけで決まる。
|
|
3
|
+
* - I だけのローマ数字(II、III)。名前の後ろでなくても番号で(Doswell, III、1964: II Structure)、この形の略語は使われない。
|
|
4
|
+
* IV や VI のように V・X を含む数字は略語(静脈注射の IV)でもあるので、名前の番号(name-numeral.ts)の位置でだけ外す。
|
|
5
|
+
* - 大文字で始まる語に - で繋いだ I・V・X の数字(Association-IV)。名前の番号を空白でなく - で書いた形。
|
|
6
|
+
* - X を 3 つ以上並べた伏せ字(the Secretary of State for XXX)や X の数で書く染色体の型(XXX症候群)。KKK や WWW のような他の字の繰り返しは略語のまま数える。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type Span = { readonly start: number; readonly end: number };
|
|
10
|
+
|
|
11
|
+
// 略語の切り出しと同じ境目。日本語の文字(XXX症候群)は語の続きではない。
|
|
12
|
+
const NOT_BEFORE = String.raw`(?<![A-Za-z0-9_&])`;
|
|
13
|
+
const NOT_AFTER = String.raw`(?![A-Za-z0-9_&])`;
|
|
14
|
+
|
|
15
|
+
const ONES_NUMERAL = new RegExp(`${NOT_BEFORE}I{2,3}${NOT_AFTER}`, "gu");
|
|
16
|
+
const MASK = new RegExp(`${NOT_BEFORE}X{3,}${NOT_AFTER}`, "gu");
|
|
17
|
+
const SMALL_NUMERAL = "(?=[IVX])X{0,3}(?:IX|IV|V?I{0,3})";
|
|
18
|
+
const JOINED_NUMERAL = new RegExp(String.raw`(?<![A-Za-z0-9_-])\p{Lu}\p{Ll}+-(?<numeral>${SMALL_NUMERAL})${NOT_AFTER}`, "gu");
|
|
19
|
+
|
|
20
|
+
const spanOf = (match: RegExpExecArray): Span => ({ start: match.index, end: match.index + match[0].length });
|
|
21
|
+
|
|
22
|
+
const joinedSpans = (text: string): Span[] =>
|
|
23
|
+
[...text.matchAll(JOINED_NUMERAL)].map((match) => {
|
|
24
|
+
const end = match.index + match[0].length;
|
|
25
|
+
return { start: end - (match.groups?.["numeral"] ?? "").length, end };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** 文の中の、番号か伏せ字と読む大文字の範囲。 */
|
|
29
|
+
export const numeralWordSpans = (text: string): Span[] => [
|
|
30
|
+
...[...text.matchAll(ONES_NUMERAL)].map(spanOf),
|
|
31
|
+
...[...text.matchAll(MASK)].map(spanOf),
|
|
32
|
+
...joinedSpans(text),
|
|
33
|
+
];
|