chaffjs 0.16.0 → 0.18.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/announced-count.d.ts +26 -0
- package/dist/announced-count.d.ts.map +1 -0
- package/dist/announced-count.js +104 -0
- package/dist/announced-count.js.map +1 -0
- package/dist/bare-url.d.ts +14 -0
- package/dist/bare-url.d.ts.map +1 -0
- package/dist/bare-url.js +28 -0
- package/dist/bare-url.js.map +1 -0
- package/dist/baseline.d.ts +5 -0
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +16 -1
- package/dist/baseline.js.map +1 -1
- package/dist/body-section.d.ts +7 -0
- package/dist/body-section.d.ts.map +1 -0
- package/dist/body-section.js +6 -0
- package/dist/body-section.js.map +1 -0
- package/dist/calendar-number.d.ts +9 -2
- package/dist/calendar-number.d.ts.map +1 -1
- package/dist/calendar-number.js +18 -4
- package/dist/calendar-number.js.map +1 -1
- package/dist/cli-args.d.ts +4 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +5 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +8 -4
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.js +33 -30
- package/dist/cli.js.map +1 -1
- package/dist/closing-numbers.d.ts +20 -0
- package/dist/closing-numbers.d.ts.map +1 -0
- package/dist/closing-numbers.js +56 -0
- package/dist/closing-numbers.js.map +1 -0
- package/dist/commands/compare.d.ts +8 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +72 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +17 -9
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/feedback.d.ts +5 -2
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +11 -5
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +13 -13
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts +13 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +35 -5
- package/dist/commands/tree.js.map +1 -1
- package/dist/compare/atom.d.ts +31 -0
- package/dist/compare/atom.d.ts.map +1 -0
- package/dist/compare/atom.js +4 -0
- package/dist/compare/atom.js.map +1 -0
- package/dist/compare/clock-time.d.ts +8 -0
- package/dist/compare/clock-time.d.ts.map +1 -0
- package/dist/compare/clock-time.js +65 -0
- package/dist/compare/clock-time.js.map +1 -0
- package/dist/compare/extract.d.ts +15 -0
- package/dist/compare/extract.d.ts.map +1 -0
- package/dist/compare/extract.js +58 -0
- package/dist/compare/extract.js.map +1 -0
- package/dist/compare/fact-text.d.ts +23 -0
- package/dist/compare/fact-text.d.ts.map +1 -0
- package/dist/compare/fact-text.js +47 -0
- package/dist/compare/fact-text.js.map +1 -0
- package/dist/compare/markup-atoms.d.ts +11 -0
- package/dist/compare/markup-atoms.d.ts.map +1 -0
- package/dist/compare/markup-atoms.js +23 -0
- package/dist/compare/markup-atoms.js.map +1 -0
- package/dist/compare/match.d.ts +19 -0
- package/dist/compare/match.d.ts.map +1 -0
- package/dist/compare/match.js +84 -0
- package/dist/compare/match.js.map +1 -0
- package/dist/compare/outcome.d.ts +33 -0
- package/dist/compare/outcome.d.ts.map +1 -0
- package/dist/compare/outcome.js +40 -0
- package/dist/compare/outcome.js.map +1 -0
- package/dist/compare/proper-nouns.d.ts +19 -0
- package/dist/compare/proper-nouns.d.ts.map +1 -0
- package/dist/compare/proper-nouns.js +44 -0
- package/dist/compare/proper-nouns.js.map +1 -0
- package/dist/compare/render.d.ts +9 -0
- package/dist/compare/render.d.ts.map +1 -0
- package/dist/compare/render.js +64 -0
- package/dist/compare/render.js.map +1 -0
- package/dist/compare/spans.d.ts +9 -0
- package/dist/compare/spans.d.ts.map +1 -0
- package/dist/compare/spans.js +11 -0
- package/dist/compare/spans.js.map +1 -0
- package/dist/compare/text-atoms.d.ts +24 -0
- package/dist/compare/text-atoms.d.ts.map +1 -0
- package/dist/compare/text-atoms.js +67 -0
- package/dist/compare/text-atoms.js.map +1 -0
- package/dist/compare/text.d.ts +24 -0
- package/dist/compare/text.d.ts.map +1 -0
- package/dist/compare/text.js +90 -0
- package/dist/compare/text.js.map +1 -0
- package/dist/compare/tree-atoms.d.ts +20 -0
- package/dist/compare/tree-atoms.d.ts.map +1 -0
- package/dist/compare/tree-atoms.js +58 -0
- package/dist/compare/tree-atoms.js.map +1 -0
- package/dist/compare/written-names.d.ts +8 -0
- package/dist/compare/written-names.d.ts.map +1 -0
- package/dist/compare/written-names.js +24 -0
- package/dist/compare/written-names.js.map +1 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +5 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/option-problems.d.ts +16 -0
- package/dist/config/option-problems.d.ts.map +1 -0
- package/dist/config/option-problems.js +58 -0
- package/dist/config/option-problems.js.map +1 -0
- package/dist/config/read.d.ts +4 -0
- package/dist/config/read.d.ts.map +1 -0
- package/dist/config/read.js +8 -0
- package/dist/config/read.js.map +1 -0
- package/dist/config/rule-problems.d.ts +2 -4
- package/dist/config/rule-problems.d.ts.map +1 -1
- package/dist/config/rule-problems.js +7 -1
- package/dist/config/rule-problems.js.map +1 -1
- package/dist/config/style.d.ts +16 -0
- package/dist/config/style.d.ts.map +1 -0
- package/dist/config/style.js +36 -0
- package/dist/config/style.js.map +1 -0
- package/dist/config/warnings.d.ts +6 -0
- package/dist/config/warnings.d.ts.map +1 -0
- package/dist/config/warnings.js +10 -0
- package/dist/config/warnings.js.map +1 -0
- package/dist/config/write.d.ts.map +1 -1
- package/dist/config/write.js +17 -4
- package/dist/config/write.js.map +1 -1
- package/dist/custom/bounded-match.d.ts +15 -0
- package/dist/custom/bounded-match.d.ts.map +1 -0
- package/dist/custom/bounded-match.js +43 -0
- package/dist/custom/bounded-match.js.map +1 -0
- package/dist/custom/load.d.ts +8 -0
- package/dist/custom/load.d.ts.map +1 -0
- package/dist/custom/load.js +11 -0
- package/dist/custom/load.js.map +1 -0
- package/dist/custom/parse.d.ts +70 -0
- package/dist/custom/parse.d.ts.map +1 -0
- package/dist/custom/parse.js +215 -0
- package/dist/custom/parse.js.map +1 -0
- package/dist/custom/problems.d.ts +5 -0
- package/dist/custom/problems.d.ts.map +1 -0
- package/dist/custom/problems.js +78 -0
- package/dist/custom/problems.js.map +1 -0
- package/dist/custom/regex-safety.d.ts +11 -0
- package/dist/custom/regex-safety.d.ts.map +1 -0
- package/dist/custom/regex-safety.js +110 -0
- package/dist/custom/regex-safety.js.map +1 -0
- package/dist/custom/token-pattern.d.ts +8 -0
- package/dist/custom/token-pattern.d.ts.map +1 -0
- package/dist/custom/token-pattern.js +75 -0
- package/dist/custom/token-pattern.js.map +1 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +24 -2
- package/dist/detect.js.map +1 -1
- package/dist/detectors/acronym-context.d.ts +4 -0
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +25 -4
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-expansion.d.ts +2 -0
- package/dist/detectors/acronym-expansion.d.ts.map +1 -1
- package/dist/detectors/acronym-expansion.js +104 -1
- package/dist/detectors/acronym-expansion.js.map +1 -1
- package/dist/detectors/agreement-slip.d.ts +32 -0
- package/dist/detectors/agreement-slip.d.ts.map +1 -0
- package/dist/detectors/agreement-slip.js +137 -0
- package/dist/detectors/agreement-slip.js.map +1 -0
- package/dist/detectors/ai-phrasing.d.ts +20 -0
- package/dist/detectors/ai-phrasing.d.ts.map +1 -0
- package/dist/detectors/ai-phrasing.js +112 -0
- package/dist/detectors/ai-phrasing.js.map +1 -0
- package/dist/detectors/announced-count.d.ts +4 -0
- package/dist/detectors/announced-count.d.ts.map +1 -0
- package/dist/detectors/announced-count.js +20 -0
- package/dist/detectors/announced-count.js.map +1 -0
- package/dist/detectors/broken-link.d.ts +17 -0
- package/dist/detectors/broken-link.d.ts.map +1 -0
- package/dist/detectors/broken-link.js +66 -0
- package/dist/detectors/broken-link.js.map +1 -0
- package/dist/detectors/cited-name.d.ts.map +1 -1
- package/dist/detectors/cited-name.js +4 -4
- package/dist/detectors/cited-name.js.map +1 -1
- package/dist/detectors/cited-title.d.ts +11 -0
- package/dist/detectors/cited-title.d.ts.map +1 -0
- package/dist/detectors/cited-title.js +75 -0
- package/dist/detectors/cited-title.js.map +1 -0
- package/dist/detectors/concrete-evidence.d.ts +1 -1
- package/dist/detectors/concrete-evidence.d.ts.map +1 -1
- package/dist/detectors/concrete-evidence.js +2 -1
- package/dist/detectors/concrete-evidence.js.map +1 -1
- package/dist/detectors/content-morphemes.d.ts +4 -0
- package/dist/detectors/content-morphemes.d.ts.map +1 -0
- package/dist/detectors/content-morphemes.js +12 -0
- package/dist/detectors/content-morphemes.js.map +1 -0
- package/dist/detectors/contrast-frame.d.ts +17 -0
- package/dist/detectors/contrast-frame.d.ts.map +1 -0
- package/dist/detectors/contrast-frame.js +32 -0
- package/dist/detectors/contrast-frame.js.map +1 -0
- package/dist/detectors/custom.d.ts +8 -0
- package/dist/detectors/custom.d.ts.map +1 -0
- package/dist/detectors/custom.js +71 -0
- package/dist/detectors/custom.js.map +1 -0
- package/dist/detectors/dangling-figure.d.ts +4 -0
- package/dist/detectors/dangling-figure.d.ts.map +1 -0
- package/dist/detectors/dangling-figure.js +18 -0
- package/dist/detectors/dangling-figure.js.map +1 -0
- package/dist/detectors/doubled-punctuation.d.ts +9 -0
- package/dist/detectors/doubled-punctuation.d.ts.map +1 -0
- package/dist/detectors/doubled-punctuation.js +33 -0
- package/dist/detectors/doubled-punctuation.js.map +1 -0
- package/dist/detectors/doubled-word.d.ts +5 -0
- package/dist/detectors/doubled-word.d.ts.map +1 -1
- package/dist/detectors/doubled-word.js +24 -11
- package/dist/detectors/doubled-word.js.map +1 -1
- package/dist/detectors/en-shape.d.ts +0 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +0 -235
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/enumerated-runs.d.ts +1 -1
- package/dist/detectors/enumerated-runs.d.ts.map +1 -1
- package/dist/detectors/enumerated-runs.js +15 -4
- package/dist/detectors/enumerated-runs.js.map +1 -1
- package/dist/detectors/gram-furniture.d.ts +6 -0
- package/dist/detectors/gram-furniture.d.ts.map +1 -0
- package/dist/detectors/gram-furniture.js +21 -0
- package/dist/detectors/gram-furniture.js.map +1 -0
- package/dist/detectors/gram-place.d.ts +2 -0
- package/dist/detectors/gram-place.d.ts.map +1 -1
- package/dist/detectors/gram-place.js +9 -7
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/gram-windows.d.ts +9 -0
- package/dist/detectors/gram-windows.d.ts.map +1 -0
- package/dist/detectors/gram-windows.js +27 -0
- package/dist/detectors/gram-windows.js.map +1 -0
- package/dist/detectors/heading-case.d.ts +2 -0
- package/dist/detectors/heading-case.d.ts.map +1 -1
- package/dist/detectors/heading-case.js +29 -2
- package/dist/detectors/heading-case.js.map +1 -1
- package/dist/detectors/heading-code-words.d.ts +6 -0
- package/dist/detectors/heading-code-words.d.ts.map +1 -0
- package/dist/detectors/heading-code-words.js +29 -0
- package/dist/detectors/heading-code-words.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +17 -20
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/heading-level-skip.d.ts +12 -0
- package/dist/detectors/heading-level-skip.d.ts.map +1 -0
- package/dist/detectors/heading-level-skip.js +11 -0
- package/dist/detectors/heading-level-skip.js.map +1 -0
- package/dist/detectors/heading-overlap.d.ts +13 -0
- package/dist/detectors/heading-overlap.d.ts.map +1 -0
- package/dist/detectors/heading-overlap.js +52 -0
- package/dist/detectors/heading-overlap.js.map +1 -0
- package/dist/detectors/image-alt-text.d.ts +3 -0
- package/dist/detectors/image-alt-text.d.ts.map +1 -0
- package/dist/detectors/image-alt-text.js +9 -0
- package/dist/detectors/image-alt-text.js.map +1 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +40 -2
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/kutoten-consistency.d.ts +24 -0
- package/dist/detectors/kutoten-consistency.d.ts.map +1 -0
- package/dist/detectors/kutoten-consistency.js +77 -0
- package/dist/detectors/kutoten-consistency.js.map +1 -0
- package/dist/detectors/lead-in.d.ts +1 -0
- package/dist/detectors/lead-in.d.ts.map +1 -1
- package/dist/detectors/lead-in.js +2 -1
- package/dist/detectors/lead-in.js.map +1 -1
- package/dist/detectors/letter-joined-name.d.ts +13 -0
- package/dist/detectors/letter-joined-name.d.ts.map +1 -0
- package/dist/detectors/letter-joined-name.js +11 -0
- package/dist/detectors/letter-joined-name.js.map +1 -0
- package/dist/detectors/lexicon-match.d.ts +4 -0
- package/dist/detectors/lexicon-match.d.ts.map +1 -1
- package/dist/detectors/lexicon-match.js +7 -2
- package/dist/detectors/lexicon-match.js.map +1 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +5 -2
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/list-item.d.ts +45 -0
- package/dist/detectors/list-item.d.ts.map +1 -0
- package/dist/detectors/list-item.js +88 -0
- package/dist/detectors/list-item.js.map +1 -0
- package/dist/detectors/list-items.d.ts +31 -0
- package/dist/detectors/list-items.d.ts.map +1 -0
- package/dist/detectors/list-items.js +113 -0
- package/dist/detectors/list-items.js.map +1 -0
- package/dist/detectors/list-lead-in.d.ts +7 -0
- package/dist/detectors/list-lead-in.d.ts.map +1 -0
- package/dist/detectors/list-lead-in.js +28 -0
- package/dist/detectors/list-lead-in.js.map +1 -0
- package/dist/detectors/list-sentence.d.ts +65 -0
- package/dist/detectors/list-sentence.d.ts.map +1 -0
- package/dist/detectors/list-sentence.js +105 -0
- package/dist/detectors/list-sentence.js.map +1 -0
- package/dist/detectors/list-view.d.ts +34 -0
- package/dist/detectors/list-view.d.ts.map +1 -0
- package/dist/detectors/list-view.js +137 -0
- package/dist/detectors/list-view.js.map +1 -0
- package/dist/detectors/long-vowel.d.ts +7 -0
- package/dist/detectors/long-vowel.d.ts.map +1 -0
- package/dist/detectors/long-vowel.js +57 -0
- package/dist/detectors/long-vowel.js.map +1 -0
- package/dist/detectors/markup-finding.d.ts +6 -0
- package/dist/detectors/markup-finding.d.ts.map +1 -0
- package/dist/detectors/markup-finding.js +19 -0
- package/dist/detectors/markup-finding.js.map +1 -0
- package/dist/detectors/name-numeral.d.ts +1 -1
- package/dist/detectors/name-numeral.d.ts.map +1 -1
- package/dist/detectors/name-numeral.js +2 -7
- package/dist/detectors/name-numeral.js.map +1 -1
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +16 -12
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/oxford-comma.d.ts +9 -0
- package/dist/detectors/oxford-comma.d.ts.map +1 -0
- package/dist/detectors/oxford-comma.js +246 -0
- package/dist/detectors/oxford-comma.js.map +1 -0
- package/dist/detectors/place-run.d.ts.map +1 -1
- package/dist/detectors/place-run.js +10 -6
- package/dist/detectors/place-run.js.map +1 -1
- package/dist/detectors/placeholder-text.d.ts +9 -0
- package/dist/detectors/placeholder-text.d.ts.map +1 -0
- package/dist/detectors/placeholder-text.js +18 -0
- package/dist/detectors/placeholder-text.js.map +1 -0
- package/dist/detectors/quote-around.d.ts +3 -0
- package/dist/detectors/quote-around.d.ts.map +1 -0
- package/dist/detectors/quote-around.js +8 -0
- package/dist/detectors/quote-around.js.map +1 -0
- package/dist/detectors/register.d.ts +11 -3
- package/dist/detectors/register.d.ts.map +1 -1
- package/dist/detectors/register.js +28 -9
- package/dist/detectors/register.js.map +1 -1
- package/dist/detectors/roman-numeral.d.ts +2 -0
- package/dist/detectors/roman-numeral.d.ts.map +1 -1
- package/dist/detectors/roman-numeral.js +2 -0
- package/dist/detectors/roman-numeral.js.map +1 -1
- package/dist/detectors/sentence-ending.d.ts +17 -1
- package/dist/detectors/sentence-ending.d.ts.map +1 -1
- package/dist/detectors/sentence-ending.js +13 -7
- package/dist/detectors/sentence-ending.js.map +1 -1
- package/dist/detectors/sentence-length.d.ts.map +1 -1
- package/dist/detectors/sentence-length.js +13 -4
- package/dist/detectors/sentence-length.js.map +1 -1
- package/dist/detectors/signals.d.ts +8 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +94 -54
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/stacked-hedge.d.ts +0 -7
- package/dist/detectors/stacked-hedge.d.ts.map +1 -1
- package/dist/detectors/stacked-hedge.js +6 -3
- package/dist/detectors/stacked-hedge.js.map +1 -1
- package/dist/detectors/stray-space.d.ts +7 -0
- package/dist/detectors/stray-space.d.ts.map +1 -0
- package/dist/detectors/stray-space.js +34 -0
- package/dist/detectors/stray-space.js.map +1 -0
- package/dist/detectors/structure-tree.d.ts +6 -0
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +37 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/structure.d.ts +4 -4
- package/dist/detectors/structure.d.ts.map +1 -1
- package/dist/detectors/structure.js +7 -6
- package/dist/detectors/structure.js.map +1 -1
- package/dist/detectors/superlative-name.d.ts.map +1 -1
- package/dist/detectors/superlative-name.js +8 -5
- package/dist/detectors/superlative-name.js.map +1 -1
- package/dist/detectors/superlative-scope.d.ts.map +1 -1
- package/dist/detectors/superlative-scope.js +9 -4
- package/dist/detectors/superlative-scope.js.map +1 -1
- package/dist/detectors/token-column.d.ts +46 -0
- package/dist/detectors/token-column.d.ts.map +1 -0
- package/dist/detectors/token-column.js +95 -0
- package/dist/detectors/token-column.js.map +1 -0
- package/dist/detectors/unbalanced-bracket.d.ts +22 -0
- package/dist/detectors/unbalanced-bracket.d.ts.map +1 -0
- package/dist/detectors/unbalanced-bracket.js +119 -0
- package/dist/detectors/unbalanced-bracket.js.map +1 -0
- package/dist/detectors/url-run-on.d.ts +9 -0
- package/dist/detectors/url-run-on.d.ts.map +1 -0
- package/dist/detectors/url-run-on.js +19 -0
- package/dist/detectors/url-run-on.js.map +1 -0
- package/dist/document-sections.d.ts +13 -0
- package/dist/document-sections.d.ts.map +1 -0
- package/dist/document-sections.js +32 -0
- package/dist/document-sections.js.map +1 -0
- package/dist/document.d.ts +11 -4
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +99 -95
- package/dist/document.js.map +1 -1
- package/dist/email-parts.d.ts +28 -0
- package/dist/email-parts.d.ts.map +1 -0
- package/dist/email-parts.js +230 -0
- package/dist/email-parts.js.map +1 -0
- package/dist/eval.d.ts +2 -1
- package/dist/eval.d.ts.map +1 -1
- package/dist/eval.js +16 -4
- package/dist/eval.js.map +1 -1
- package/dist/figure-references.d.ts +30 -0
- package/dist/figure-references.d.ts.map +1 -0
- package/dist/figure-references.js +76 -0
- package/dist/figure-references.js.map +1 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +4 -0
- package/dist/files.js.map +1 -1
- package/dist/genre-parse.d.ts +5 -1
- package/dist/genre-parse.d.ts.map +1 -1
- package/dist/genre-parse.js +16 -3
- package/dist/genre-parse.js.map +1 -1
- package/dist/genre-suggest.d.ts.map +1 -1
- package/dist/genre-suggest.js +15 -1
- package/dist/genre-suggest.js.map +1 -1
- package/dist/heading-label.d.ts +22 -0
- package/dist/heading-label.d.ts.map +1 -0
- package/dist/heading-label.js +50 -0
- package/dist/heading-label.js.map +1 -0
- package/dist/in-page-nav.d.ts +4 -1
- package/dist/in-page-nav.d.ts.map +1 -1
- package/dist/in-page-nav.js +53 -18
- package/dist/in-page-nav.js.map +1 -1
- package/dist/joined-view.d.ts.map +1 -1
- package/dist/joined-view.js +1 -0
- package/dist/joined-view.js.map +1 -1
- package/dist/levels.d.ts +5 -1
- package/dist/levels.d.ts.map +1 -1
- package/dist/levels.js +9 -0
- package/dist/levels.js.map +1 -1
- package/dist/line-paragraphs.d.ts +8 -0
- package/dist/line-paragraphs.d.ts.map +1 -1
- package/dist/line-paragraphs.js +36 -11
- package/dist/line-paragraphs.js.map +1 -1
- package/dist/long-vowel.d.ts +35 -0
- package/dist/long-vowel.d.ts.map +1 -0
- package/dist/long-vowel.js +71 -0
- package/dist/long-vowel.js.map +1 -0
- package/dist/markdown-node.d.ts +19 -0
- package/dist/markdown-node.d.ts.map +1 -0
- package/dist/markdown-node.js +6 -0
- package/dist/markdown-node.js.map +1 -0
- package/dist/markdown-read.d.ts +17 -0
- package/dist/markdown-read.d.ts.map +1 -0
- package/dist/markdown-read.js +37 -0
- package/dist/markdown-read.js.map +1 -0
- package/dist/markup.d.ts +22 -0
- package/dist/markup.d.ts.map +1 -0
- package/dist/markup.js +180 -0
- package/dist/markup.js.map +1 -0
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +10 -16
- package/dist/mask.js.map +1 -1
- package/dist/number-name.d.ts +21 -5
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +120 -18
- package/dist/number-name.js.map +1 -1
- package/dist/opening-line.d.ts +3 -0
- package/dist/opening-line.d.ts.map +1 -0
- package/dist/opening-line.js +14 -0
- package/dist/opening-line.js.map +1 -0
- package/dist/orthography.d.ts +7 -1
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +16 -5
- package/dist/orthography.js.map +1 -1
- package/dist/page-furniture.d.ts +5 -2
- package/dist/page-furniture.d.ts.map +1 -1
- package/dist/page-furniture.js +8 -2
- package/dist/page-furniture.js.map +1 -1
- package/dist/paragraph-length.d.ts +7 -0
- package/dist/paragraph-length.d.ts.map +1 -0
- package/dist/paragraph-length.js +10 -0
- package/dist/paragraph-length.js.map +1 -0
- package/dist/plain-source.d.ts +1 -0
- package/dist/plain-source.d.ts.map +1 -1
- package/dist/plain-source.js +2 -1
- package/dist/plain-source.js.map +1 -1
- package/dist/plugin.d.ts +121 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/for-file.d.ts.map +1 -1
- package/dist/profile/for-file.js +3 -1
- package/dist/profile/for-file.js.map +1 -1
- package/dist/quoted-span.d.ts +0 -2
- package/dist/quoted-span.d.ts.map +1 -1
- package/dist/quoted-span.js +9 -4
- package/dist/quoted-span.js.map +1 -1
- package/dist/reasons.d.ts +3 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +6 -0
- package/dist/reasons.js.map +1 -1
- package/dist/reference-lists.d.ts +3 -0
- package/dist/reference-lists.d.ts.map +1 -0
- package/dist/reference-lists.js +75 -0
- package/dist/reference-lists.js.map +1 -0
- package/dist/render/explain.d.ts +7 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +36 -12
- package/dist/render/explain.js.map +1 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +19 -9
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/options.d.ts +7 -0
- package/dist/render/options.d.ts.map +1 -0
- package/dist/render/options.js +50 -0
- package/dist/render/options.js.map +1 -0
- package/dist/render/rules-json.d.ts +4 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +102 -12
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/rules-table.d.ts +4 -0
- package/dist/render/rules-table.d.ts.map +1 -0
- package/dist/render/rules-table.js +70 -0
- package/dist/render/rules-table.js.map +1 -0
- package/dist/render/sarif.js +4 -4
- package/dist/render/sarif.js.map +1 -1
- package/dist/render/semantic.js +3 -3
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/severity-name.d.ts +6 -0
- package/dist/render/severity-name.d.ts.map +1 -0
- package/dist/render/severity-name.js +7 -0
- package/dist/render/severity-name.js.map +1 -0
- package/dist/render/text.d.ts +7 -2
- package/dist/render/text.d.ts.map +1 -1
- package/dist/render/text.js +15 -4
- package/dist/render/text.js.map +1 -1
- package/dist/resolve-genre.d.ts +1 -1
- package/dist/resolve-genre.d.ts.map +1 -1
- package/dist/resolve-genre.js +4 -1
- package/dist/resolve-genre.js.map +1 -1
- package/dist/rule-genres.d.ts +24 -0
- package/dist/rule-genres.d.ts.map +1 -0
- package/dist/rule-genres.js +11 -0
- package/dist/rule-genres.js.map +1 -0
- package/dist/rule-guide.d.ts +54 -0
- package/dist/rule-guide.d.ts.map +1 -0
- package/dist/rule-guide.js +81 -0
- package/dist/rule-guide.js.map +1 -0
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +94 -32
- package/dist/rule-load.js.map +1 -1
- package/dist/rule-options.d.ts +50 -0
- package/dist/rule-options.d.ts.map +1 -0
- package/dist/rule-options.js +106 -0
- package/dist/rule-options.js.map +1 -0
- package/dist/run-semantic.d.ts +3 -1
- package/dist/run-semantic.d.ts.map +1 -1
- package/dist/run-semantic.js +8 -3
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +15 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +57 -10
- package/dist/run.js.map +1 -1
- package/dist/semantic.d.ts +1 -1
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +10 -3
- package/dist/semantic.js.map +1 -1
- package/dist/sentence-shape.d.ts.map +1 -1
- package/dist/sentence-shape.js +4 -4
- package/dist/sentence-shape.js.map +1 -1
- package/dist/setting-problems.d.ts +2 -1
- package/dist/setting-problems.d.ts.map +1 -1
- package/dist/setting-problems.js +6 -1
- package/dist/setting-problems.js.map +1 -1
- package/dist/span-cut.d.ts +6 -0
- package/dist/span-cut.d.ts.map +1 -0
- package/dist/span-cut.js +22 -0
- package/dist/span-cut.js.map +1 -0
- package/dist/span-merge.d.ts +4 -0
- package/dist/span-merge.d.ts.map +1 -0
- package/dist/span-merge.js +12 -0
- package/dist/span-merge.js.map +1 -0
- package/dist/speaker-labels.d.ts +4 -0
- package/dist/speaker-labels.d.ts.map +1 -0
- package/dist/speaker-labels.js +106 -0
- package/dist/speaker-labels.js.map +1 -0
- package/dist/stray-space.d.ts +17 -0
- package/dist/stray-space.d.ts.map +1 -0
- package/dist/stray-space.js +66 -0
- package/dist/stray-space.js.map +1 -0
- package/dist/structure/aside-depth.d.ts +21 -0
- package/dist/structure/aside-depth.d.ts.map +1 -0
- package/dist/structure/aside-depth.js +65 -0
- package/dist/structure/aside-depth.js.map +1 -0
- package/dist/structure/build.d.ts +5 -1
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +29 -17
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/closed-pairs.d.ts +8 -0
- package/dist/structure/closed-pairs.d.ts.map +1 -0
- package/dist/structure/closed-pairs.js +22 -0
- package/dist/structure/closed-pairs.js.map +1 -0
- package/dist/structure/date-range.d.ts +21 -0
- package/dist/structure/date-range.d.ts.map +1 -0
- package/dist/structure/date-range.js +45 -0
- package/dist/structure/date-range.js.map +1 -0
- package/dist/structure/known-mentions.d.ts +18 -0
- package/dist/structure/known-mentions.d.ts.map +1 -0
- package/dist/structure/known-mentions.js +78 -0
- package/dist/structure/known-mentions.js.map +1 -0
- package/dist/structure/numbering.d.ts +1 -0
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +11 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/of.d.ts +3 -1
- package/dist/structure/of.d.ts.map +1 -1
- package/dist/structure/of.js +5 -1
- package/dist/structure/of.js.map +1 -1
- package/dist/structure/percent-sum.d.ts +16 -0
- package/dist/structure/percent-sum.d.ts.map +1 -0
- package/dist/structure/percent-sum.js +104 -0
- package/dist/structure/percent-sum.js.map +1 -0
- package/dist/structure/quoted-number.d.ts +8 -0
- package/dist/structure/quoted-number.d.ts.map +1 -0
- package/dist/structure/quoted-number.js +34 -0
- package/dist/structure/quoted-number.js.map +1 -0
- package/dist/structure/relative-find.d.ts.map +1 -1
- package/dist/structure/relative-find.js +34 -46
- package/dist/structure/relative-find.js.map +1 -1
- package/dist/structure/relative-resolve.d.ts.map +1 -1
- package/dist/structure/relative-resolve.js +94 -42
- package/dist/structure/relative-resolve.js.map +1 -1
- package/dist/structure/runs.d.ts +2 -0
- package/dist/structure/runs.d.ts.map +1 -1
- package/dist/structure/runs.js +2 -2
- package/dist/structure/runs.js.map +1 -1
- package/dist/structure/sexp.d.ts +1 -1
- package/dist/structure/sexp.d.ts.map +1 -1
- package/dist/structure/sexp.js +18 -6
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/total.d.ts +3 -0
- package/dist/structure/total.d.ts.map +1 -1
- package/dist/structure/total.js +2 -2
- package/dist/structure/total.js.map +1 -1
- package/dist/structure/universal.d.ts +14 -2
- package/dist/structure/universal.d.ts.map +1 -1
- package/dist/structure/universal.js +17 -2
- package/dist/structure/universal.js.map +1 -1
- package/dist/structure/wrapped-tail.d.ts +14 -7
- package/dist/structure/wrapped-tail.d.ts.map +1 -1
- package/dist/structure/wrapped-tail.js +33 -8
- package/dist/structure/wrapped-tail.js.map +1 -1
- package/dist/style-load.d.ts +4 -0
- package/dist/style-load.d.ts.map +1 -0
- package/dist/style-load.js +28 -0
- package/dist/style-load.js.map +1 -0
- package/dist/style-parse.d.ts +19 -0
- package/dist/style-parse.d.ts.map +1 -0
- package/dist/style-parse.js +60 -0
- package/dist/style-parse.js.map +1 -0
- package/dist/subheading-line.d.ts +6 -0
- package/dist/subheading-line.d.ts.map +1 -0
- package/dist/subheading-line.js +57 -0
- package/dist/subheading-line.js.map +1 -0
- package/dist/team-names.d.ts.map +1 -1
- package/dist/team-names.js +11 -14
- package/dist/team-names.js.map +1 -1
- package/dist/template-syntax.d.ts +17 -0
- package/dist/template-syntax.d.ts.map +1 -0
- package/dist/template-syntax.js +140 -0
- package/dist/template-syntax.js.map +1 -0
- package/dist/text-above.d.ts +19 -0
- package/dist/text-above.d.ts.map +1 -0
- package/dist/text-above.js +43 -0
- package/dist/text-above.js.map +1 -0
- package/dist/text-figures.d.ts +10 -0
- package/dist/text-figures.d.ts.map +1 -0
- package/dist/text-figures.js +131 -0
- package/dist/text-figures.js.map +1 -0
- package/dist/trailing-aside.d.ts +2 -0
- package/dist/trailing-aside.d.ts.map +1 -0
- package/dist/trailing-aside.js +53 -0
- package/dist/trailing-aside.js.map +1 -0
- package/dist/tree-walk.d.ts +18 -0
- package/dist/tree-walk.d.ts.map +1 -0
- package/dist/tree-walk.js +45 -0
- package/dist/tree-walk.js.map +1 -0
- package/genres.yaml +60 -2
- package/package.json +5 -3
- package/reference-headings.yaml +17 -0
- package/rules/adverb-overuse.yaml +19 -0
- package/rules/agentless-passive.yaml +19 -0
- package/rules/agreement-slip.yaml +49 -0
- package/rules/ai-generated-composite.yaml +102 -0
- package/rules/ai-tell.yaml +23 -0
- package/rules/announced-count-mismatch.yaml +57 -0
- package/rules/announcing-opener.yaml +51 -0
- package/rules/assistant-residue.yaml +52 -0
- package/rules/bold-density.yaml +31 -0
- package/rules/broken-link.yaml +65 -0
- package/rules/closing-cliche.yaml +33 -0
- package/rules/colon-lead-in.yaml +59 -0
- package/rules/concrete-evidence-density.yaml +41 -0
- package/rules/contraction-consistency.yaml +18 -0
- package/rules/contrast-framing.yaml +57 -0
- package/rules/cushion-phrase-density.yaml +25 -0
- package/rules/dangling-figure-reference.yaml +61 -0
- package/rules/dangling-reference.yaml +53 -1
- package/rules/date-order.yaml +33 -1
- package/rules/date-range-reversed.yaml +46 -0
- package/rules/date-weekday-mismatch.yaml +17 -1
- package/rules/double-keigo.yaml +16 -0
- package/rules/doubled-punctuation.yaml +45 -0
- package/rules/doubled-word.yaml +17 -1
- package/rules/duplicate-definition.yaml +59 -1
- package/rules/emoji-density.yaml +25 -0
- package/rules/empty-conclusion.yaml +29 -0
- package/rules/empty-intensifier.yaml +19 -0
- package/rules/excessive-hedging.yaml +19 -0
- package/rules/expletive-construction.yaml +19 -0
- package/rules/heading-echo.yaml +32 -0
- package/rules/heading-level-skip.yaml +78 -0
- package/rules/hiragana-fukushi.yaml +16 -0
- package/rules/image-alt-text.yaml +54 -0
- package/rules/internal-jargon.yaml +21 -0
- package/rules/katakana-long-vowel.yaml +98 -0
- package/rules/kutoten-consistency.yaml +53 -0
- package/rules/latin-spacing.yaml +23 -4
- package/rules/max-kanji-continuous.yaml +16 -0
- package/rules/max-paragraph-length.yaml +42 -2
- package/rules/max-sentence-length.yaml +36 -0
- package/rules/ngram-repetition.yaml +29 -2
- package/rules/no-doubled-joshi.yaml +16 -0
- package/rules/no-em-dash.yaml +25 -0
- package/rules/no-mixed-desumasu.yaml +27 -2
- package/rules/no-nakaguro-parallel.yaml +20 -0
- package/rules/numbering-gap.yaml +53 -1
- package/rules/oxford-comma-consistency.yaml +19 -1
- package/rules/padded-intro.yaml +19 -0
- package/rules/paragraph-length-variance.yaml +49 -0
- package/rules/percent-sum-mismatch.yaml +58 -0
- package/rules/preamble-length.yaml +56 -0
- package/rules/preferred-term.yaml +27 -0
- package/rules/proper-noun-density.yaml +26 -0
- package/rules/repeated-conjunction.yaml +47 -0
- package/rules/repeated-sentence-head.yaml +23 -0
- package/rules/required-sections.yaml +49 -0
- package/rules/risk-disclosure.yaml +22 -0
- package/rules/rule-of-three.yaml +97 -0
- package/rules/sasete-itadaku.yaml +18 -0
- package/rules/section-length-uniformity.yaml +89 -0
- package/rules/sentence-initial-conjunction-run.yaml +18 -0
- package/rules/sentence-rhythm.yaml +25 -0
- package/rules/stock-transition.yaml +55 -0
- package/rules/stray-space.yaml +56 -0
- package/rules/taigen-dome-in-prose.yaml +19 -0
- package/rules/title-case-consistency.yaml +42 -0
- package/rules/total-mismatch.yaml +37 -1
- package/rules/unbalanced-bracket.yaml +56 -0
- package/rules/undefined-acronym.yaml +23 -0
- package/rules/unfilled-placeholder.yaml +50 -0
- package/rules/unqualified-superlative.yaml +19 -0
- package/rules/unsourced-number.yaml +16 -0
- package/rules/url-run-on.yaml +45 -0
- package/skills/chaff/SKILL.md +70 -0
- package/src/announced-count.ts +148 -0
- package/src/bare-url.ts +35 -0
- package/src/baseline.ts +17 -1
- package/src/body-section.ts +7 -0
- package/src/calendar-number.ts +33 -10
- package/src/cli-args.ts +6 -0
- package/src/cli-text.ts +8 -4
- package/src/cli.ts +37 -37
- package/src/closing-numbers.ts +72 -0
- package/src/commands/compare.ts +80 -0
- package/src/commands/eval.ts +19 -8
- package/src/commands/feedback.ts +12 -6
- package/src/commands/test.ts +20 -17
- package/src/commands/tree.ts +41 -5
- package/src/compare/atom.ts +33 -0
- package/src/compare/clock-time.ts +86 -0
- package/src/compare/extract.ts +78 -0
- package/src/compare/fact-text.ts +68 -0
- package/src/compare/markup-atoms.ts +29 -0
- package/src/compare/match.ts +105 -0
- package/src/compare/outcome.ts +70 -0
- package/src/compare/proper-nouns.ts +65 -0
- package/src/compare/render.ts +102 -0
- package/src/compare/spans.ts +17 -0
- package/src/compare/text-atoms.ts +95 -0
- package/src/compare/text.ts +120 -0
- package/src/compare/tree-atoms.ts +80 -0
- package/src/compare/written-names.ts +26 -0
- package/src/config/load.ts +21 -0
- package/src/config/option-problems.ts +70 -0
- package/src/config/read.ts +8 -0
- package/src/config/rule-problems.ts +12 -2
- package/src/config/style.ts +42 -0
- package/src/config/warnings.ts +13 -0
- package/src/config/write.ts +18 -4
- package/src/custom/bounded-match.ts +58 -0
- package/src/custom/load.ts +15 -0
- package/src/custom/parse.ts +274 -0
- package/src/custom/problems.ts +89 -0
- package/src/custom/regex-safety.ts +118 -0
- package/src/custom/token-pattern.ts +84 -0
- package/src/detect.ts +28 -2
- package/src/detectors/acronym-context.ts +32 -4
- package/src/detectors/acronym-expansion.ts +132 -1
- package/src/detectors/agreement-slip.ts +191 -0
- package/src/detectors/ai-phrasing.ts +137 -0
- package/src/detectors/announced-count.ts +25 -0
- package/src/detectors/broken-link.ts +90 -0
- package/src/detectors/cited-name.ts +6 -5
- package/src/detectors/cited-title.ts +96 -0
- package/src/detectors/concrete-evidence.ts +3 -2
- package/src/detectors/content-morphemes.ts +19 -0
- package/src/detectors/contrast-frame.ts +54 -0
- package/src/detectors/custom.ts +89 -0
- package/src/detectors/dangling-figure.ts +22 -0
- package/src/detectors/doubled-punctuation.ts +49 -0
- package/src/detectors/doubled-word.ts +19 -4
- package/src/detectors/en-shape.ts +0 -269
- package/src/detectors/enumerated-runs.ts +13 -4
- package/src/detectors/gram-furniture.ts +30 -0
- package/src/detectors/gram-place.ts +9 -5
- package/src/detectors/gram-windows.ts +31 -0
- package/src/detectors/heading-case.ts +30 -2
- package/src/detectors/heading-code-words.ts +35 -0
- package/src/detectors/heading-echo.ts +18 -22
- package/src/detectors/heading-level-skip.ts +19 -0
- package/src/detectors/heading-overlap.ts +65 -0
- package/src/detectors/image-alt-text.ts +11 -0
- package/src/detectors/index.ts +49 -2
- package/src/detectors/kutoten-consistency.ts +96 -0
- package/src/detectors/lead-in.ts +3 -1
- package/src/detectors/letter-joined-name.ts +15 -0
- package/src/detectors/lexicon-match.ts +9 -2
- package/src/detectors/lexicon.ts +6 -2
- package/src/detectors/list-item.ts +122 -0
- package/src/detectors/list-items.ts +163 -0
- package/src/detectors/list-lead-in.ts +33 -0
- package/src/detectors/list-sentence.ts +157 -0
- package/src/detectors/list-view.ts +157 -0
- package/src/detectors/long-vowel.ts +66 -0
- package/src/detectors/markup-finding.ts +24 -0
- package/src/detectors/name-numeral.ts +4 -2
- package/src/detectors/orthography.ts +24 -18
- package/src/detectors/oxford-comma.ts +297 -0
- package/src/detectors/place-run.ts +7 -4
- package/src/detectors/placeholder-text.ts +27 -0
- package/src/detectors/quote-around.ts +9 -0
- package/src/detectors/register.ts +37 -10
- package/src/detectors/roman-numeral.ts +3 -0
- package/src/detectors/sentence-ending.ts +14 -7
- package/src/detectors/sentence-length.ts +16 -5
- package/src/detectors/signals.ts +110 -53
- package/src/detectors/stacked-hedge.ts +6 -3
- package/src/detectors/stray-space.ts +40 -0
- package/src/detectors/structure-tree.ts +45 -1
- package/src/detectors/structure.ts +13 -6
- package/src/detectors/superlative-name.ts +7 -4
- package/src/detectors/superlative-scope.ts +8 -3
- package/src/detectors/token-column.ts +149 -0
- package/src/detectors/unbalanced-bracket.ts +153 -0
- package/src/detectors/url-run-on.ts +27 -0
- package/src/document-sections.ts +46 -0
- package/src/document.ts +116 -127
- package/src/email-parts.ts +269 -0
- package/src/eval.ts +14 -4
- package/src/figure-references.ts +112 -0
- package/src/files.ts +5 -0
- package/src/genre-parse.ts +30 -5
- package/src/genre-suggest.ts +15 -2
- package/src/heading-label.ts +55 -0
- package/src/in-page-nav.ts +48 -19
- package/src/joined-view.ts +1 -0
- package/src/levels.ts +14 -1
- package/src/line-paragraphs.ts +56 -10
- package/src/long-vowel.ts +99 -0
- package/src/markdown-node.ts +19 -0
- package/src/markdown-read.ts +44 -0
- package/src/markup.ts +210 -0
- package/src/mask.ts +10 -19
- package/src/number-name.ts +152 -25
- package/src/opening-line.ts +17 -0
- package/src/orthography.ts +20 -5
- package/src/page-furniture.ts +11 -2
- package/src/paragraph-length.ts +9 -0
- package/src/plain-source.ts +2 -1
- package/src/plugin.ts +107 -1
- package/src/profile/for-file.ts +3 -1
- package/src/quoted-span.ts +10 -4
- package/src/reasons.ts +9 -0
- package/src/reference-lists.ts +91 -0
- package/src/render/explain.ts +47 -13
- package/src/render/friendly.ts +20 -9
- package/src/render/options.ts +58 -0
- package/src/render/rules-json.ts +130 -14
- package/src/render/rules-table.ts +91 -0
- package/src/render/sarif.ts +4 -4
- package/src/render/semantic.ts +3 -3
- package/src/render/severity-name.ts +10 -0
- package/src/render/text.ts +21 -7
- package/src/resolve-genre.ts +4 -1
- package/src/rule-genres.ts +25 -0
- package/src/rule-guide.ts +135 -0
- package/src/rule-load.ts +98 -33
- package/src/rule-options.ts +131 -0
- package/src/run-semantic.ts +28 -20
- package/src/run.ts +78 -11
- package/src/semantic.ts +11 -3
- package/src/sentence-shape.ts +4 -5
- package/src/setting-problems.ts +7 -1
- package/src/span-cut.ts +28 -0
- package/src/span-merge.ts +13 -0
- package/src/speaker-labels.ts +129 -0
- package/src/stray-space.ts +81 -0
- package/src/structure/aside-depth.ts +67 -0
- package/src/structure/build.ts +38 -19
- package/src/structure/closed-pairs.ts +20 -0
- package/src/structure/date-range.ts +69 -0
- package/src/structure/known-mentions.ts +96 -0
- package/src/structure/numbering.ts +9 -5
- package/src/structure/of.ts +11 -3
- package/src/structure/percent-sum.ts +135 -0
- package/src/structure/quoted-number.ts +36 -0
- package/src/structure/relative-find.ts +34 -47
- package/src/structure/relative-resolve.ts +104 -42
- package/src/structure/runs.ts +2 -2
- package/src/structure/sexp.ts +17 -5
- package/src/structure/total.ts +2 -2
- package/src/structure/universal.ts +24 -4
- package/src/structure/wrapped-tail.ts +38 -13
- package/src/style-load.ts +28 -0
- package/src/style-parse.ts +77 -0
- package/src/subheading-line.ts +68 -0
- package/src/team-names.ts +13 -13
- package/src/template-syntax.ts +164 -0
- package/src/text-above.ts +49 -0
- package/src/text-figures.ts +147 -0
- package/src/trailing-aside.ts +50 -0
- package/src/tree-walk.ts +48 -0
- package/styles/bunkacho.yaml +25 -0
- package/styles/ieice.yaml +29 -0
- package/styles/jis-z8301-2011.yaml +25 -0
package/src/document.ts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { gfmTable } from "micromark-extension-gfm-table";
|
|
3
|
-
import { gfmTableFromMarkdown } from "mdast-util-gfm-table";
|
|
4
|
-
import { frontmatter } from "micromark-extension-frontmatter";
|
|
5
|
-
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
1
|
+
import { headingReadersOf, sectionsOf } from "./document-sections.ts";
|
|
6
2
|
import { atxHeadingText, headingText } from "./heading-text.ts";
|
|
7
3
|
import { hasTitle } from "./heading-title.ts";
|
|
8
4
|
import { maskSpans } from "./mask.ts";
|
|
9
5
|
import { spansWithin, unmaskedSoftBreaks } from "./soft-break.ts";
|
|
10
6
|
import { segmentJoined } from "./joined-view.ts";
|
|
11
|
-
import {
|
|
7
|
+
import { documentLineParagraphs } from "./line-paragraphs.ts";
|
|
8
|
+
import { subheadingPieces } from "./subheading-line.ts";
|
|
9
|
+
import { speakerLabels } from "./speaker-labels.ts";
|
|
12
10
|
import { buildTree, type Outline } from "./structure/build.ts";
|
|
13
11
|
import { isMarkdownPath } from "./structure/markdown-path.ts";
|
|
14
|
-
import {
|
|
12
|
+
import { layoutMasks, textOutline } from "./page-furniture.ts";
|
|
15
13
|
import { tokenizedLexicons } from "./lexicon-tokens.ts";
|
|
14
|
+
import { plainSource } from "./plain-source.ts";
|
|
16
15
|
import { inPageAnchors, isInPageNavigation, isNavigationList, type InPageAnchors } from "./in-page-nav.ts";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type Node
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
16
|
+
import { eachPreOrder } from "./tree-walk.ts";
|
|
17
|
+
import { alertReader } from "./template-syntax.ts";
|
|
18
|
+
import { opaqueSpans, parse, readMarkdown, spansOfType } from "./markdown-read.ts";
|
|
19
|
+
import { spanOf, type MarkdownNode as Node } from "./markdown-node.ts";
|
|
20
|
+
import { emailParts, emailVocabulary } from "./email-parts.ts";
|
|
21
|
+
import { cutTextSpans } from "./span-cut.ts";
|
|
22
|
+
import { markdownFigures } from "./text-figures.ts";
|
|
23
|
+
import { documentMarkup } from "./markup.ts";
|
|
24
|
+
import { BARE_URL } from "./bare-url.ts";
|
|
25
|
+
import type { BulletList, LanguageAdapter, Markup, Paragraph, ProseDocument, Sentence, Span, StructureNode, DocumentProfile } from "./plugin.ts";
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
28
|
* 本文として数えないもの。
|
|
@@ -35,22 +33,8 @@ type Node = {
|
|
|
35
33
|
*/
|
|
36
34
|
const NOT_PROSE = new Set(["code", "inlineCode", "html", "yaml", "toml", "table", "blockquote", "thematicBreak", "definition", "image", "imageReference"]);
|
|
37
35
|
|
|
38
|
-
const spanOf = (node: Node): Span | undefined => {
|
|
39
|
-
const start = node.position?.start.offset;
|
|
40
|
-
const end = node.position?.end.offset;
|
|
41
|
-
return start === undefined || end === undefined ? undefined : { start, end };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const walk = (node: Node, visit: (node: Node) => void): void => {
|
|
45
|
-
visit(node);
|
|
46
|
-
(node.children ?? []).forEach((child) => walk(child, visit));
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const parse = (source: string): Node =>
|
|
50
|
-
fromMarkdown(source, { extensions: [gfmTable(), frontmatter(["yaml"])], mdastExtensions: [gfmTableFromMarkdown(), frontmatterFromMarkdown(["yaml"])] });
|
|
51
|
-
|
|
52
36
|
/** link は `[text](url)` の外側だけを覆う。表示される文字は本文なので残す。 */
|
|
53
|
-
const linkChrome = (node: Node): Span[] => {
|
|
37
|
+
export const linkChrome = (node: Node): Span[] => {
|
|
54
38
|
const whole = spanOf(node);
|
|
55
39
|
const first = node.children?.[0];
|
|
56
40
|
const last = node.children?.at(-1);
|
|
@@ -87,27 +71,26 @@ const EMPHASIS = new Set(["strong", "emphasis", "delete"]);
|
|
|
87
71
|
* `:::note` `:::` のようなディレクティブ。Zenn・Docusaurus・VitePress が使う記法で、
|
|
88
72
|
* 標準の Markdown には無いため段落として解析される。囲みの指定であって文章ではない。
|
|
89
73
|
*/
|
|
90
|
-
const DIRECTIVE =
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* `https://…` をそのまま書いた URL。GFM の autolink 拡張を入れていないので mdast では
|
|
94
|
-
* ただのテキストになり、本文として残る。残すと、見出しと URL の中の識別子が一致して
|
|
95
|
-
* 「見出しの繰り返し」と読まれる。表示される文字も本文ではない。
|
|
96
|
-
*/
|
|
97
|
-
const BARE_URL = /https?:\/\/[^\s)<>"'\]]+/gu;
|
|
74
|
+
const DIRECTIVE = /^[ \t]*:::[^\n]*/gmu;
|
|
98
75
|
|
|
99
76
|
const matchSpans = (source: string, pattern: RegExp): Span[] =>
|
|
100
77
|
[...source.matchAll(pattern)].map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
|
101
78
|
|
|
102
79
|
const directiveSpans = (source: string): Span[] => [...matchSpans(source, DIRECTIVE), ...matchSpans(source, BARE_URL)];
|
|
103
80
|
|
|
104
|
-
const collectMasks = (root: Node, source: string, anchors: InPageAnchors): Span[] => {
|
|
105
|
-
const spans: Span[] = [...directiveSpans(source)];
|
|
106
|
-
|
|
81
|
+
const collectMasks = (root: Node, source: string, anchors: InPageAnchors, syntax: readonly Span[]): Span[] => {
|
|
82
|
+
const spans: Span[] = [...directiveSpans(source), ...syntax];
|
|
83
|
+
const alertOf = alertReader(source);
|
|
84
|
+
eachPreOrder(root, (node) => {
|
|
107
85
|
if (node.type === "link" || node.type === "linkReference") {
|
|
108
86
|
spans.push(...linkChrome(node));
|
|
109
87
|
return;
|
|
110
88
|
}
|
|
89
|
+
const alert = alertOf(node);
|
|
90
|
+
if (alert !== undefined) {
|
|
91
|
+
spans.push(...alert);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
111
94
|
if (!NOT_PROSE.has(node.type) && node.type !== "heading" && !isInPageNavigation(node, anchors)) return;
|
|
112
95
|
const span = spanOf(node);
|
|
113
96
|
if (span !== undefined) spans.push(span);
|
|
@@ -117,7 +100,7 @@ const collectMasks = (root: Node, source: string, anchors: InPageAnchors): Span[
|
|
|
117
100
|
|
|
118
101
|
const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
119
102
|
const spans: Span[] = [];
|
|
120
|
-
|
|
103
|
+
eachPreOrder(root, (node) => {
|
|
121
104
|
if (EMPHASIS.has(node.type)) spans.push(...emphasisChrome(node, source));
|
|
122
105
|
});
|
|
123
106
|
return spans;
|
|
@@ -125,7 +108,7 @@ const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
|
125
108
|
|
|
126
109
|
const textOf = (node: Node, source: string): string => {
|
|
127
110
|
const parts: string[] = [];
|
|
128
|
-
|
|
111
|
+
eachPreOrder(node, (child) => {
|
|
129
112
|
if (child.type !== "text" && child.type !== "inlineCode") return;
|
|
130
113
|
const span = spanOf(child);
|
|
131
114
|
if (span !== undefined) parts.push(source.slice(span.start, span.end));
|
|
@@ -135,17 +118,25 @@ const textOf = (node: Node, source: string): string => {
|
|
|
135
118
|
|
|
136
119
|
export type Heading = { readonly depth: number; readonly text: string; readonly start: number; readonly end: number };
|
|
137
120
|
|
|
138
|
-
|
|
121
|
+
/**
|
|
122
|
+
* `neutral` is the source with the template syntax blanked: `## Usage {% if x %}` is headed "Usage". Whether a
|
|
123
|
+
* heading has a title is read from the source, since `## {{ product }}` names something.
|
|
124
|
+
* 引用した返信(outside)の中の見出しは、ほかの人の文書の見出しなので数えない。
|
|
125
|
+
*/
|
|
126
|
+
const headingsOf = (root: Node, source: string, neutral: string, outside: readonly Span[] = []): Heading[] => {
|
|
139
127
|
const found: Heading[] = [];
|
|
140
|
-
|
|
128
|
+
eachPreOrder(root, (node) => {
|
|
141
129
|
if (node.type !== "heading") return;
|
|
142
130
|
const span = spanOf(node);
|
|
143
131
|
const depth = typeof node === "object" && "depth" in node && typeof node.depth === "number" ? node.depth : 1;
|
|
144
|
-
if (span === undefined) return;
|
|
132
|
+
if (span === undefined || startsInside(span, outside)) return;
|
|
145
133
|
// ATX(行頭が #)なら閉じの # も外す。setext の見出しの末尾の # は言葉なので残す。
|
|
146
134
|
const read = source.startsWith("#", span.start) ? atxHeadingText : headingText;
|
|
147
|
-
const
|
|
148
|
-
|
|
135
|
+
const written = textOf(node, source);
|
|
136
|
+
const shown = textOf(node, neutral);
|
|
137
|
+
// A blanked tag leaves a run of spaces where the reader sees one gap.
|
|
138
|
+
const text = read(shown === written ? written : shown.replace(/ {2,}/gu, " "));
|
|
139
|
+
if (hasTitle(read(written))) found.push({ depth, text, start: span.start, end: span.end });
|
|
149
140
|
});
|
|
150
141
|
return found;
|
|
151
142
|
};
|
|
@@ -161,7 +152,7 @@ const startsInside = (span: Span, regions: readonly Span[]): boolean => regions.
|
|
|
161
152
|
*/
|
|
162
153
|
const strongSpans = (root: Node, masked: readonly Span[]): Span[] => {
|
|
163
154
|
const found: Span[] = [];
|
|
164
|
-
|
|
155
|
+
eachPreOrder(root, (node) => {
|
|
165
156
|
if (node.type !== "strong") return;
|
|
166
157
|
const span = spanOf(node);
|
|
167
158
|
if (span !== undefined && !startsInside(span, masked)) found.push(span);
|
|
@@ -169,18 +160,6 @@ const strongSpans = (root: Node, masked: readonly Span[]): Span[] => {
|
|
|
169
160
|
return found;
|
|
170
161
|
};
|
|
171
162
|
|
|
172
|
-
const within = (span: Span, from: number, to: number): boolean => span.start >= from && span.start < to;
|
|
173
|
-
|
|
174
|
-
const spansOfType = (root: Node, type: string, keep: (node: Node) => boolean = () => true): Span[] => {
|
|
175
|
-
const found: Span[] = [];
|
|
176
|
-
walk(root, (node) => {
|
|
177
|
-
if (node.type !== type || !keep(node)) return;
|
|
178
|
-
const span = spanOf(node);
|
|
179
|
-
if (span !== undefined) found.push(span);
|
|
180
|
-
});
|
|
181
|
-
return found;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
163
|
/**
|
|
185
164
|
* 文の分割は**段落ごと**に行う。
|
|
186
165
|
*
|
|
@@ -192,6 +171,10 @@ const shift = (span: Span, by: number): Span => ({ start: by + span.start, end:
|
|
|
192
171
|
|
|
193
172
|
const breaksWithin = (breaks: readonly Span[], paragraph: Span): Span[] => spansWithin(breaks, paragraph).map((span) => shift(span, -paragraph.start));
|
|
194
173
|
|
|
174
|
+
/** 段落を、中の小見出しの行(「(経済再生)」)の後ろで切った片。片ごとに分割すれば、小見出しが次の文に入らない。 */
|
|
175
|
+
const piecesOf = (prose: string, paragraphs: readonly Span[]): Span[] =>
|
|
176
|
+
paragraphs.flatMap((paragraph) => subheadingPieces(prose.slice(paragraph.start, paragraph.end)).map((piece) => shift(piece, paragraph.start)));
|
|
177
|
+
|
|
195
178
|
const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: LanguageAdapter, softBreaks: readonly Span[]): Sentence[] =>
|
|
196
179
|
paragraphs.flatMap((paragraph) =>
|
|
197
180
|
segmentJoined(prose.slice(paragraph.start, paragraph.end), breaksWithin(softBreaks, paragraph), (text) => adapter.segment(text))
|
|
@@ -201,27 +184,10 @@ const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: Langua
|
|
|
201
184
|
text: sentence.text,
|
|
202
185
|
...(sentence.tokens === undefined ? {} : { tokens: sentence.tokens.map((token) => ({ ...token, span: shift(token.span, paragraph.start) })) }),
|
|
203
186
|
...(sentence.wrapBreaks === undefined ? {} : { wrapBreaks: sentence.wrapBreaks.map((span) => shift(span, paragraph.start)) }),
|
|
187
|
+
...(sentence.embeddedLanguage === undefined ? {} : { embeddedLanguage: sentence.embeddedLanguage }),
|
|
204
188
|
})),
|
|
205
189
|
);
|
|
206
190
|
|
|
207
|
-
const sectionsOf = (headings: readonly Heading[], sentences: readonly Sentence[], strongs: readonly Span[], length: number): Section[] => {
|
|
208
|
-
const bounds = headings.map((heading, index) => ({ heading, from: heading.end, to: headings[index + 1]?.start ?? length }));
|
|
209
|
-
const lead = { heading: { depth: 0, text: "", start: 0, end: 0 }, from: 0, to: headings[0]?.start ?? length };
|
|
210
|
-
return [lead, ...bounds]
|
|
211
|
-
.filter((bound) => bound.to > bound.from)
|
|
212
|
-
.map(({ heading, from, to }) => {
|
|
213
|
-
const inside = sentences.filter((sentence) => within(sentence.span, from, to));
|
|
214
|
-
return {
|
|
215
|
-
depth: heading.depth,
|
|
216
|
-
heading: heading.text,
|
|
217
|
-
span: { start: from, end: to },
|
|
218
|
-
sentences: inside,
|
|
219
|
-
strongCount: strongs.filter((span) => within(span, from, to)).length,
|
|
220
|
-
firstSentence: inside[0],
|
|
221
|
-
};
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
|
|
225
191
|
/** sentences(並び順)の中で、start が offset 以上の最初の添字。1 行 1 段落の何万行でも、段落ごとに全部をなめない。 */
|
|
226
192
|
const firstStartingAt = (sentences: readonly Sentence[], offset: number): number => {
|
|
227
193
|
const search = (low: number, high: number): number => {
|
|
@@ -235,24 +201,18 @@ const firstStartingAt = (sentences: readonly Sentence[], offset: number): number
|
|
|
235
201
|
const sentencesWithin = (sentences: readonly Sentence[], span: Span): Sentence[] =>
|
|
236
202
|
sentences.slice(firstStartingAt(sentences, span.start), firstStartingAt(sentences, span.end));
|
|
237
203
|
|
|
238
|
-
const paragraphsOf = (prose: string, spans: readonly Span[], sentences: readonly Sentence[], listSpans: readonly Span[]): Paragraph[] =>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
prose,
|
|
246
|
-
span,
|
|
247
|
-
sentencesWithin(sentences, span).map((sentence) => sentence.span),
|
|
248
|
-
),
|
|
249
|
-
)
|
|
250
|
-
.map((span) => ({ span, sentences: sentencesWithin(sentences, span) }));
|
|
204
|
+
const paragraphsOf = (prose: string, spans: readonly Span[], sentences: readonly Sentence[], listSpans: readonly Span[]): Paragraph[] => {
|
|
205
|
+
// 箇条書きの中の段落は「段落」として数えない。項目 1 つを 1 段落と読むと、
|
|
206
|
+
// 段落あたりの文数も長さのばらつきも、箇条書きの多い文書で壊れる。
|
|
207
|
+
const outsideLists = spans.filter((span) => !listSpans.some((list) => span.start >= list.start && span.start < list.end));
|
|
208
|
+
const withSentences = outsideLists.map((span) => ({ span, sentences: sentencesWithin(sentences, span).map((sentence) => sentence.span) }));
|
|
209
|
+
return documentLineParagraphs(prose, withSentences).map((span) => ({ span, sentences: sentencesWithin(sentences, span) }));
|
|
210
|
+
};
|
|
251
211
|
|
|
252
212
|
/** 箇条書きは list ノードの直下の項目を数える。入れ子の項目は内側の list のものとして数える。目次のような案内だけの箇条書きは数えない。 */
|
|
253
213
|
const listsOf = (root: Node, source: string, anchors: InPageAnchors): BulletList[] => {
|
|
254
214
|
const found: BulletList[] = [];
|
|
255
|
-
|
|
215
|
+
eachPreOrder(root, (node) => {
|
|
256
216
|
if (node.type !== "list" || isNavigationList(node, anchors)) return;
|
|
257
217
|
const span = spanOf(node);
|
|
258
218
|
if (span === undefined) return;
|
|
@@ -290,33 +250,44 @@ export const teamRules = (config: {
|
|
|
290
250
|
names: config.names ?? [],
|
|
291
251
|
});
|
|
292
252
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
253
|
+
/**
|
|
254
|
+
* masked を覆った本文から、話し手の名前(戯曲・議事録)も覆う。名前は誰が話すかの印で、文でも本文でもない。
|
|
255
|
+
* 名前は覆った後の本文で探す。コードや表の中の行頭を話し手と数えない。
|
|
256
|
+
*/
|
|
257
|
+
const proseOf = (source: string, masked: readonly Span[]): string => {
|
|
258
|
+
const unlabelled = maskSpans(source, masked);
|
|
259
|
+
return maskSpans(unlabelled, speakerLabels(unlabelled));
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const teamLexicons = (adapter: LanguageAdapter, team: TeamRules): LanguageAdapter["lexicons"] => ({
|
|
263
|
+
...adapter.lexicons,
|
|
264
|
+
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
265
|
+
// 使わない書き方を pattern に、使う書き方を instead_of に置く。語彙表の「同じことの別の書き方」と同じ向き。
|
|
266
|
+
"preferred-term": Object.entries(team.prefer ?? {}).map(([pattern, use]) => ({ pattern, instead_of: use })),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const documentOf = (path: string, source: string, adapter: LanguageAdapter, team: TeamRules, profile: DocumentProfile | undefined): ProseDocument => {
|
|
270
|
+
const markdown = isMarkdownPath(path);
|
|
271
|
+
const { root, syntax } = markdown ? readMarkdown(source) : { root: parse(source), syntax: [] };
|
|
301
272
|
const anchors = inPageAnchors(root);
|
|
273
|
+
const emailLayout = emailParts(source, emailVocabulary(adapter.lexicons));
|
|
302
274
|
// 強調の記号は「本文でないもの」だが、太字の数を数えるときの「覆われた場所」ではない。
|
|
303
275
|
// 同じ集合にすると、太字が自分の記号のせいで覆われた場所にあることになり、1 つも数えられなくなる。
|
|
304
|
-
//
|
|
305
|
-
const blocks = [...collectMasks(root, source, anchors), ...(
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
276
|
+
// ページのヘッダーとフッター(テキストの文書)と、線で描いた図も本文ではない。
|
|
277
|
+
const blocks = [...collectMasks(root, source, anchors, syntax), ...layoutMasks(root, source, markdown), ...emailLayout.furniture];
|
|
278
|
+
const prose = proseOf(source, [...blocks, ...emphasisSpans(root, source)]);
|
|
279
|
+
// ページの案内は段落としても数えない。数えると、目次の行が「本題までの段落」に入る。メールのヘッダーや署名の行は段落から切り取る。
|
|
280
|
+
const paragraphSpans = cutTextSpans(
|
|
281
|
+
spansOfType(root, "paragraph", (node) => !isInPageNavigation(node, anchors)),
|
|
282
|
+
emailLayout.furniture,
|
|
283
|
+
source,
|
|
284
|
+
);
|
|
285
|
+
const headings = headingsOf(root, source, maskSpans(source, syntax), emailLayout.replyQuotes);
|
|
310
286
|
const listItems = spansOfType(root, "listItem");
|
|
311
287
|
// Markdown は段落を流し込んで表示するので、段落の中の改行は読み手に見えない。テキストの文書は行をそのまま見せる(法令は 1 行 1 号)。
|
|
312
|
-
const softBreaks =
|
|
313
|
-
const sentences = sentencesOf(prose, paragraphSpans, adapter, softBreaks);
|
|
314
|
-
const lexicons =
|
|
315
|
-
...adapter.lexicons,
|
|
316
|
-
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
317
|
-
// 使わない書き方を pattern に、使う書き方を instead_of に置く。語彙表の「同じことの別の書き方」と同じ向き。
|
|
318
|
-
"preferred-term": Object.entries(team.prefer ?? {}).map(([pattern, use]) => ({ pattern, instead_of: use })),
|
|
319
|
-
};
|
|
288
|
+
const softBreaks = markdown ? unmaskedSoftBreaks(source, prose) : [];
|
|
289
|
+
const sentences = sentencesOf(prose, piecesOf(prose, paragraphSpans), adapter, softBreaks);
|
|
290
|
+
const lexicons = teamLexicons(adapter, team);
|
|
320
291
|
const tagged = sentences.some((sentence) => sentence.tokens !== undefined);
|
|
321
292
|
const patterns = adapter.structure;
|
|
322
293
|
// null は「作ったが構造を読めない言語だった」、undefined は「まだ作っていない」。
|
|
@@ -327,7 +298,7 @@ export const buildDocument = (
|
|
|
327
298
|
language: adapter.id,
|
|
328
299
|
lengthUnit: adapter.capabilities.lengthUnit,
|
|
329
300
|
capabilities: adapter.capabilities,
|
|
330
|
-
sections: sectionsOf(
|
|
301
|
+
sections: sectionsOf(headings, sentences, strongSpans(root, blocks), source.length, headingReadersOf(adapter, tagged, lexicons)),
|
|
331
302
|
sentences,
|
|
332
303
|
listSpans: listItems,
|
|
333
304
|
paragraphs: paragraphsOf(prose, paragraphSpans, sentences, listItems),
|
|
@@ -347,7 +318,7 @@ export const buildDocument = (
|
|
|
347
318
|
source,
|
|
348
319
|
language: adapter.id,
|
|
349
320
|
// テキストの文書は、ページの飾りを覆って読む。フッターの「Section 9」を木の節にしない。
|
|
350
|
-
outline:
|
|
321
|
+
outline: markdown ? outlineOf(root, source, syntax, emailLayout.replyQuotes) : textOutline(source, emailLayout.replyQuotes),
|
|
351
322
|
markdown: isMarkdownPath(path),
|
|
352
323
|
profile,
|
|
353
324
|
},
|
|
@@ -357,16 +328,34 @@ export const buildDocument = (
|
|
|
357
328
|
},
|
|
358
329
|
profile,
|
|
359
330
|
prose,
|
|
331
|
+
replyQuotes: emailLayout.replyQuotes,
|
|
332
|
+
get markup(): Markup {
|
|
333
|
+
return documentMarkup(root, source, markdown, [...emailLayout.replyQuotes, ...syntax]);
|
|
334
|
+
},
|
|
360
335
|
};
|
|
361
336
|
};
|
|
362
337
|
|
|
363
|
-
/**
|
|
364
|
-
|
|
338
|
+
/**
|
|
339
|
+
* 文書モデルを作る。text はファイルの中身のままでよい。先頭の BOM を外し、CRLF と CR を LF にそろえた doc.source を読む。
|
|
340
|
+
* 位置(span・offset)はすべて doc.source の上の位置で、渡した text の上の位置ではない。行と桁は同じ。
|
|
341
|
+
*/
|
|
342
|
+
export const buildDocument = (
|
|
343
|
+
path: string,
|
|
344
|
+
text: string,
|
|
345
|
+
adapter: LanguageAdapter,
|
|
346
|
+
team: TeamRules = EMPTY_TEAM,
|
|
347
|
+
profile: DocumentProfile | undefined = undefined,
|
|
348
|
+
): ProseDocument => documentOf(path, plainSource(text), adapter, team, profile);
|
|
365
349
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
350
|
+
/** 引用した返信も中を読まない。ほかの人の言葉の中の定義や番号は、この文書のものではない。 */
|
|
351
|
+
const outlineOf = (root: Node, source: string, syntax: readonly Span[], replyQuotes: readonly Span[]): Outline => ({
|
|
352
|
+
headings: headingsOf(root, source, maskSpans(source, syntax), replyQuotes),
|
|
353
|
+
opaque: [...opaqueSpans(root), ...markdownFigures(root, source), ...syntax, ...replyQuotes],
|
|
354
|
+
tables: spansOfType(root, "table"),
|
|
369
355
|
});
|
|
370
356
|
|
|
371
|
-
/** 構造を読むための Markdown
|
|
372
|
-
export const markdownOutline = (source: string): Outline =>
|
|
357
|
+
/** 構造を読むための Markdown の手がかり。見出しと、中を読まない範囲。返信の引用は、言語パッケージの語彙表で見分ける。 */
|
|
358
|
+
export const markdownOutline = (source: string, lexicons: LanguageAdapter["lexicons"] = {}): Outline => {
|
|
359
|
+
const { root, syntax } = readMarkdown(source);
|
|
360
|
+
return outlineOf(root, source, syntax, emailParts(source, emailVocabulary(lexicons)).replyQuotes);
|
|
361
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Lexicon, Span } from "./plugin.ts";
|
|
2
|
+
import { linesOf, type Line } from "./structure/lines.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The parts of a plain-text or Markdown email that are not the writer's prose: the header, the separator lines between
|
|
6
|
+
* messages, the signature, and a quoted reply. Found by shape, in any genre; the field names and the attribution words
|
|
7
|
+
* come from the language package's lexicons.
|
|
8
|
+
*/
|
|
9
|
+
export type EmailVocabulary = {
|
|
10
|
+
/** Header field names, lower-cased. */
|
|
11
|
+
readonly headerFields: ReadonlySet<string>;
|
|
12
|
+
/** The fields whose value the writer types (Subject, To), lower-cased. Their value stays prose, on a line of its own. */
|
|
13
|
+
readonly writtenFields: ReadonlySet<string>;
|
|
14
|
+
/** Words an attribution line ends with, before its colon ("wrote", 「書きました」). */
|
|
15
|
+
readonly attributions: readonly string[];
|
|
16
|
+
/** How an archive's note in place of a removed attachment ends ("was scrubbed...", 「を保管しました...」). */
|
|
17
|
+
readonly attachmentNotes: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type EmailParts = {
|
|
21
|
+
/**
|
|
22
|
+
* Header lines (all of them but a written field's value), separator lines, signatures and attribution lines: masked
|
|
23
|
+
* from prose, and paragraphs are cut around them.
|
|
24
|
+
*/
|
|
25
|
+
readonly furniture: readonly Span[];
|
|
26
|
+
/** An attribution line and the quoted lines after it: someone else's words, so not this document's headings or structure. */
|
|
27
|
+
readonly replyQuotes: readonly Span[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const lowerCased = (lexicon: Lexicon | undefined): ReadonlySet<string> => new Set((lexicon ?? []).map((entry) => entry.pattern.toLowerCase()));
|
|
31
|
+
|
|
32
|
+
export const emailVocabulary = (lexicons: Readonly<Record<string, Lexicon>>): EmailVocabulary => ({
|
|
33
|
+
headerFields: lowerCased(lexicons["email-header-field"]),
|
|
34
|
+
writtenFields: lowerCased(lexicons["email-written-field"]),
|
|
35
|
+
attributions: (lexicons["email-attribution"] ?? []).map((entry) => entry.pattern),
|
|
36
|
+
attachmentNotes: (lexicons["email-attachment-note"] ?? []).map((entry) => entry.pattern),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** A signature has at most this many lines after its "-- " line (the convention is four). More is prose again. */
|
|
40
|
+
const SIGNATURE_MAX_LINES = 4;
|
|
41
|
+
/** An attribution paragraph longer than this is a paragraph of the writer's that happens to end with "wrote:". */
|
|
42
|
+
const ATTRIBUTION_MAX_LINES = 3;
|
|
43
|
+
/** Away from the top of a message, one known field ("Date: the first Monday") is a sentence; a header names several. */
|
|
44
|
+
const HEADER_MIN_FIELDS = 2;
|
|
45
|
+
/** An attachment stub is its note and a few fields: Mailman's longest is the note, Name, Type, Size, Desc and URL. */
|
|
46
|
+
const ATTACHMENT_STUB_MAX_LINES = 6;
|
|
47
|
+
|
|
48
|
+
/** "Field: value" or 「件名:値」. A colon followed by text, as in a URL, is not a field. */
|
|
49
|
+
const FIELD = /^([^\s::]{1,40})(?::(?:[ \t]+|$)|:[ \t]*)/u;
|
|
50
|
+
const FOLDED = /^[ \t]+\S/u;
|
|
51
|
+
/** An mbox archive's envelope line (RFC 4155): "From ", the sender, then the date. It has no colon after From. */
|
|
52
|
+
const ENVELOPE = /^From [^\s:]+ /u;
|
|
53
|
+
const SIGNATURE = /^-- ?$/u;
|
|
54
|
+
/** A line of dashes, underscores or equals signs, with a few words between them or none: "-----Original Message-----". */
|
|
55
|
+
const SEPARATOR = /^[-_=]{3,}(?:([^-_=]{1,40})[-_=]{2,})?$/u;
|
|
56
|
+
/** Two rules with only spaces or strokes between them are a drawing (an RFC's diagram), not a separator. */
|
|
57
|
+
const LETTER = /\p{L}/u;
|
|
58
|
+
const QUOTED = /^ {0,3}>/u;
|
|
59
|
+
const QUOTED_TEXT = /^ {0,3}>[\s>]*\S/u;
|
|
60
|
+
/** Gmail's attribution ends with the sender's address and a colon: 「… 山田太郎 <taro@example.com>:」. An archive writes "taro at example.com". */
|
|
61
|
+
const BRACKETED_THEN_COLON = /<([^<>]*)>[ \t]*[::]$/u;
|
|
62
|
+
const ADDRESS_MARK = /@| at /u;
|
|
63
|
+
const LATIN_LETTER = /[A-Za-z]/u;
|
|
64
|
+
/** The stub's last field: its value is a URL alone, bare or in angle brackets. */
|
|
65
|
+
const URL_VALUE = /^<?[a-z][a-z0-9+.-]*:\/\/\S+?>?$/iu;
|
|
66
|
+
|
|
67
|
+
const isBlank = (line: Line | undefined): boolean => line !== undefined && line.text.trim() === "";
|
|
68
|
+
const isSeparator = (line: Line | undefined): boolean => {
|
|
69
|
+
const match = line === undefined ? null : SEPARATOR.exec(line.text.trim());
|
|
70
|
+
return match !== null && (match[1] === undefined || LETTER.test(match[1]));
|
|
71
|
+
};
|
|
72
|
+
const isQuoted = (line: Line | undefined): boolean => line !== undefined && QUOTED.test(line.text);
|
|
73
|
+
const endOf = (line: Line): number => line.start + line.text.length;
|
|
74
|
+
const spanOfLines = (first: Line, last: Line): Span => ({ start: first.start, end: endOf(last) });
|
|
75
|
+
|
|
76
|
+
/** The first index at or after `from` whose line meets `test`, or -1. Scans forward only, so a long document stays linear. */
|
|
77
|
+
const indexFrom = (lines: readonly Line[], from: number, test: (line: Line, index: number) => boolean): number => {
|
|
78
|
+
for (let index = Math.max(0, from); index < lines.length; index += 1) {
|
|
79
|
+
const line = lines[index];
|
|
80
|
+
if (line !== undefined && test(line, index)) return index;
|
|
81
|
+
}
|
|
82
|
+
return -1;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** The last index at or before `from` whose line meets `test`, or -1. */
|
|
86
|
+
const indexBackFrom = (lines: readonly Line[], from: number, test: (line: Line) => boolean): number => {
|
|
87
|
+
for (let index = Math.min(from, lines.length - 1); index >= 0; index -= 1) {
|
|
88
|
+
const line = lines[index];
|
|
89
|
+
if (line !== undefined && test(line)) return index;
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Runs of lines between blank lines and separator lines. */
|
|
95
|
+
const blocksOf = (lines: readonly Line[]): Line[][] =>
|
|
96
|
+
lines.reduce<Line[][]>((blocks, line, index) => {
|
|
97
|
+
if (isBlank(line) || isSeparator(line) || SIGNATURE.test(line.text)) return blocks;
|
|
98
|
+
const previous = lines[index - 1];
|
|
99
|
+
if (previous !== undefined && !isBlank(previous) && !isSeparator(previous) && !SIGNATURE.test(previous.text)) blocks.at(-1)?.push(line);
|
|
100
|
+
else blocks.push([line]);
|
|
101
|
+
return blocks;
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
/** The block opens a message: nothing but blank lines before it, or before it and the nearest separator line. */
|
|
105
|
+
const opensMessage = (lines: readonly Line[], first: Line): boolean => {
|
|
106
|
+
const last = indexBackFrom(lines, first.number - 2, (line) => !isBlank(line));
|
|
107
|
+
return last === -1 || isSeparator(lines[last]);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const knownField = (text: string, vocabulary: EmailVocabulary): boolean => {
|
|
111
|
+
const name = FIELD.exec(text)?.[1];
|
|
112
|
+
return name !== undefined && vocabulary.headerFields.has(name.toLowerCase());
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isHeaderLine = (line: Line, index: number): boolean =>
|
|
116
|
+
FIELD.test(line.text) || (index > 0 && FOLDED.test(line.text)) || (index === 0 && ENVELOPE.test(line.text));
|
|
117
|
+
|
|
118
|
+
const isHeader = (block: readonly Line[], atTop: boolean, vocabulary: EmailVocabulary): boolean => {
|
|
119
|
+
if (!block.every(isHeaderLine)) return false;
|
|
120
|
+
const known = block.filter((line) => knownField(line.text, vocabulary)).length;
|
|
121
|
+
if (known >= HEADER_MIN_FIELDS) return true;
|
|
122
|
+
// Fewer known fields make a header only at the top of a message, and only if no other field sits beside them: "Summary:" is the writer's.
|
|
123
|
+
return atTop && known > 0 && block.every((line) => !FIELD.test(line.text) || knownField(line.text, vocabulary));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** A header's fields: each line with the folded lines under it. */
|
|
127
|
+
const fieldsOf = (block: readonly Line[]): Line[][] =>
|
|
128
|
+
block.reduce<Line[][]>((fields, line, index) => {
|
|
129
|
+
if (index > 0 && FOLDED.test(line.text)) fields.at(-1)?.push(line);
|
|
130
|
+
else fields.push([line]);
|
|
131
|
+
return fields;
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
/** A written field loses only its label and keeps its value; any other field, and the envelope line, go whole. */
|
|
135
|
+
const fieldMask = (field: readonly Line[], vocabulary: EmailVocabulary): Span | undefined => {
|
|
136
|
+
const first = field[0];
|
|
137
|
+
const last = field.at(-1);
|
|
138
|
+
if (first === undefined || last === undefined) return undefined;
|
|
139
|
+
const label = FIELD.exec(first.text);
|
|
140
|
+
const name = label?.[1]?.toLowerCase();
|
|
141
|
+
if (label === null || name === undefined || !vocabulary.writtenFields.has(name)) return spanOfLines(first, last);
|
|
142
|
+
return { start: first.start, end: first.start + label[0].length };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const headerSpans = (lines: readonly Line[], vocabulary: EmailVocabulary): Span[] =>
|
|
146
|
+
blocksOf(lines).flatMap((block) => {
|
|
147
|
+
const first = block[0];
|
|
148
|
+
if (first === undefined || !isHeader(block, opensMessage(lines, first), vocabulary)) return [];
|
|
149
|
+
return fieldsOf(block).flatMap((field) => fieldMask(field, vocabulary) ?? []);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const isUrlField = (line: Line | undefined): boolean => {
|
|
153
|
+
const label = line === undefined ? null : FIELD.exec(line.text);
|
|
154
|
+
return line !== undefined && label !== null && URL_VALUE.test(line.text.slice(label[0].length).trim());
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* An archive's note where it removed an attachment: right after a separator, a line ending as the lexicon says, then
|
|
159
|
+
* only field lines, the last one a URL. Mailman's "An HTML attachment was scrubbed..." and its "URL: <…>" line.
|
|
160
|
+
*/
|
|
161
|
+
const isAttachmentStub = (lines: readonly Line[], block: readonly Line[], vocabulary: EmailVocabulary): boolean => {
|
|
162
|
+
const [note, ...fields] = block;
|
|
163
|
+
if (note === undefined || block.length > ATTACHMENT_STUB_MAX_LINES) return false;
|
|
164
|
+
const noteText = note.text.trim();
|
|
165
|
+
if (!vocabulary.attachmentNotes.some((ending) => noteText.endsWith(ending))) return false;
|
|
166
|
+
return fields.every((line) => FIELD.test(line.text)) && isUrlField(fields.at(-1)) && isSeparator(lines[note.number - 2]);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const attachmentStubSpans = (lines: readonly Line[], vocabulary: EmailVocabulary): Span[] =>
|
|
170
|
+
blocksOf(lines).flatMap((block) => {
|
|
171
|
+
const first = block[0];
|
|
172
|
+
const last = block.at(-1);
|
|
173
|
+
return first !== undefined && last !== undefined && isAttachmentStub(lines, block, vocabulary) ? [spanOfLines(first, last)] : [];
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/** From the "-- " line: the blank lines right after it, then its lines up to a blank line, a separator or a quote. */
|
|
177
|
+
const signatureAt = (lines: readonly Line[], delimiter: Line): Span => {
|
|
178
|
+
const firstText = indexFrom(lines, delimiter.number, (line) => !isBlank(line));
|
|
179
|
+
if (firstText === -1) return spanOfLines(delimiter, delimiter);
|
|
180
|
+
const stop = indexFrom(
|
|
181
|
+
lines,
|
|
182
|
+
firstText,
|
|
183
|
+
(line, index) => index - firstText >= SIGNATURE_MAX_LINES || isBlank(line) || isSeparator(line) || isQuoted(line) || SIGNATURE.test(line.text),
|
|
184
|
+
);
|
|
185
|
+
return spanOfLines(delimiter, lines[(stop === -1 ? lines.length : stop) - 1] ?? delimiter);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const signatureSpans = (lines: readonly Line[]): Span[] => lines.filter((line) => SIGNATURE.test(line.text)).map((line) => signatureAt(lines, line));
|
|
189
|
+
|
|
190
|
+
const endsWithAttribution = (text: string, vocabulary: EmailVocabulary): boolean => {
|
|
191
|
+
const trimmed = text.trimEnd();
|
|
192
|
+
if (ADDRESS_MARK.test(BRACKETED_THEN_COLON.exec(trimmed)?.[1] ?? "")) return true;
|
|
193
|
+
if (!/[::]$/u.test(trimmed)) return false;
|
|
194
|
+
const words = trimmed.slice(0, -1).trimEnd();
|
|
195
|
+
return vocabulary.attributions.some((word) => {
|
|
196
|
+
if (!words.endsWith(word)) return false;
|
|
197
|
+
const before = words.at(-word.length - 1) ?? "";
|
|
198
|
+
return !(LATIN_LETTER.test(word.charAt(0)) && LATIN_LETTER.test(before));
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** The paragraph that ends right before `index`, skipping blank lines: its lines, if it is short enough to be an attribution. */
|
|
203
|
+
const attributionBefore = (lines: readonly Line[], index: number, vocabulary: EmailVocabulary): Line[] | undefined => {
|
|
204
|
+
const lastText = indexBackFrom(lines, index - 1, (line) => !isBlank(line));
|
|
205
|
+
if (lastText === -1) return undefined;
|
|
206
|
+
const floor = lastText - ATTRIBUTION_MAX_LINES;
|
|
207
|
+
const window = lines.slice(Math.max(0, floor), lastText + 1);
|
|
208
|
+
const opening = window.findLastIndex((line) => isBlank(line) || isSeparator(line) || isQuoted(line));
|
|
209
|
+
if (opening === -1 && floor > 0) return undefined;
|
|
210
|
+
const paragraph = window.slice(opening + 1);
|
|
211
|
+
if (paragraph.length === 0 || paragraph.length > ATTRIBUTION_MAX_LINES) return undefined;
|
|
212
|
+
return endsWithAttribution(paragraph.map((line) => line.text.trim()).join(" "), vocabulary) ? paragraph : undefined;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** A quoted line, or a line with text run on from a quoted line with text (a lazy continuation, as Markdown reads it). */
|
|
216
|
+
const continuesQuote = (lines: readonly Line[], index: number): boolean => {
|
|
217
|
+
const line = lines[index];
|
|
218
|
+
if (isQuoted(line)) return true;
|
|
219
|
+
const previous = lines[index - 1];
|
|
220
|
+
return line !== undefined && !isBlank(line) && !isSeparator(line) && previous !== undefined && QUOTED_TEXT.test(previous.text);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/** The last line of the quote that starts at `index`. Blank lines between quoted lines stay inside it. */
|
|
224
|
+
const quoteEnd = (lines: readonly Line[], index: number): number => {
|
|
225
|
+
const chunk = { start: index };
|
|
226
|
+
for (;;) {
|
|
227
|
+
const next = indexFrom(lines, chunk.start + 1, (_, at) => !continuesQuote(lines, at));
|
|
228
|
+
if (next === -1) return lines.length - 1;
|
|
229
|
+
const resumes = indexFrom(lines, next, (line) => !isBlank(line));
|
|
230
|
+
if (resumes === -1 || !isQuoted(lines[resumes])) return next - 1;
|
|
231
|
+
chunk.start = resumes;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const quoteStarts = (lines: readonly Line[]): number[] => lines.flatMap((line, index) => (isQuoted(line) && !continuesQuote(lines, index - 1) ? [index] : []));
|
|
236
|
+
|
|
237
|
+
type Reply = { readonly attribution: Span; readonly quote: Span };
|
|
238
|
+
|
|
239
|
+
const repliesOf = (lines: readonly Line[], vocabulary: EmailVocabulary): Reply[] =>
|
|
240
|
+
quoteStarts(lines).reduce<Reply[]>((replies, start) => {
|
|
241
|
+
const covered = replies.at(-1);
|
|
242
|
+
const line = lines[start];
|
|
243
|
+
if (line === undefined || (covered !== undefined && line.start < covered.quote.end)) return replies;
|
|
244
|
+
const attribution = attributionBefore(lines, start, vocabulary);
|
|
245
|
+
const first = attribution?.[0];
|
|
246
|
+
const last = attribution?.at(-1);
|
|
247
|
+
if (first === undefined || last === undefined) return replies;
|
|
248
|
+
const end = lines[quoteEnd(lines, start)];
|
|
249
|
+
if (end === undefined) return replies;
|
|
250
|
+
return [...replies, { attribution: spanOfLines(first, last), quote: spanOfLines(first, end) }];
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
const bySpanStart = (left: Span, right: Span): number => left.start - right.start;
|
|
254
|
+
|
|
255
|
+
export const emailParts = (source: string, vocabulary: EmailVocabulary): EmailParts => {
|
|
256
|
+
const lines = linesOf(source);
|
|
257
|
+
const replies = repliesOf(lines, vocabulary);
|
|
258
|
+
const separators = lines.filter((line) => isSeparator(line)).map((line) => spanOfLines(line, line));
|
|
259
|
+
return {
|
|
260
|
+
furniture: [
|
|
261
|
+
...headerSpans(lines, vocabulary),
|
|
262
|
+
...attachmentStubSpans(lines, vocabulary),
|
|
263
|
+
...separators,
|
|
264
|
+
...signatureSpans(lines),
|
|
265
|
+
...replies.map((reply) => reply.attribution),
|
|
266
|
+
].toSorted(bySpanStart),
|
|
267
|
+
replyQuotes: replies.map((reply) => reply.quote),
|
|
268
|
+
};
|
|
269
|
+
};
|