chaffjs 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/api.d.ts +7 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +6 -4
- package/dist/cli-text.js.map +1 -1
- package/dist/commands/grade.d.ts.map +1 -1
- package/dist/commands/grade.js +66 -21
- package/dist/commands/grade.js.map +1 -1
- package/dist/company-kana-spelling.d.ts +6 -1
- package/dist/company-kana-spelling.d.ts.map +1 -1
- package/dist/company-kana-spelling.js +19 -2
- package/dist/company-kana-spelling.js.map +1 -1
- package/dist/company-name-ending.d.ts +7 -0
- package/dist/company-name-ending.d.ts.map +1 -0
- package/dist/company-name-ending.js +44 -0
- package/dist/company-name-ending.js.map +1 -0
- package/dist/company-names.d.ts +11 -3
- package/dist/company-names.d.ts.map +1 -1
- package/dist/company-names.js +27 -14
- package/dist/company-names.js.map +1 -1
- package/dist/cross-run.d.ts.map +1 -1
- package/dist/cross-run.js +1 -0
- package/dist/cross-run.js.map +1 -1
- package/dist/custom/no-rewrite.d.ts +3 -0
- package/dist/custom/no-rewrite.d.ts.map +1 -0
- package/dist/custom/no-rewrite.js +3 -0
- package/dist/custom/no-rewrite.js.map +1 -0
- package/dist/custom/parse.d.ts.map +1 -1
- package/dist/custom/parse.js +14 -2
- package/dist/custom/parse.js.map +1 -1
- package/dist/detectors/age-words.d.ts +5 -0
- package/dist/detectors/age-words.d.ts.map +1 -0
- package/dist/detectors/age-words.js +8 -0
- package/dist/detectors/age-words.js.map +1 -0
- package/dist/detectors/agreement-slip.d.ts.map +1 -1
- package/dist/detectors/agreement-slip.js +4 -0
- package/dist/detectors/agreement-slip.js.map +1 -1
- package/dist/detectors/annual-pay-mismatch.d.ts.map +1 -1
- package/dist/detectors/annual-pay-mismatch.js +2 -12
- package/dist/detectors/annual-pay-mismatch.js.map +1 -1
- package/dist/detectors/blank-structure.d.ts.map +1 -1
- package/dist/detectors/blank-structure.js +4 -3
- package/dist/detectors/blank-structure.js.map +1 -1
- package/dist/detectors/closing-balance-mismatch.d.ts.map +1 -1
- package/dist/detectors/closing-balance-mismatch.js +1 -0
- package/dist/detectors/closing-balance-mismatch.js.map +1 -1
- package/dist/detectors/code-fences.d.ts +5 -0
- package/dist/detectors/code-fences.d.ts.map +1 -1
- package/dist/detectors/code-fences.js +9 -0
- package/dist/detectors/code-fences.js.map +1 -1
- package/dist/detectors/condition-toki.d.ts +16 -0
- package/dist/detectors/condition-toki.d.ts.map +1 -0
- package/dist/detectors/condition-toki.js +42 -0
- package/dist/detectors/condition-toki.js.map +1 -0
- package/dist/detectors/conjugated-entry.d.ts +17 -0
- package/dist/detectors/conjugated-entry.d.ts.map +1 -0
- package/dist/detectors/conjugated-entry.js +50 -0
- package/dist/detectors/conjugated-entry.js.map +1 -0
- package/dist/detectors/content-morphemes.d.ts +2 -0
- package/dist/detectors/content-morphemes.d.ts.map +1 -1
- package/dist/detectors/content-morphemes.js +1 -1
- package/dist/detectors/content-morphemes.js.map +1 -1
- package/dist/detectors/cross-facts.d.ts.map +1 -1
- package/dist/detectors/cross-facts.js +2 -1
- package/dist/detectors/cross-facts.js.map +1 -1
- package/dist/detectors/custom.d.ts.map +1 -1
- package/dist/detectors/custom.js +8 -2
- package/dist/detectors/custom.js.map +1 -1
- package/dist/detectors/document-shape.d.ts.map +1 -1
- package/dist/detectors/document-shape.js +6 -0
- package/dist/detectors/document-shape.js.map +1 -1
- package/dist/detectors/due-date.d.ts.map +1 -1
- package/dist/detectors/due-date.js +12 -2
- package/dist/detectors/due-date.js.map +1 -1
- package/dist/detectors/fact-consistency.d.ts.map +1 -1
- package/dist/detectors/fact-consistency.js +12 -13
- package/dist/detectors/fact-consistency.js.map +1 -1
- package/dist/detectors/form-line.d.ts +9 -0
- package/dist/detectors/form-line.d.ts.map +1 -0
- package/dist/detectors/form-line.js +36 -0
- package/dist/detectors/form-line.js.map +1 -0
- package/dist/detectors/formula-amount.d.ts +3 -0
- package/dist/detectors/formula-amount.d.ts.map +1 -0
- package/dist/detectors/formula-amount.js +31 -0
- package/dist/detectors/formula-amount.js.map +1 -0
- package/dist/detectors/heading-claim.d.ts +14 -0
- package/dist/detectors/heading-claim.d.ts.map +1 -0
- package/dist/detectors/heading-claim.js +49 -0
- package/dist/detectors/heading-claim.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +29 -1
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/installment-total-mismatch.d.ts.map +1 -1
- package/dist/detectors/installment-total-mismatch.js +28 -19
- package/dist/detectors/installment-total-mismatch.js.map +1 -1
- package/dist/detectors/list-parallel.d.ts +5 -2
- package/dist/detectors/list-parallel.d.ts.map +1 -1
- package/dist/detectors/list-parallel.js +45 -7
- package/dist/detectors/list-parallel.js.map +1 -1
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +34 -7
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/number-unit.d.ts +10 -0
- package/dist/detectors/number-unit.d.ts.map +1 -1
- package/dist/detectors/number-unit.js +28 -8
- package/dist/detectors/number-unit.js.map +1 -1
- package/dist/detectors/orthographic-variant.d.ts.map +1 -1
- package/dist/detectors/orthographic-variant.js +4 -3
- package/dist/detectors/orthographic-variant.js.map +1 -1
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +16 -7
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/per-unit-words.d.ts +7 -0
- package/dist/detectors/per-unit-words.d.ts.map +1 -0
- package/dist/detectors/per-unit-words.js +17 -0
- package/dist/detectors/per-unit-words.js.map +1 -0
- package/dist/detectors/period-multiple-mismatch.d.ts.map +1 -1
- package/dist/detectors/period-multiple-mismatch.js +2 -12
- package/dist/detectors/period-multiple-mismatch.js.map +1 -1
- package/dist/detectors/reference-number-variant.d.ts.map +1 -1
- package/dist/detectors/reference-number-variant.js +21 -3
- package/dist/detectors/reference-number-variant.js.map +1 -1
- package/dist/detectors/registry/condition-toki.d.ts +3 -0
- package/dist/detectors/registry/condition-toki.d.ts.map +1 -0
- package/dist/detectors/registry/condition-toki.js +3 -0
- package/dist/detectors/registry/condition-toki.js.map +1 -0
- package/dist/detectors/registry/formula-amount.d.ts +3 -0
- package/dist/detectors/registry/formula-amount.d.ts.map +1 -0
- package/dist/detectors/registry/formula-amount.js +3 -0
- package/dist/detectors/registry/formula-amount.js.map +1 -0
- package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -1
- package/dist/detectors/rent-multiple-mismatch.js +2 -12
- package/dist/detectors/rent-multiple-mismatch.js.map +1 -1
- package/dist/detectors/row-sentence-amounts.d.ts.map +1 -1
- package/dist/detectors/row-sentence-amounts.js +5 -10
- package/dist/detectors/row-sentence-amounts.js.map +1 -1
- package/dist/detectors/stated-amounts.d.ts +4 -0
- package/dist/detectors/stated-amounts.d.ts.map +1 -0
- package/dist/detectors/stated-amounts.js +12 -0
- package/dist/detectors/stated-amounts.js.map +1 -0
- package/dist/detectors/step-time-sum.d.ts.map +1 -1
- package/dist/detectors/step-time-sum.js +2 -1
- package/dist/detectors/step-time-sum.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts +1 -1
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +7 -2
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +10 -6
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/time-order.d.ts.map +1 -1
- package/dist/detectors/time-order.js +17 -13
- package/dist/detectors/time-order.js.map +1 -1
- package/dist/detectors/unknown-word.d.ts +4 -1
- package/dist/detectors/unknown-word.d.ts.map +1 -1
- package/dist/detectors/unknown-word.js +18 -2
- package/dist/detectors/unknown-word.js.map +1 -1
- package/dist/detectors/use-interval.d.ts +1 -0
- package/dist/detectors/use-interval.d.ts.map +1 -1
- package/dist/detectors/use-interval.js +15 -10
- package/dist/detectors/use-interval.js.map +1 -1
- package/dist/detectors/use-maximum.d.ts.map +1 -1
- package/dist/detectors/use-maximum.js +3 -1
- package/dist/detectors/use-maximum.js.map +1 -1
- package/dist/eval.js +3 -3
- package/dist/eval.js.map +1 -1
- package/dist/extension/document-view.d.ts +2 -2
- package/dist/extension/document-view.d.ts.map +1 -1
- package/dist/extension/document-view.js +17 -8
- package/dist/extension/document-view.js.map +1 -1
- package/dist/extension/entry-genres.d.ts +5 -0
- package/dist/extension/entry-genres.d.ts.map +1 -0
- package/dist/extension/entry-genres.js +13 -0
- package/dist/extension/entry-genres.js.map +1 -0
- package/dist/extension/module-detector.d.ts.map +1 -1
- package/dist/extension/module-detector.js +4 -4
- package/dist/extension/module-detector.js.map +1 -1
- package/dist/extension/plugin-parse.d.ts +1 -1
- package/dist/extension/plugin-parse.d.ts.map +1 -1
- package/dist/extension/plugin-parse.js +24 -7
- package/dist/extension/plugin-parse.js.map +1 -1
- package/dist/extension/problem-text.d.ts.map +1 -1
- package/dist/extension/problem-text.js +2 -0
- package/dist/extension/problem-text.js.map +1 -1
- package/dist/facts/age-values.d.ts.map +1 -1
- package/dist/facts/age-values.js +20 -2
- package/dist/facts/age-values.js.map +1 -1
- package/dist/facts/column-units.d.ts +31 -0
- package/dist/facts/column-units.d.ts.map +1 -0
- package/dist/facts/column-units.js +37 -0
- package/dist/facts/column-units.js.map +1 -0
- package/dist/facts/fact-values.d.ts +4 -0
- package/dist/facts/fact-values.d.ts.map +1 -1
- package/dist/facts/fact-values.js +3 -1
- package/dist/facts/fact-values.js.map +1 -1
- package/dist/facts/labelled-facts.d.ts +10 -0
- package/dist/facts/labelled-facts.d.ts.map +1 -1
- package/dist/facts/labelled-facts.js +6 -1
- package/dist/facts/labelled-facts.js.map +1 -1
- package/dist/facts/per-unit.d.ts +36 -0
- package/dist/facts/per-unit.d.ts.map +1 -0
- package/dist/facts/per-unit.js +56 -0
- package/dist/facts/per-unit.js.map +1 -0
- package/dist/facts/row-sentence-amounts.d.ts +3 -12
- package/dist/facts/row-sentence-amounts.d.ts.map +1 -1
- package/dist/facts/row-sentence-amounts.js +1 -15
- package/dist/facts/row-sentence-amounts.js.map +1 -1
- package/dist/facts/table-facts.d.ts +1 -1
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +13 -9
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/facts/term-values.d.ts +13 -1
- package/dist/facts/term-values.d.ts.map +1 -1
- package/dist/facts/term-values.js +71 -12
- package/dist/facts/term-values.js.map +1 -1
- package/dist/fix-plan/text.js +2 -2
- package/dist/fix-plan/text.js.map +1 -1
- package/dist/grade/baseline.d.ts +4 -2
- package/dist/grade/baseline.d.ts.map +1 -1
- package/dist/grade/baseline.js +13 -11
- package/dist/grade/baseline.js.map +1 -1
- package/dist/grade/expect-read.d.ts +16 -0
- package/dist/grade/expect-read.d.ts.map +1 -0
- package/dist/grade/expect-read.js +52 -0
- package/dist/grade/expect-read.js.map +1 -0
- package/dist/grade/expect.d.ts +50 -0
- package/dist/grade/expect.d.ts.map +1 -0
- package/dist/grade/expect.js +53 -0
- package/dist/grade/expect.js.map +1 -0
- package/dist/grade/grade-item.d.ts +1 -1
- package/dist/grade/grade-item.d.ts.map +1 -1
- package/dist/grade/grade-item.js +13 -3
- package/dist/grade/grade-item.js.map +1 -1
- package/dist/grade/item.d.ts +9 -1
- package/dist/grade/item.d.ts.map +1 -1
- package/dist/grade/item.js +32 -7
- package/dist/grade/item.js.map +1 -1
- package/dist/grade/result.d.ts +5 -0
- package/dist/grade/result.d.ts.map +1 -1
- package/dist/grade/results-read.d.ts.map +1 -1
- package/dist/grade/results-read.js +4 -2
- package/dist/grade/results-read.js.map +1 -1
- package/dist/grade/rubric-file.d.ts +23 -0
- package/dist/grade/rubric-file.d.ts.map +1 -0
- package/dist/grade/rubric-file.js +35 -0
- package/dist/grade/rubric-file.js.map +1 -0
- package/dist/grade/rubric.d.ts +2 -0
- package/dist/grade/rubric.d.ts.map +1 -1
- package/dist/grade/rubric.js +75 -47
- package/dist/grade/rubric.js.map +1 -1
- package/dist/grade/setup.d.ts +2 -1
- package/dist/grade/setup.d.ts.map +1 -1
- package/dist/grade/setup.js +2 -2
- package/dist/grade/setup.js.map +1 -1
- package/dist/grade/stamp.d.ts +2 -0
- package/dist/grade/stamp.d.ts.map +1 -1
- package/dist/grade/stamp.js +8 -2
- package/dist/grade/stamp.js.map +1 -1
- package/dist/grade/task-rubric.d.ts +16 -0
- package/dist/grade/task-rubric.d.ts.map +1 -0
- package/dist/grade/task-rubric.js +34 -0
- package/dist/grade/task-rubric.js.map +1 -0
- package/dist/grade/text.d.ts +5 -1
- package/dist/grade/text.d.ts.map +1 -1
- package/dist/grade/text.js +16 -4
- package/dist/grade/text.js.map +1 -1
- package/dist/lexicon-genres.d.ts +7 -0
- package/dist/lexicon-genres.d.ts.map +1 -0
- package/dist/lexicon-genres.js +8 -0
- package/dist/lexicon-genres.js.map +1 -0
- package/dist/name-char-reading.d.ts +5 -0
- package/dist/name-char-reading.d.ts.map +1 -1
- package/dist/name-char-reading.js +15 -0
- package/dist/name-char-reading.js.map +1 -1
- package/dist/name-end-letter.d.ts +19 -0
- package/dist/name-end-letter.d.ts.map +1 -0
- package/dist/name-end-letter.js +37 -0
- package/dist/name-end-letter.js.map +1 -0
- package/dist/name-neighbours.d.ts.map +1 -1
- package/dist/name-neighbours.js +2 -1
- package/dist/name-neighbours.js.map +1 -1
- package/dist/name-separator.d.ts +6 -0
- package/dist/name-separator.d.ts.map +1 -1
- package/dist/name-separator.js +29 -0
- package/dist/name-separator.js.map +1 -1
- package/dist/name-variants.d.ts +6 -3
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +37 -10
- package/dist/name-variants.js.map +1 -1
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +11 -2
- package/dist/number-name.js.map +1 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/cue-lines.d.ts +6 -0
- package/dist/profile/cue-lines.d.ts.map +1 -0
- package/dist/profile/cue-lines.js +13 -0
- package/dist/profile/cue-lines.js.map +1 -0
- package/dist/profile/parse.d.ts +1 -1
- package/dist/profile/parse.d.ts.map +1 -1
- package/dist/profile/parse.js +8 -2
- package/dist/profile/parse.js.map +1 -1
- package/dist/reference-numbers.d.ts +2 -0
- package/dist/reference-numbers.d.ts.map +1 -1
- package/dist/reference-numbers.js +1 -1
- package/dist/reference-numbers.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1 -0
- package/dist/run.js.map +1 -1
- package/dist/spaced-person-name.d.ts.map +1 -1
- package/dist/spaced-person-name.js +26 -1
- package/dist/spaced-person-name.js.map +1 -1
- package/dist/spacing-code.d.ts.map +1 -1
- package/dist/spacing-code.js +10 -1
- package/dist/spacing-code.js.map +1 -1
- package/dist/spacing-minority.d.ts +7 -0
- package/dist/spacing-minority.d.ts.map +1 -1
- package/dist/spacing-minority.js +14 -0
- package/dist/spacing-minority.js.map +1 -1
- package/dist/structure/closing-balance.d.ts +9 -0
- package/dist/structure/closing-balance.d.ts.map +1 -1
- package/dist/structure/closing-balance.js +34 -7
- package/dist/structure/closing-balance.js.map +1 -1
- package/dist/structure/date-range.d.ts +3 -2
- package/dist/structure/date-range.d.ts.map +1 -1
- package/dist/structure/date-range.js +4 -4
- package/dist/structure/date-range.js.map +1 -1
- package/dist/structure/definition-use.d.ts.map +1 -1
- package/dist/structure/definition-use.js +20 -1
- package/dist/structure/definition-use.js.map +1 -1
- package/dist/structure/document-context.d.ts +2 -0
- package/dist/structure/document-context.d.ts.map +1 -0
- package/dist/structure/document-context.js +42 -0
- package/dist/structure/document-context.js.map +1 -0
- package/dist/structure/fenced-lines.d.ts +7 -0
- package/dist/structure/fenced-lines.d.ts.map +1 -0
- package/dist/structure/fenced-lines.js +27 -0
- package/dist/structure/fenced-lines.js.map +1 -0
- package/dist/structure/formula-amount.d.ts +38 -0
- package/dist/structure/formula-amount.d.ts.map +1 -0
- package/dist/structure/formula-amount.js +187 -0
- package/dist/structure/formula-amount.js.map +1 -0
- package/dist/structure/installment-fee.d.ts +52 -0
- package/dist/structure/installment-fee.d.ts.map +1 -0
- package/dist/structure/installment-fee.js +110 -0
- package/dist/structure/installment-fee.js.map +1 -0
- package/dist/structure/installment-total.d.ts +7 -0
- package/dist/structure/installment-total.d.ts.map +1 -1
- package/dist/structure/installment-total.js +6 -6
- package/dist/structure/installment-total.js.map +1 -1
- package/dist/structure/labelled-date-order.d.ts +31 -9
- package/dist/structure/labelled-date-order.d.ts.map +1 -1
- package/dist/structure/labelled-date-order.js +60 -15
- package/dist/structure/labelled-date-order.js.map +1 -1
- package/dist/structure/labelled-order.d.ts +7 -1
- package/dist/structure/labelled-order.d.ts.map +1 -1
- package/dist/structure/labelled-order.js +10 -3
- package/dist/structure/labelled-order.js.map +1 -1
- package/dist/structure/line-amount-label.d.ts.map +1 -1
- package/dist/structure/line-amount-label.js +2 -1
- package/dist/structure/line-amount-label.js.map +1 -1
- package/dist/structure/line-amount.d.ts +0 -5
- package/dist/structure/line-amount.d.ts.map +1 -1
- package/dist/structure/line-amount.js +1 -10
- package/dist/structure/line-amount.js.map +1 -1
- package/dist/structure/prose-total.d.ts +8 -0
- package/dist/structure/prose-total.d.ts.map +1 -1
- package/dist/structure/prose-total.js +70 -2
- package/dist/structure/prose-total.js.map +1 -1
- package/dist/structure/runs.d.ts.map +1 -1
- package/dist/structure/runs.js +9 -1
- package/dist/structure/runs.js.map +1 -1
- package/dist/structure/signed-date-line.d.ts +15 -0
- package/dist/structure/signed-date-line.d.ts.map +1 -0
- package/dist/structure/signed-date-line.js +68 -0
- package/dist/structure/signed-date-line.js.map +1 -0
- package/dist/structure/start-end-range.d.ts +30 -0
- package/dist/structure/start-end-range.d.ts.map +1 -0
- package/dist/structure/start-end-range.js +79 -0
- package/dist/structure/start-end-range.js.map +1 -0
- package/dist/structure/tax.d.ts.map +1 -1
- package/dist/structure/tax.js +5 -3
- package/dist/structure/tax.js.map +1 -1
- package/dist/structure/total-line.d.ts.map +1 -1
- package/dist/structure/total-line.js +10 -6
- package/dist/structure/total-line.js.map +1 -1
- package/dist/structure/total.d.ts +2 -0
- package/dist/structure/total.d.ts.map +1 -1
- package/dist/structure/total.js +34 -3
- package/dist/structure/total.js.map +1 -1
- package/dist/subject-codes.d.ts +21 -0
- package/dist/subject-codes.d.ts.map +1 -0
- package/dist/subject-codes.js +79 -0
- package/dist/subject-codes.js.map +1 -0
- package/dist/unlabelled-references.d.ts +6 -2
- package/dist/unlabelled-references.d.ts.map +1 -1
- package/dist/unlabelled-references.js +11 -10
- package/dist/unlabelled-references.js.map +1 -1
- package/genres.yaml +17 -0
- package/package.json +4 -4
- package/profiles/listing.yaml +1 -1
- package/profiles/property-sale.yaml +21 -0
- package/rules/closing-balance-mismatch.yaml +3 -3
- package/rules/date-range-reversed.yaml +4 -4
- package/rules/due-before-issue.yaml +9 -5
- package/rules/fact-conflict.yaml +2 -2
- package/rules/formula-amount-mismatch.yaml +67 -0
- package/rules/heading-echo.yaml +2 -2
- package/rules/installment-total-mismatch.yaml +9 -4
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +4 -4
- package/rules/list-item-form-mix.yaml +1 -1
- package/rules/name-variant.yaml +5 -2
- package/rules/no-lead.yaml +2 -2
- package/rules/number-without-unit.yaml +2 -2
- package/rules/orthographic-variant.yaml +3 -3
- package/rules/reference-number-variant.yaml +3 -3
- package/rules/toki-as-condition.yaml +71 -0
- package/rules/total-mismatch.yaml +4 -4
- package/rules/unknown-word.yaml +2 -2
- package/rules/use-before-definition.yaml +2 -2
- package/src/api.ts +11 -2
- package/src/cli-text.ts +6 -4
- package/src/commands/grade.ts +75 -23
- package/src/company-kana-spelling.ts +22 -2
- package/src/company-name-ending.ts +50 -0
- package/src/company-names.ts +52 -17
- package/src/cross-run.ts +1 -0
- package/src/custom/no-rewrite.ts +2 -0
- package/src/custom/parse.ts +18 -2
- package/src/detectors/age-words.ts +12 -0
- package/src/detectors/agreement-slip.ts +3 -0
- package/src/detectors/annual-pay-mismatch.ts +3 -18
- package/src/detectors/blank-structure.ts +4 -3
- package/src/detectors/closing-balance-mismatch.ts +1 -0
- package/src/detectors/code-fences.ts +10 -0
- package/src/detectors/condition-toki.ts +60 -0
- package/src/detectors/conjugated-entry.ts +53 -0
- package/src/detectors/content-morphemes.ts +2 -1
- package/src/detectors/cross-facts.ts +2 -1
- package/src/detectors/custom.ts +9 -2
- package/src/detectors/document-shape.ts +8 -0
- package/src/detectors/due-date.ts +17 -7
- package/src/detectors/fact-consistency.ts +13 -17
- package/src/detectors/form-line.ts +47 -0
- package/src/detectors/formula-amount.ts +43 -0
- package/src/detectors/heading-claim.ts +76 -0
- package/src/detectors/heading-echo.ts +32 -1
- package/src/detectors/installment-total-mismatch.ts +35 -24
- package/src/detectors/list-parallel.ts +46 -7
- package/src/detectors/name-variant.ts +45 -8
- package/src/detectors/number-unit.ts +35 -9
- package/src/detectors/orthographic-variant.ts +5 -3
- package/src/detectors/orthography.ts +17 -8
- package/src/detectors/per-unit-words.ts +24 -0
- package/src/detectors/period-multiple-mismatch.ts +2 -18
- package/src/detectors/reference-number-variant.ts +27 -5
- package/src/detectors/registry/condition-toki.ts +4 -0
- package/src/detectors/registry/formula-amount.ts +4 -0
- package/src/detectors/rent-multiple-mismatch.ts +2 -18
- package/src/detectors/row-sentence-amounts.ts +5 -20
- package/src/detectors/stated-amounts.ts +20 -0
- package/src/detectors/step-time-sum.ts +2 -1
- package/src/detectors/structure-tree.ts +7 -2
- package/src/detectors/team.ts +13 -7
- package/src/detectors/time-order.ts +6 -3
- package/src/detectors/unknown-word.ts +20 -2
- package/src/detectors/use-interval.ts +8 -4
- package/src/detectors/use-maximum.ts +3 -1
- package/src/eval.ts +3 -3
- package/src/extension/document-view.ts +18 -9
- package/src/extension/entry-genres.ts +15 -0
- package/src/extension/module-detector.ts +9 -4
- package/src/extension/plugin-parse.ts +29 -7
- package/src/extension/problem-text.ts +4 -0
- package/src/facts/age-values.ts +24 -2
- package/src/facts/column-units.ts +67 -0
- package/src/facts/fact-values.ts +11 -1
- package/src/facts/labelled-facts.ts +16 -2
- package/src/facts/per-unit.ts +105 -0
- package/src/facts/row-sentence-amounts.ts +4 -26
- package/src/facts/table-facts.ts +7 -4
- package/src/facts/term-values.ts +94 -13
- package/src/fix-plan/text.ts +2 -2
- package/src/grade/baseline.ts +20 -12
- package/src/grade/expect-read.ts +65 -0
- package/src/grade/expect.ts +74 -0
- package/src/grade/grade-item.ts +14 -3
- package/src/grade/item.ts +48 -8
- package/src/grade/result.ts +5 -0
- package/src/grade/results-read.ts +7 -2
- package/src/grade/rubric-file.ts +46 -0
- package/src/grade/rubric.ts +77 -47
- package/src/grade/setup.ts +3 -3
- package/src/grade/stamp.ts +10 -2
- package/src/grade/task-rubric.ts +39 -0
- package/src/grade/text.ts +27 -5
- package/src/lexicon-genres.ts +11 -0
- package/src/name-char-reading.ts +16 -0
- package/src/name-end-letter.ts +52 -0
- package/src/name-neighbours.ts +2 -1
- package/src/name-separator.ts +32 -0
- package/src/name-variants.ts +54 -13
- package/src/number-name.ts +12 -3
- package/src/plugin.ts +6 -0
- package/src/profile/cue-lines.ts +14 -0
- package/src/profile/parse.ts +11 -2
- package/src/reference-numbers.ts +1 -1
- package/src/run.ts +1 -0
- package/src/spaced-person-name.ts +27 -2
- package/src/spacing-code.ts +13 -2
- package/src/spacing-minority.ts +17 -0
- package/src/structure/closing-balance.ts +46 -8
- package/src/structure/date-range.ts +4 -3
- package/src/structure/definition-use.ts +24 -1
- package/src/structure/document-context.ts +46 -0
- package/src/structure/fenced-lines.ts +28 -0
- package/src/structure/formula-amount.ts +236 -0
- package/src/structure/installment-fee.ts +201 -0
- package/src/structure/installment-total.ts +6 -6
- package/src/structure/labelled-date-order.ts +105 -21
- package/src/structure/labelled-order.ts +21 -10
- package/src/structure/line-amount-label.ts +2 -1
- package/src/structure/line-amount.ts +1 -11
- package/src/structure/prose-total.ts +81 -2
- package/src/structure/runs.ts +10 -1
- package/src/structure/signed-date-line.ts +78 -0
- package/src/structure/start-end-range.ts +120 -0
- package/src/structure/tax.ts +6 -3
- package/src/structure/total-line.ts +12 -6
- package/src/structure/total.ts +42 -3
- package/src/subject-codes.ts +105 -0
- package/src/unlabelled-references.ts +24 -16
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as Api from "../api.ts";
|
|
2
2
|
import type { BulletList, Lexicon, Markup, Paragraph, ProseDocument, Section, Sentence, Span, Token } from "../plugin.ts";
|
|
3
|
+
import { entriesForGenre } from "../lexicon-genres.ts";
|
|
3
4
|
|
|
4
5
|
// The document as the plugin API shows it (api.ts RuleDocument): copies of only the public fields, frozen. A copy, so
|
|
5
6
|
// chaff's own shapes can change without breaking a plugin; frozen, so a plugin's rule cannot change what the next rule reads.
|
|
@@ -55,7 +56,12 @@ const sectionOf = (section: Section, copy: SentenceCopies): Api.Section => ({
|
|
|
55
56
|
const listOf = (list: BulletList): Api.List => ({ span: spanOf(list.span), itemLengths: [...list.items] });
|
|
56
57
|
|
|
57
58
|
const lexiconOf = (lexicon: Lexicon): Api.Lexicon =>
|
|
58
|
-
lexicon.map((entry) => ({
|
|
59
|
+
lexicon.map((entry) => ({
|
|
60
|
+
pattern: entry.pattern,
|
|
61
|
+
...(entry.instead_of === undefined ? {} : { instead_of: entry.instead_of }),
|
|
62
|
+
...(entry.rewrite === undefined ? {} : { rewrite: entry.rewrite }),
|
|
63
|
+
...(entry.genres === undefined ? {} : { genres: [...entry.genres] }),
|
|
64
|
+
}));
|
|
59
65
|
|
|
60
66
|
const markupOf = (markup: Markup): Api.Markup => ({
|
|
61
67
|
markdown: markup.markdown,
|
|
@@ -68,7 +74,7 @@ const markupOf = (markup: Markup): Api.Markup => ({
|
|
|
68
74
|
|
|
69
75
|
const EMPTY_MARKUP: Markup = { markdown: false, headings: [], images: [], links: [], ids: new Set(), texts: [] };
|
|
70
76
|
|
|
71
|
-
const viewOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
77
|
+
const viewOf = (doc: ProseDocument, genre: string | undefined): Api.RuleDocument => {
|
|
72
78
|
const copy = sentenceCopier();
|
|
73
79
|
const fields = deepFreeze({
|
|
74
80
|
path: doc.path,
|
|
@@ -81,7 +87,7 @@ const viewOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
|
81
87
|
lists: doc.lists.map(listOf),
|
|
82
88
|
listItems: doc.listSpans.map(spanOf),
|
|
83
89
|
links: doc.links.map(spanOf),
|
|
84
|
-
lexicons: Object.fromEntries(Object.entries(doc.lexicons).map(([name, lexicon]) => [name, lexiconOf(lexicon)])),
|
|
90
|
+
lexicons: Object.fromEntries(Object.entries(doc.lexicons).map(([name, lexicon]) => [name, lexiconOf(entriesForGenre(lexicon, genre))])),
|
|
85
91
|
});
|
|
86
92
|
// The markup is read from the Markdown tree only when a rule asks for it, as chaff's own rules do.
|
|
87
93
|
const markup: { value: Api.Markup | undefined } = { value: undefined };
|
|
@@ -97,13 +103,16 @@ const viewOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
|
97
103
|
/** A word list as a detector is given it (DetectorOptions.lexicon): public fields only, frozen. */
|
|
98
104
|
export const frozenLexiconOf = (lexicon: Lexicon): Api.Lexicon => deepFreeze(lexiconOf(lexicon));
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
// By genre too: the run, not the document, carries the genre, so one document may be run under several.
|
|
107
|
+
const views = new WeakMap<ProseDocument, Map<string | undefined, Api.RuleDocument>>();
|
|
101
108
|
|
|
102
|
-
/** The document as a plugin's detector sees it. Made once per document
|
|
103
|
-
export const ruleDocumentOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
104
|
-
const
|
|
109
|
+
/** The document as a plugin's detector sees it in a genre, its word lists holding that genre's entries. Made once per document and genre. */
|
|
110
|
+
export const ruleDocumentOf = (doc: ProseDocument, genre: string | undefined): Api.RuleDocument => {
|
|
111
|
+
const byGenre = views.get(doc) ?? new Map<string | undefined, Api.RuleDocument>();
|
|
112
|
+
views.set(doc, byGenre);
|
|
113
|
+
const known = byGenre.get(genre);
|
|
105
114
|
if (known !== undefined) return known;
|
|
106
|
-
const view = viewOf(doc);
|
|
107
|
-
|
|
115
|
+
const view = viewOf(doc, genre);
|
|
116
|
+
byGenre.set(genre, view);
|
|
108
117
|
return view;
|
|
109
118
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// A word-list entry's genres as a plugin writes them: a non-empty list of the genres and groups chaff knows (legal,
|
|
2
|
+
// blog/tech). Pure: the known ids come in.
|
|
3
|
+
|
|
4
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? String(value);
|
|
5
|
+
|
|
6
|
+
/** What cannot be read in genres, printed: the whole value when it is not a non-empty list, else its unknown items. undefined when it reads. */
|
|
7
|
+
export const genresProblemOf = (genres: unknown, known: ReadonlySet<string>): string | undefined => {
|
|
8
|
+
if (!Array.isArray(genres) || genres.length === 0) return printed(genres);
|
|
9
|
+
const unknown = genres.filter((genre: unknown) => typeof genre !== "string" || !known.has(genre));
|
|
10
|
+
return unknown.length === 0 ? undefined : printed(unknown);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** genres when they read, as a list of ids. */
|
|
14
|
+
export const readGenres = (genres: unknown, known: ReadonlySet<string>): string[] | undefined =>
|
|
15
|
+
Array.isArray(genres) && genresProblemOf(genres, known) === undefined ? genres.filter((genre: unknown) => typeof genre === "string") : undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type * as Api from "../api.ts";
|
|
2
2
|
import type { Detector, Finding, ProseDocument } from "../plugin.ts";
|
|
3
3
|
import { frozenLexiconOf, ruleDocumentOf } from "./document-view.ts";
|
|
4
|
+
import { entriesForGenre } from "../lexicon-genres.ts";
|
|
4
5
|
import { returnedFindings, type ShapeProblem } from "./returned-findings.ts";
|
|
5
6
|
|
|
6
7
|
// A detector a team or a plugin wrote, run as one of chaff's. It reads the document through the plugin API, and what it
|
|
@@ -38,8 +39,12 @@ const messageOf = (error: unknown): string => {
|
|
|
38
39
|
* What the detector returned, as findings, or how it failed. Reading the return can run the plugin's code too (a getter,
|
|
39
40
|
* a Proxy), so it is checked inside the try. A Promise is a wrong return; its rejection must not end the run later.
|
|
40
41
|
*/
|
|
41
|
-
const findingsOf = (
|
|
42
|
-
|
|
42
|
+
const findingsOf = (
|
|
43
|
+
detect: UntrustedDetector,
|
|
44
|
+
view: Api.RuleDocument,
|
|
45
|
+
doc: ProseDocument,
|
|
46
|
+
options: Api.DetectorOptions,
|
|
47
|
+
): { findings: Finding[] } | { failure: RuleFailure } => {
|
|
43
48
|
try {
|
|
44
49
|
const returned: unknown = detect(view, options);
|
|
45
50
|
if (returned instanceof Promise) returned.catch(() => undefined);
|
|
@@ -54,8 +59,8 @@ const findingsOf = (detect: UntrustedDetector, doc: ProseDocument, options: Api.
|
|
|
54
59
|
export const moduleDetector =
|
|
55
60
|
(detect: UntrustedDetector, origin: string): Detector =>
|
|
56
61
|
(doc, options): Finding[] => {
|
|
57
|
-
const lexicon = options.lexicon === undefined ? undefined : frozenLexiconOf(options.lexicon);
|
|
58
|
-
const outcome = findingsOf(detect, doc, Object.freeze({ lexicon }));
|
|
62
|
+
const lexicon = options.lexicon === undefined ? undefined : frozenLexiconOf(entriesForGenre(options.lexicon, options.genre));
|
|
63
|
+
const outcome = findingsOf(detect, ruleDocumentOf(doc, options.genre), doc, Object.freeze({ lexicon }));
|
|
59
64
|
if ("failure" in outcome) throw new PluginRuleFailure(outcome.failure, origin);
|
|
60
65
|
return outcome.findings;
|
|
61
66
|
};
|
|
@@ -8,6 +8,7 @@ import { describeValue } from "./returned-findings.ts";
|
|
|
8
8
|
import { isPluginName } from "./plugin-name.ts";
|
|
9
9
|
import { knownGenres } from "../known-genres.ts";
|
|
10
10
|
import { guideLayerOf, type GuideLayer, type GuideProblem } from "../genre-guide/layer.ts";
|
|
11
|
+
import { genresProblemOf, readGenres } from "./entry-genres.ts";
|
|
11
12
|
|
|
12
13
|
// What a plugin package exports (api.ts PluginSpec), read into what chaff runs: its rules with ids under the plugin's
|
|
13
14
|
// name, their detectors, its word lists and its house styles. A rule is read exactly like a rule in custom_rules, with
|
|
@@ -28,6 +29,7 @@ type PluginProblemKind =
|
|
|
28
29
|
| "name-mismatch"
|
|
29
30
|
| "no-detect"
|
|
30
31
|
| "bad-lexicon"
|
|
32
|
+
| "bad-lexicon-genres"
|
|
31
33
|
| "bad-style";
|
|
32
34
|
|
|
33
35
|
/** plugin: as chaff.yaml writes it. detail: what was there instead, the name expected, the rule, list or style at fault. */
|
|
@@ -137,30 +139,46 @@ const rulesOf = (raw: unknown, context: RulesContext): Rules => {
|
|
|
137
139
|
|
|
138
140
|
// ───────── word lists ─────────
|
|
139
141
|
|
|
140
|
-
const lexiconEntryOf = (value: unknown): LexiconEntry | undefined => {
|
|
142
|
+
const lexiconEntryOf = (value: unknown, known: ReadonlySet<string>): LexiconEntry | undefined => {
|
|
141
143
|
if (typeof value === "string") return value.trim() === "" ? undefined : { pattern: value.trim() };
|
|
142
144
|
if (!isRecord(value) || typeof value["pattern"] !== "string" || value["pattern"].trim() === "") return undefined;
|
|
143
145
|
const [insteadOf, rewrite] = [value["instead_of"], value["rewrite"]];
|
|
146
|
+
const genres = readGenres(value["genres"], known);
|
|
144
147
|
return {
|
|
145
148
|
pattern: value["pattern"].trim(),
|
|
146
149
|
...(typeof insteadOf === "string" && insteadOf.trim() !== "" ? { instead_of: insteadOf.trim() } : {}),
|
|
147
150
|
...(typeof rewrite === "string" && rewrite.trim() !== "" ? { rewrite: rewrite.trim() } : {}),
|
|
151
|
+
...(genres === undefined ? {} : { genres }),
|
|
148
152
|
};
|
|
149
153
|
};
|
|
150
154
|
|
|
151
|
-
const lexiconOf = (words: unknown): Lexicon | undefined => {
|
|
152
|
-
const entries = Array.isArray(words) ? words.map(lexiconEntryOf) : [undefined];
|
|
155
|
+
const lexiconOf = (words: unknown, known: ReadonlySet<string>): Lexicon | undefined => {
|
|
156
|
+
const entries = Array.isArray(words) ? words.map((word: unknown) => lexiconEntryOf(word, known)) : [undefined];
|
|
153
157
|
const readable = entries.flatMap((entry) => (entry === undefined ? [] : [entry]));
|
|
154
158
|
// An empty list would leave its rules running and finding nothing, which reads as a clean document.
|
|
155
159
|
return readable.length === entries.length && readable.length > 0 ? readable : undefined;
|
|
156
160
|
};
|
|
157
161
|
|
|
162
|
+
/** An entry's genres that cannot be read, as its list, language and pattern with what is wrong. */
|
|
163
|
+
const entryGenresProblem = (list: string, language: string, word: unknown, known: ReadonlySet<string>): string[] => {
|
|
164
|
+
if (!isRecord(word) || word["genres"] === undefined) return [];
|
|
165
|
+
const wrong = genresProblemOf(word["genres"], known);
|
|
166
|
+
return wrong === undefined ? [] : [`${list} (${language}) ${printed(word["pattern"])}: ${wrong}`];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const genresProblemsOf = (list: string, raw: unknown, known: ReadonlySet<string>): string[] =>
|
|
170
|
+
isRecord(raw)
|
|
171
|
+
? Object.entries(raw).flatMap(([language, words]) =>
|
|
172
|
+
(Array.isArray(words) ? words : []).flatMap((word: unknown) => entryGenresProblem(list, language, word, known)),
|
|
173
|
+
)
|
|
174
|
+
: [];
|
|
175
|
+
|
|
158
176
|
type ListInLanguage = { readonly language: string; readonly list: string; readonly lexicon: Lexicon };
|
|
159
177
|
|
|
160
178
|
/** One word list in each language it has. undefined when any part of it cannot be read. */
|
|
161
|
-
const listOf = (list: string, raw: unknown): ListInLanguage[] | undefined => {
|
|
179
|
+
const listOf = (list: string, raw: unknown, known: ReadonlySet<string>): ListInLanguage[] | undefined => {
|
|
162
180
|
if (!LOCAL_NAME.test(list) || !isRecord(raw)) return undefined;
|
|
163
|
-
const read = Object.entries(raw).map(([language, words]) => ({ language, lexicon: lexiconOf(words) }));
|
|
181
|
+
const read = Object.entries(raw).map(([language, words]) => ({ language, lexicon: lexiconOf(words, known) }));
|
|
164
182
|
const readable = read.flatMap(({ language, lexicon }) => (lexicon === undefined ? [] : [{ language, list, lexicon }]));
|
|
165
183
|
return readable.length === read.length ? readable : undefined;
|
|
166
184
|
};
|
|
@@ -178,12 +196,16 @@ const byLanguage = (lists: readonly ListInLanguage[], name: string): Record<stri
|
|
|
178
196
|
const lexiconsOf = (raw: unknown, name: string, problem: Problem): Lexicons => {
|
|
179
197
|
if (raw === undefined) return { lexicons: {}, lists: new Set(), problems: [] };
|
|
180
198
|
if (!isRecord(raw)) return { lexicons: {}, lists: new Set(), problems: [problem("bad-lexicon", printed(raw))] };
|
|
181
|
-
const
|
|
199
|
+
const known = new Set(knownGenres());
|
|
200
|
+
const read = Object.entries(raw).map(([list, words]) => ({ list, words, inLanguages: listOf(list, words, known) }));
|
|
182
201
|
const readable = read.flatMap((entry) => entry.inLanguages ?? []);
|
|
183
202
|
return {
|
|
184
203
|
lexicons: byLanguage(readable, name),
|
|
185
204
|
lists: new Set(readable.map((entry) => entry.list)),
|
|
186
|
-
problems:
|
|
205
|
+
problems: [
|
|
206
|
+
...read.filter((entry) => entry.inLanguages === undefined).map((entry) => problem("bad-lexicon", entry.list)),
|
|
207
|
+
...read.flatMap((entry) => genresProblemsOf(entry.list, entry.words, known).map((detail) => problem("bad-lexicon-genres", detail))),
|
|
208
|
+
],
|
|
187
209
|
};
|
|
188
210
|
};
|
|
189
211
|
|
|
@@ -64,6 +64,8 @@ const PLUGIN_TEXT: Texts<PluginText> = {
|
|
|
64
64
|
"name-mismatch": (plugin, detail) => `plugins の ${plugin}: name は ${detail} にしてください(パッケージの名前から決まります)`,
|
|
65
65
|
"no-detect": (plugin, detail) => `plugins の ${plugin}: ルール ${detail} に detect がありません`,
|
|
66
66
|
"bad-lexicon": (plugin, detail) => `plugins の ${plugin}: 語彙表 ${detail} が読めません(名前: { ja: [語, …], en: [語, …] })`,
|
|
67
|
+
"bad-lexicon-genres": (plugin, detail) =>
|
|
68
|
+
`plugins の ${plugin}: 語彙表 ${detail} の genres が読めません。chaff の知っているジャンルかその組の並び([legal] や [blog/tech])で書いてください。npx chaffjs genres で一覧が出ます`,
|
|
67
69
|
"bad-style": (plugin, detail) => `plugins の ${plugin}: スタイルが読めません(${detail})`,
|
|
68
70
|
},
|
|
69
71
|
},
|
|
@@ -87,6 +89,8 @@ const PLUGIN_TEXT: Texts<PluginText> = {
|
|
|
87
89
|
"name-mismatch": (plugin, detail) => `plugins ${plugin}: its name must be ${detail}, which the package's name gives`,
|
|
88
90
|
"no-detect": (plugin, detail) => `plugins ${plugin}: the rule ${detail} has no detect`,
|
|
89
91
|
"bad-lexicon": (plugin, detail) => `plugins ${plugin}: cannot read the word list ${detail} (name: { ja: [word, …], en: [word, …] })`,
|
|
92
|
+
"bad-lexicon-genres": (plugin, detail) =>
|
|
93
|
+
`plugins ${plugin}: cannot read the genres of the word list entry ${detail}; write a list of genres or groups chaff knows ([legal], [blog/tech]). npx chaffjs genres lists them`,
|
|
90
94
|
"bad-style": (plugin, detail) => `plugins ${plugin}: cannot read a style (${detail})`,
|
|
91
95
|
},
|
|
92
96
|
},
|
package/src/facts/age-values.ts
CHANGED
|
@@ -65,14 +65,36 @@ const endsWithMark = (source: string, value: FactValue, marks: readonly string[]
|
|
|
65
65
|
const mayBeAge = (source: string, value: FactValue, after: readonly string[]): boolean =>
|
|
66
66
|
value.kind === "quantity" && (value.unit === "" || endsWithMark(source, value, after) || after.some((mark) => folded(mark).startsWith(folded(value.unit))));
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const TABLE_ROW = /^[ \t]*\|/u;
|
|
69
|
+
const NOT_LETTER = /[^\p{L}\p{N}]+/u;
|
|
70
|
+
|
|
71
|
+
const lettersOf = (text: string): string[] =>
|
|
72
|
+
folded(text)
|
|
73
|
+
.split(NOT_LETTER)
|
|
74
|
+
.filter((word) => word !== "");
|
|
75
|
+
|
|
76
|
+
/** 語の並びの中に、印の語の並びがそのまま続いてあるか(満期 の 満 は印ではない)。 */
|
|
77
|
+
const hasWords = (words: readonly string[], mark: readonly string[]): boolean =>
|
|
78
|
+
mark.length > 0 && words.some((_, index) => mark.every((word, offset) => words[index + offset] === word));
|
|
79
|
+
|
|
80
|
+
/** 表の行の見出しの升(最初の升)に年齢の印の語があり、数がそのすぐ後ろの升にある(| Age at entry | 20 to 70 |)。 */
|
|
81
|
+
const underAgeHeading = (source: string, value: FactValue, words: AgeWords): boolean => {
|
|
82
|
+
const line = source.slice(source.lastIndexOf("\n", value.start - 1) + 1, value.start);
|
|
83
|
+
if (value.unit !== "" || !TABLE_ROW.test(line)) return false;
|
|
84
|
+
const [heading, ...before] = line.trim().slice(1).split("|");
|
|
85
|
+
if (heading === undefined || before.length !== 1) return false;
|
|
86
|
+
const headingWords = lettersOf(heading);
|
|
87
|
+
return words.marks.some((mark) => hasWords(headingWords, lettersOf(mark.pattern)));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** 年齢の印まで延ばした値。印が無ければ marked は false(範囲の片方なら年齢として読む)。年齢を見出しにした表の行の数は、印が無くても年齢。 */
|
|
69
91
|
const markedAge = (source: string, value: FactValue, words: AgeWords): Marked => {
|
|
70
92
|
const after = at(words.marks, "after");
|
|
71
93
|
const inside = endsWithMark(source, value, after);
|
|
72
94
|
const end = inside ? value.end : wordAfter(source, value.end, after);
|
|
73
95
|
const start = wordBefore(source, value.start, at(words.marks, "before"));
|
|
74
96
|
const age: FactValue = { ...value, start: start ?? value.start, end: end ?? value.end, unit: "" };
|
|
75
|
-
return { age, marked: inside || start !== undefined || end !== undefined };
|
|
97
|
+
return { age, marked: inside || start !== undefined || end !== undefined || underAgeHeading(source, value, words) };
|
|
76
98
|
};
|
|
77
99
|
|
|
78
100
|
/** 上限の印まで延ばした値。印があれば上の端。 */
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Span } from "../plugin.ts";
|
|
2
|
+
import { linesOf } from "../structure/lines.ts";
|
|
3
|
+
import { cellsOf, tablesOf } from "./table-facts.ts";
|
|
4
|
+
import { withoutEdgeMarks } from "./trim-marks.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 表の升の値が、列の見出しで何の値か。単価の列(単価、Unit price、時給)の値は数量を掛けて金額になる値で、同じ行の見出しの金額の列
|
|
8
|
+
* (| りんご | 1,200円 |)とは別の量。見出しが単価の列の語(語彙表 unit-price-column)ならその group、group の無い語は UNIT_PRICE。
|
|
9
|
+
* 見出しが何あたりの句(/日、per day、1日あたり)を含めば、その単位。ほかの列の升は IN_FULL(そのままの値)。表の外の値は見出しを
|
|
10
|
+
* 持たないので、どの列の値とも比べる(文の「りんごは120円です」が単価か金額かは、読まないと分からない)。
|
|
11
|
+
*/
|
|
12
|
+
export type HeadingUnit = { readonly pattern: string; readonly group: string };
|
|
13
|
+
|
|
14
|
+
export type HeadingUnitWords = {
|
|
15
|
+
/** 単価の列の見出し。見出しの全体がこの語のとき。 */
|
|
16
|
+
readonly headings: readonly HeadingUnit[];
|
|
17
|
+
/** 何あたりの句。見出しのどこかに、語の途中でなく書いたとき。 */
|
|
18
|
+
readonly phrases: readonly HeadingUnit[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** 単位を言わない単価の列(単価、Unit price)。二つの単価の列どうしは比べ、金額とは比べない。 */
|
|
22
|
+
export const UNIT_PRICE = "unit-price";
|
|
23
|
+
|
|
24
|
+
/** 見出しが何あたりとも言わない列(金額、Amount、料金)。 */
|
|
25
|
+
export const IN_FULL = "";
|
|
26
|
+
|
|
27
|
+
export type ColumnUnit = Span & { readonly column: string };
|
|
28
|
+
|
|
29
|
+
const LATIN = /[a-z0-9]/iu;
|
|
30
|
+
|
|
31
|
+
const folded = (text: string): string => text.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ").trim();
|
|
32
|
+
|
|
33
|
+
/** 句が text のその位置で、英字の語の途中でない(per day は per days の頭、/h は /hr の頭ではない)。 */
|
|
34
|
+
const isWordAt = (text: string, phrase: string, at: number): boolean => {
|
|
35
|
+
const opensWord = LATIN.test(phrase.charAt(0)) && LATIN.test(text.charAt(at - 1));
|
|
36
|
+
const closesWord = LATIN.test(phrase.charAt(phrase.length - 1)) && LATIN.test(text.charAt(at + phrase.length));
|
|
37
|
+
return !opensWord && !closesWord;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const occurrences = (text: string, phrase: string, from = 0): number[] => {
|
|
41
|
+
const at = phrase === "" ? -1 : text.indexOf(phrase, from);
|
|
42
|
+
return at === -1 ? [] : [at, ...occurrences(text, phrase, at + 1)];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const standsIn = (text: string, phrase: string): boolean => occurrences(text, phrase).some((at) => isWordAt(text, phrase, at));
|
|
46
|
+
|
|
47
|
+
const longest = (phrases: readonly HeadingUnit[]): HeadingUnit | undefined =>
|
|
48
|
+
phrases.reduce<HeadingUnit | undefined>((best, phrase) => (best === undefined || phrase.pattern.length > best.pattern.length ? phrase : best), undefined);
|
|
49
|
+
|
|
50
|
+
/** 見出しの言う単位。見出しが単価の列の語ならその group。でなければ見出しに書いた句の単位(二つ当たれば長い句)。どちらも無ければ IN_FULL。 */
|
|
51
|
+
export const headingPerUnit = (heading: string, words: HeadingUnitWords): string => {
|
|
52
|
+
const text = folded(withoutEdgeMarks(heading));
|
|
53
|
+
if (text === "") return IN_FULL;
|
|
54
|
+
const named = words.headings.find((word) => folded(word.pattern) === text);
|
|
55
|
+
return named?.group ?? longest(words.phrases.filter((phrase) => standsIn(text, folded(phrase.pattern))))?.group ?? IN_FULL;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** 文書の表すべての本体の升と、その列の見出しの単位。 */
|
|
59
|
+
export const columnUnits = (source: string, words: HeadingUnitWords): ColumnUnit[] =>
|
|
60
|
+
tablesOf(linesOf(source)).flatMap((table) => {
|
|
61
|
+
const units = cellsOf(table.header).map((cell) => headingPerUnit(cell.text, words));
|
|
62
|
+
return table.rows.flatMap((row) => cellsOf(row).map((cell, index): ColumnUnit => ({ start: cell.start, end: cell.end, column: units[index] ?? IN_FULL })));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** 値のある升の列の単位。表の外の値は undefined。 */
|
|
66
|
+
export const columnUnitAt = (columns: readonly ColumnUnit[], value: Span): string | undefined =>
|
|
67
|
+
columns.find((column) => value.start >= column.start && value.end <= column.end)?.column;
|
package/src/facts/fact-values.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type ValueKind = "quantity" | "date" | "name";
|
|
|
12
12
|
/**
|
|
13
13
|
* bound: upper なら key は上の端(満70歳まで、20 to 70 の 70)。lower は範囲の下の端(満20歳〜満70歳 の 20)。
|
|
14
14
|
* basis: 率に書いた基準(period:year、kind:fixed)。両方に書いた同じ種類の基準が違えば、別の量として比べない。
|
|
15
|
+
* per: 何あたりの値か(1kmあたり15円 の km、per-unit.ts)。何あたりかの違う値、片方だけ何あたりの値は比べない。
|
|
16
|
+
* column: 表の升の値の、列の見出しの単位(単価の列、column-units.ts)。どちらも表の升で、列の単位が違えば比べない。
|
|
15
17
|
*/
|
|
16
18
|
export type FactValue = Span & {
|
|
17
19
|
readonly kind: ValueKind;
|
|
@@ -20,6 +22,8 @@ export type FactValue = Span & {
|
|
|
20
22
|
readonly bound?: "upper";
|
|
21
23
|
readonly lower?: string;
|
|
22
24
|
readonly basis?: RateBasis;
|
|
25
|
+
readonly per?: string;
|
|
26
|
+
readonly column?: string;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
const attr = (node: StructureNode, name: string): string => String(node.attrs[name] ?? "");
|
|
@@ -124,10 +128,16 @@ const comparableDates = (left: string, right: string): [string, string] | undefi
|
|
|
124
128
|
const comparableBounds = (left: FactValue, right: FactValue): boolean =>
|
|
125
129
|
(left.lower === undefined || right.bound === "upper") && (right.lower === undefined || left.bound === "upper");
|
|
126
130
|
|
|
131
|
+
/** 表の外の値は列を持たないので、どの列の値とも比べる。二つの升は、列の見出しの単位が同じときだけ(単価と金額は別の量)。 */
|
|
132
|
+
const sameColumn = (left: FactValue, right: FactValue): boolean => left.column === undefined || right.column === undefined || left.column === right.column;
|
|
133
|
+
|
|
127
134
|
/** 同じ種類の値で、比べられるもの。数量は同じ単位どうし(単位の違いは別の rule が見る)。 */
|
|
128
135
|
export const comparable = (left: FactValue, right: FactValue): boolean => {
|
|
129
136
|
if (left.kind !== right.kind) return false;
|
|
130
|
-
if (left.kind === "quantity")
|
|
137
|
+
if (left.kind === "quantity")
|
|
138
|
+
return (
|
|
139
|
+
left.unit === right.unit && left.per === right.per && sameColumn(left, right) && comparableBounds(left, right) && basesAgree(left.basis, right.basis)
|
|
140
|
+
);
|
|
131
141
|
return left.kind !== "date" || comparableDates(left.key, right.key) !== undefined;
|
|
132
142
|
};
|
|
133
143
|
|
|
@@ -10,7 +10,14 @@ import { isRate, joinsNextClause, rateHead, rateTail, type RateBasis, type RateW
|
|
|
10
10
|
* 「$300 per person」)、条件の付いた値なので読まない。読点のあとから始まる名前(「大人は、」「For adults, the fee」)も、
|
|
11
11
|
* 前に条件があるので読まない。言語の知識(区切り、値の終わり、落とす冠詞)は言語パッケージの語彙表から取る。
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/** clause: 主語から書いた節(you can apply)。前に読点があれば、読点の前の句は条件(For the basic plan, you can apply)。 */
|
|
14
|
+
export type AttributePhrase = { readonly pattern: string; readonly position: "before" | "after"; readonly clause?: boolean };
|
|
15
|
+
|
|
16
|
+
type AttributeEntry = { readonly pattern: string; readonly group?: string | undefined; readonly position?: "before" | "after" | undefined };
|
|
17
|
+
|
|
18
|
+
/** 語彙表の言い回し。group が clause なら主語から書いた節。 */
|
|
19
|
+
export const attributePhrasesOf = (entries: readonly AttributeEntry[]): AttributePhrase[] =>
|
|
20
|
+
entries.map((entry) => ({ pattern: entry.pattern, position: entry.position ?? "before", clause: entry.group === "clause" }));
|
|
14
21
|
|
|
15
22
|
export type FactWords = {
|
|
16
23
|
/** 名前と値の区切り(:、は、is)。英字だけの区切りは、前が空白のときだけ。 */
|
|
@@ -173,6 +180,9 @@ const labelledFact = (source: string, read: FactValue, words: FactWords): Fact |
|
|
|
173
180
|
return undefined;
|
|
174
181
|
};
|
|
175
182
|
|
|
183
|
+
/** 前に置いた言い回しの前に、同じ文の読点がある(For the basic plan, you can apply)。読点の前の句は条件。 */
|
|
184
|
+
const afterCondition = (head: string, phrase: string): boolean => CLAUSE_COMMA.test(afterLastBreak(head.slice(0, head.length - phrase.length)));
|
|
185
|
+
|
|
176
186
|
/** 属性の言い回しのすぐ前か後ろの値。名前は言い回しそのもの。 */
|
|
177
187
|
const attributeFact = (source: string, value: FactValue, words: FactWords): Fact | undefined => {
|
|
178
188
|
const head = trimEndOf(source.slice(lineStartOf(source, value.start), value.start), EDGE_MARKS).toLowerCase();
|
|
@@ -183,7 +193,11 @@ const attributeFact = (source: string, value: FactValue, words: FactWords): Fact
|
|
|
183
193
|
const found = words.attributes.find((phrase) =>
|
|
184
194
|
phrase.position === "before" ? head.endsWith(phrase.pattern.toLowerCase()) : tail.startsWith(phrase.pattern.toLowerCase()),
|
|
185
195
|
);
|
|
186
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
found === undefined ||
|
|
198
|
+
(found.position === "before" && (!endsAfter(source, value, words) || (found.clause === true && afterCondition(head, found.pattern))))
|
|
199
|
+
)
|
|
200
|
+
return undefined;
|
|
187
201
|
return { label: found.pattern, key: keyOf(found.pattern, words), value };
|
|
188
202
|
};
|
|
189
203
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { FactValue } from "./fact-values.ts";
|
|
2
|
+
import { columnUnitAt, columnUnits, UNIT_PRICE, type HeadingUnitWords } from "./column-units.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 値が何あたりか(1kmあたり15円、毎月5,000円、$0.45 per mile、日額5,000円)。単位が量(時間、日、月、km、mile。語彙表 rate-unit)の
|
|
6
|
+
* 値は、その量を掛けて金額になる単価なので、同じ単位あたりの値とだけ比べる。1kmあたり15円 と 表の 1,650円 は別の量。
|
|
7
|
+
* 件や回ごと(1件につき220円、for each transfer)は量ではない。手数料の表は 1件分 を印なしで書く(| 振込手数料 | 165円 |)ので、
|
|
8
|
+
* 何あたりとは読まず、これまでどおり比べる。決まった印(日額、/km、a day)は語彙表 per-unit-mark が単位まで言う。
|
|
9
|
+
* 型(*あたり、毎*、per *)は * に rate-unit の単位の語(日 は day)を入れた句だけを読む。型の position は値のどちら側に書くか。
|
|
10
|
+
*/
|
|
11
|
+
export type PerUnitMark = { readonly pattern: string; readonly group: string };
|
|
12
|
+
|
|
13
|
+
export type PerUnitTemplate = { readonly pattern: string; readonly position: "before" | "after" };
|
|
14
|
+
|
|
15
|
+
export type PerUnitWords = {
|
|
16
|
+
/** 単位まで決まった印(日額、/km、a day)。group が単位。 */
|
|
17
|
+
readonly marks: readonly PerUnitMark[];
|
|
18
|
+
/** * が単位の語の型(*あたり、毎*、per *)。 */
|
|
19
|
+
readonly templates: readonly PerUnitTemplate[];
|
|
20
|
+
/** 量の単位の語と、それが数える単位(日 は day)。ここに無い語(件、回)は何あたりとは読まない。 */
|
|
21
|
+
readonly units: readonly PerUnitMark[];
|
|
22
|
+
/** 百分率の単位(%)。率の期間(年4.8%)は率の基準(rate-values)が見て、期間を書かない率とも比べるので、ここでは読まない。 */
|
|
23
|
+
readonly percentUnits: readonly string[];
|
|
24
|
+
/** 単価の列の見出し(単価、Unit price、時給)。その列の升は、見出しの単位あたりの値(column-units.ts)。 */
|
|
25
|
+
readonly priceHeadings: readonly { readonly pattern: string; readonly group?: string }[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const PLACEHOLDER = "*";
|
|
29
|
+
/** 印を探す、値の前と後ろの字数。「日額」「1日あたり」が値の前の数字の前に書かれる。 */
|
|
30
|
+
const MARK_REACH = 24;
|
|
31
|
+
/** 値の前の型と値のあいだに置ける字(空白、読点。畳んだ後の字)。1kmあたり、15円。 */
|
|
32
|
+
const GAP_CHARS: ReadonlySet<string> = new Set([" ", "、", ","]);
|
|
33
|
+
const LETTER_OR_DIGIT = /[\p{L}\p{N}]/u;
|
|
34
|
+
const LETTER = /\p{L}/u;
|
|
35
|
+
|
|
36
|
+
const folded = (text: string): string => text.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ");
|
|
37
|
+
|
|
38
|
+
const withoutGapAtEnd = (text: string): string => (GAP_CHARS.has(text.slice(-1)) ? withoutGapAtEnd(text.slice(0, -1)) : text);
|
|
39
|
+
|
|
40
|
+
/** 型の * に単位の語を入れた句と、その単位。* の無い型は単位を言わないので、句にならない。 */
|
|
41
|
+
type Phrase = { readonly text: string; readonly group: string; readonly unitFirst: boolean };
|
|
42
|
+
|
|
43
|
+
const phrasesOf = (template: PerUnitTemplate, units: readonly PerUnitMark[]): Phrase[] => {
|
|
44
|
+
const pattern = folded(template.pattern);
|
|
45
|
+
const at = pattern.indexOf(PLACEHOLDER);
|
|
46
|
+
if (at === -1) return [];
|
|
47
|
+
return units.map((unit) => ({ text: `${pattern.slice(0, at)}${folded(unit.pattern)}${pattern.slice(at + 1)}`, group: unit.group, unitFirst: at === 0 }));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** 印がちょうど値の後ろに始まり、語の途中で切れていない(per mile は per milestone の頭ではない)。 */
|
|
51
|
+
const startsAfter = (after: string, text: string): boolean => after.startsWith(text) && !LETTER_OR_DIGIT.test(after.charAt(text.length));
|
|
52
|
+
|
|
53
|
+
/** 印がちょうど値の前で終わる。単位の語で始まる句は、前が字でない(1日につき の 日。利用日につき の 日 ではない)。 */
|
|
54
|
+
const endsBefore = (before: string, text: string, unitFirst: boolean): boolean =>
|
|
55
|
+
before.endsWith(text) && !(unitFirst && LETTER.test(before.charAt(before.length - text.length - 1)));
|
|
56
|
+
|
|
57
|
+
const longest = (groups: readonly { readonly length: number; readonly group: string }[]): string | undefined =>
|
|
58
|
+
groups.reduce<{ readonly length: number; readonly group: string } | undefined>(
|
|
59
|
+
(best, match) => (best === undefined || match.length > best.length ? match : best),
|
|
60
|
+
undefined,
|
|
61
|
+
)?.group;
|
|
62
|
+
|
|
63
|
+
/** 値のすぐ後ろ($100 a day、5,000円/日)かすぐ前(日額5,000円)の、決まった印の単位。 */
|
|
64
|
+
const markOf = (before: string, after: string, marks: readonly PerUnitMark[]): string | undefined =>
|
|
65
|
+
marks.find((mark) => startsAfter(after, folded(mark.pattern)))?.group ?? marks.find((mark) => before.endsWith(folded(mark.pattern)))?.group;
|
|
66
|
+
|
|
67
|
+
/** 値の前(1kmあたり15円、毎月5,000円)か後ろ($0.45 per mile)の型の単位。当たる句が二つあれば長いほう(か月 と 月)。 */
|
|
68
|
+
const templateOf = (before: string, after: string, words: PerUnitWords): string | undefined =>
|
|
69
|
+
longest(
|
|
70
|
+
words.templates.flatMap((template) =>
|
|
71
|
+
phrasesOf(template, words.units)
|
|
72
|
+
.filter((phrase) => (template.position === "before" ? endsBefore(before, phrase.text, phrase.unitFirst) : startsAfter(after, phrase.text)))
|
|
73
|
+
.map((phrase) => ({ length: phrase.text.length, group: phrase.group })),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/** 値が何の量あたりか。量あたりでなければ(件ごと、印なし)undefined。 */
|
|
78
|
+
export const perUnitOf = (source: string, value: FactValue, words: PerUnitWords): string | undefined => {
|
|
79
|
+
const after = folded(source.slice(value.end, value.end + MARK_REACH)).trimStart();
|
|
80
|
+
const before = withoutGapAtEnd(folded(source.slice(Math.max(0, value.start - MARK_REACH), value.start)));
|
|
81
|
+
return markOf(before, after, words.marks) ?? templateOf(before, after, words);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** 列の見出しを読む語: 単価の列の語と、見出しに書く何あたりの句(決まった印と、型に単位の語を入れた句)。 */
|
|
85
|
+
const headingWordsOf = (words: PerUnitWords): HeadingUnitWords => ({
|
|
86
|
+
headings: words.priceHeadings.map((heading) => ({ pattern: heading.pattern, group: heading.group ?? UNIT_PRICE })),
|
|
87
|
+
phrases: [...words.marks, ...words.templates.flatMap((template) => phrasesOf(template, words.units).map(({ text, group }) => ({ pattern: text, group })))],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** 値の書き方と表の列から読む、何あたりか(per)と、表の升ならその列の見出しの単位(column)。 */
|
|
91
|
+
const unitsOf = (source: string, value: FactValue, words: PerUnitWords, column: string | undefined): Pick<FactValue, "per" | "column"> => {
|
|
92
|
+
const percent = words.percentUnits.some((unit) => folded(unit) === folded(value.unit));
|
|
93
|
+
if (value.kind !== "quantity" || percent) return {};
|
|
94
|
+
const per = perUnitOf(source, value, words);
|
|
95
|
+
return { ...(per === undefined ? {} : { per }), ...(column === undefined ? {} : { column }) };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** 数量の値に、何あたりか(1kmあたり15円 の km)と、表の升ならその列の見出しの単位(単価の列)を付ける。 */
|
|
99
|
+
export const withPerUnits = <T extends FactValue>(source: string, values: readonly T[], words: PerUnitWords): T[] => {
|
|
100
|
+
const columns = columnUnits(source, headingWordsOf(words));
|
|
101
|
+
return values.map((value) => {
|
|
102
|
+
const units = unitsOf(source, value, words, columnUnitAt(columns, value));
|
|
103
|
+
return units.per === undefined && units.column === undefined ? value : { ...value, ...units };
|
|
104
|
+
});
|
|
105
|
+
};
|
|
@@ -5,18 +5,15 @@ import { withoutEdgeMarks } from "./trim-marks.ts";
|
|
|
5
5
|
* 表の行の金額と、その行の見出しを主語に書いた文の金額(| 治療・救援費用 | 3,000万円 | と「治療・救援費用は、5,000万円を限度に」)。
|
|
6
6
|
* 文は値の前後に言い回し(入院1日につき、を限度に、is paid at)を書くので、名前付きの値(labelled-facts)としては読めない。
|
|
7
7
|
* 主語が行の見出しとちょうど同じで、同じ範囲にあり、行と文のどちらにも同じ通貨の金額がちょうど一つのときだけ比べる。
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 二つ以上あれば、どれがどれに当たるか(日額と上限)は読まないと分からないので比べない。何あたりの金額(日額、1kmあたり)は、
|
|
9
|
+
* 同じ何あたりの金額とだけ比べる(fact-values の comparable)。日額と月額、1kmあたりの単価とそれを掛けた金額は別の値。
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
export type Amount = FactValue & { readonly period?: string };
|
|
11
|
+
export type Amount = FactValue;
|
|
13
12
|
|
|
14
13
|
export type AmountRow = { readonly label: string; readonly scope: string; readonly amounts: readonly Amount[] };
|
|
15
14
|
|
|
16
15
|
export type SubjectSentence = { readonly text: string; readonly scope: string; readonly amounts: readonly Amount[] };
|
|
17
16
|
|
|
18
|
-
export type PeriodMark = { readonly pattern: string; readonly group: string };
|
|
19
|
-
|
|
20
17
|
export type SubjectWords = {
|
|
21
18
|
/** 主語と述べる部分の区切り(は、は、、is、are)。英字だけの区切りは、前が空白で後ろが語の切れ目のときだけ。 */
|
|
22
19
|
readonly separators: readonly string[];
|
|
@@ -28,8 +25,6 @@ export type SubjectWords = {
|
|
|
28
25
|
|
|
29
26
|
export type RowSentenceConflict = { readonly label: string; readonly value: FactValue; readonly other: FactValue };
|
|
30
27
|
|
|
31
|
-
const SPACE = /^[ \t]?/u;
|
|
32
|
-
const MARK_REACH = 16;
|
|
33
28
|
const STRONG_MARK = /\*\*|__/gu;
|
|
34
29
|
const LATIN_WORD = /^[a-z ]+$/iu;
|
|
35
30
|
const LATIN_LETTER = /[a-z]/iu;
|
|
@@ -38,20 +33,6 @@ const ITEM_MARK = /^(?:[-*+]|\d{1,3}[.)])[ \t]+/u;
|
|
|
38
33
|
|
|
39
34
|
const folded = (text: string): string => text.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ");
|
|
40
35
|
|
|
41
|
-
const markAt = (text: string, marks: readonly PeriodMark[]): string | undefined => {
|
|
42
|
-
const lowered = folded(text);
|
|
43
|
-
return marks.find((mark) => lowered.startsWith(folded(mark.pattern)))?.group;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const markBefore = (text: string, marks: readonly PeriodMark[]): string | undefined => {
|
|
47
|
-
const lowered = folded(text).trimEnd();
|
|
48
|
-
return marks.find((mark) => lowered.endsWith(folded(mark.pattern)))?.group;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** 金額のすぐ前(日額5,000円)かすぐ後ろ($100 a day、5,000円/日)の期間あたりの印の期間。 */
|
|
52
|
-
export const periodOf = (source: string, value: FactValue, marks: readonly PeriodMark[]): string | undefined =>
|
|
53
|
-
markAt(source.slice(value.end).replace(SPACE, ""), marks) ?? markBefore(source.slice(Math.max(0, value.start - MARK_REACH), value.start), marks);
|
|
54
|
-
|
|
55
36
|
const withoutDeterminer = (text: string, determiners: readonly string[]): string => {
|
|
56
37
|
const word = determiners.map(folded).find((determiner) => LATIN_WORD.test(determiner) && text.startsWith(`${determiner} `));
|
|
57
38
|
return word === undefined ? text : text.slice(word.length + 1);
|
|
@@ -87,13 +68,10 @@ const onlyPair = (row: AmountRow, sentence: SubjectSentence, value: Amount): Amo
|
|
|
87
68
|
return inRow.length === 1 && inSentence.length === 1 ? inRow[0] : undefined;
|
|
88
69
|
};
|
|
89
70
|
|
|
90
|
-
/** 期間の違う金額(日額と月額)は別の値。片方にしか印が無ければ比べる(1日につき は語彙表に無い書き方)。 */
|
|
91
|
-
const samePeriod = (left: Amount, right: Amount): boolean => left.period === undefined || right.period === undefined || left.period === right.period;
|
|
92
|
-
|
|
93
71
|
const conflictsWith = (row: AmountRow, sentence: SubjectSentence): RowSentenceConflict[] =>
|
|
94
72
|
sentence.amounts.flatMap((value) => {
|
|
95
73
|
const other = onlyPair(row, sentence, value);
|
|
96
|
-
return other === undefined || sameValue(other, value)
|
|
74
|
+
return other === undefined || sameValue(other, value) ? [] : [{ label: row.label, value, other }];
|
|
97
75
|
});
|
|
98
76
|
|
|
99
77
|
const rowsByScope = (rows: readonly AmountRow[]): ReadonlyMap<string, AmountRow[]> =>
|
package/src/facts/table-facts.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Span } from "../plugin.ts";
|
|
2
2
|
import { linesOf, type Line } from "../structure/lines.ts";
|
|
3
3
|
import { TABLE_RULE } from "../structure/runs.ts";
|
|
4
|
+
import { fencedLineIndexes } from "../structure/fenced-lines.ts";
|
|
4
5
|
import { CELL_SEPARATOR } from "../structure/bare-numbers.ts";
|
|
5
6
|
import type { FactValue } from "./fact-values.ts";
|
|
6
7
|
import type { Fact } from "./labelled-facts.ts";
|
|
@@ -29,15 +30,17 @@ export const cellsOf = (line: Line): Cell[] => {
|
|
|
29
30
|
|
|
30
31
|
const plain = withoutEdgeMarks;
|
|
31
32
|
|
|
32
|
-
/** 区切り行(|---|)の前の行が見出し、後ろに続く |
|
|
33
|
-
export const tablesOf = (lines: readonly Line[]): { header: Line; rows: Line[] }[] =>
|
|
34
|
-
lines.
|
|
33
|
+
/** 区切り行(|---|)の前の行が見出し、後ろに続く | の行が本体。コードブロックの中の表は例なので読まない。 */
|
|
34
|
+
export const tablesOf = (lines: readonly Line[]): { header: Line; rows: Line[] }[] => {
|
|
35
|
+
const fenced = fencedLineIndexes(lines.map((line) => line.text));
|
|
36
|
+
return lines.flatMap((line, index) => {
|
|
35
37
|
const header = lines[index - 1];
|
|
36
|
-
if (!TABLE_RULE.test(line.text) || header === undefined || !header.text.includes("|")) return [];
|
|
38
|
+
if (fenced.has(index) || !TABLE_RULE.test(line.text) || header === undefined || !header.text.includes("|")) return [];
|
|
37
39
|
const after = lines.slice(index + 1);
|
|
38
40
|
const end = after.findIndex((row) => !row.text.includes("|"));
|
|
39
41
|
return [{ header, rows: end === -1 ? after : after.slice(0, end) }];
|
|
40
42
|
});
|
|
43
|
+
};
|
|
41
44
|
|
|
42
45
|
const inside = (cell: Span, value: FactValue): boolean => value.start >= cell.start && value.end <= cell.end;
|
|
43
46
|
|