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.
Files changed (535) hide show
  1. package/README.md +7 -0
  2. package/dist/api.d.ts +7 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js.map +1 -1
  5. package/dist/cli-text.d.ts.map +1 -1
  6. package/dist/cli-text.js +6 -4
  7. package/dist/cli-text.js.map +1 -1
  8. package/dist/commands/grade.d.ts.map +1 -1
  9. package/dist/commands/grade.js +66 -21
  10. package/dist/commands/grade.js.map +1 -1
  11. package/dist/company-kana-spelling.d.ts +6 -1
  12. package/dist/company-kana-spelling.d.ts.map +1 -1
  13. package/dist/company-kana-spelling.js +19 -2
  14. package/dist/company-kana-spelling.js.map +1 -1
  15. package/dist/company-name-ending.d.ts +7 -0
  16. package/dist/company-name-ending.d.ts.map +1 -0
  17. package/dist/company-name-ending.js +44 -0
  18. package/dist/company-name-ending.js.map +1 -0
  19. package/dist/company-names.d.ts +11 -3
  20. package/dist/company-names.d.ts.map +1 -1
  21. package/dist/company-names.js +27 -14
  22. package/dist/company-names.js.map +1 -1
  23. package/dist/cross-run.d.ts.map +1 -1
  24. package/dist/cross-run.js +1 -0
  25. package/dist/cross-run.js.map +1 -1
  26. package/dist/custom/no-rewrite.d.ts +3 -0
  27. package/dist/custom/no-rewrite.d.ts.map +1 -0
  28. package/dist/custom/no-rewrite.js +3 -0
  29. package/dist/custom/no-rewrite.js.map +1 -0
  30. package/dist/custom/parse.d.ts.map +1 -1
  31. package/dist/custom/parse.js +14 -2
  32. package/dist/custom/parse.js.map +1 -1
  33. package/dist/detectors/age-words.d.ts +5 -0
  34. package/dist/detectors/age-words.d.ts.map +1 -0
  35. package/dist/detectors/age-words.js +8 -0
  36. package/dist/detectors/age-words.js.map +1 -0
  37. package/dist/detectors/agreement-slip.d.ts.map +1 -1
  38. package/dist/detectors/agreement-slip.js +4 -0
  39. package/dist/detectors/agreement-slip.js.map +1 -1
  40. package/dist/detectors/annual-pay-mismatch.d.ts.map +1 -1
  41. package/dist/detectors/annual-pay-mismatch.js +2 -12
  42. package/dist/detectors/annual-pay-mismatch.js.map +1 -1
  43. package/dist/detectors/blank-structure.d.ts.map +1 -1
  44. package/dist/detectors/blank-structure.js +4 -3
  45. package/dist/detectors/blank-structure.js.map +1 -1
  46. package/dist/detectors/closing-balance-mismatch.d.ts.map +1 -1
  47. package/dist/detectors/closing-balance-mismatch.js +1 -0
  48. package/dist/detectors/closing-balance-mismatch.js.map +1 -1
  49. package/dist/detectors/code-fences.d.ts +5 -0
  50. package/dist/detectors/code-fences.d.ts.map +1 -1
  51. package/dist/detectors/code-fences.js +9 -0
  52. package/dist/detectors/code-fences.js.map +1 -1
  53. package/dist/detectors/condition-toki.d.ts +16 -0
  54. package/dist/detectors/condition-toki.d.ts.map +1 -0
  55. package/dist/detectors/condition-toki.js +42 -0
  56. package/dist/detectors/condition-toki.js.map +1 -0
  57. package/dist/detectors/conjugated-entry.d.ts +17 -0
  58. package/dist/detectors/conjugated-entry.d.ts.map +1 -0
  59. package/dist/detectors/conjugated-entry.js +50 -0
  60. package/dist/detectors/conjugated-entry.js.map +1 -0
  61. package/dist/detectors/content-morphemes.d.ts +2 -0
  62. package/dist/detectors/content-morphemes.d.ts.map +1 -1
  63. package/dist/detectors/content-morphemes.js +1 -1
  64. package/dist/detectors/content-morphemes.js.map +1 -1
  65. package/dist/detectors/cross-facts.d.ts.map +1 -1
  66. package/dist/detectors/cross-facts.js +2 -1
  67. package/dist/detectors/cross-facts.js.map +1 -1
  68. package/dist/detectors/custom.d.ts.map +1 -1
  69. package/dist/detectors/custom.js +8 -2
  70. package/dist/detectors/custom.js.map +1 -1
  71. package/dist/detectors/document-shape.d.ts.map +1 -1
  72. package/dist/detectors/document-shape.js +6 -0
  73. package/dist/detectors/document-shape.js.map +1 -1
  74. package/dist/detectors/due-date.d.ts.map +1 -1
  75. package/dist/detectors/due-date.js +12 -2
  76. package/dist/detectors/due-date.js.map +1 -1
  77. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  78. package/dist/detectors/fact-consistency.js +12 -13
  79. package/dist/detectors/fact-consistency.js.map +1 -1
  80. package/dist/detectors/form-line.d.ts +9 -0
  81. package/dist/detectors/form-line.d.ts.map +1 -0
  82. package/dist/detectors/form-line.js +36 -0
  83. package/dist/detectors/form-line.js.map +1 -0
  84. package/dist/detectors/formula-amount.d.ts +3 -0
  85. package/dist/detectors/formula-amount.d.ts.map +1 -0
  86. package/dist/detectors/formula-amount.js +31 -0
  87. package/dist/detectors/formula-amount.js.map +1 -0
  88. package/dist/detectors/heading-claim.d.ts +14 -0
  89. package/dist/detectors/heading-claim.d.ts.map +1 -0
  90. package/dist/detectors/heading-claim.js +49 -0
  91. package/dist/detectors/heading-claim.js.map +1 -0
  92. package/dist/detectors/heading-echo.d.ts.map +1 -1
  93. package/dist/detectors/heading-echo.js +29 -1
  94. package/dist/detectors/heading-echo.js.map +1 -1
  95. package/dist/detectors/installment-total-mismatch.d.ts.map +1 -1
  96. package/dist/detectors/installment-total-mismatch.js +28 -19
  97. package/dist/detectors/installment-total-mismatch.js.map +1 -1
  98. package/dist/detectors/list-parallel.d.ts +5 -2
  99. package/dist/detectors/list-parallel.d.ts.map +1 -1
  100. package/dist/detectors/list-parallel.js +45 -7
  101. package/dist/detectors/list-parallel.js.map +1 -1
  102. package/dist/detectors/name-variant.d.ts.map +1 -1
  103. package/dist/detectors/name-variant.js +34 -7
  104. package/dist/detectors/name-variant.js.map +1 -1
  105. package/dist/detectors/number-unit.d.ts +10 -0
  106. package/dist/detectors/number-unit.d.ts.map +1 -1
  107. package/dist/detectors/number-unit.js +28 -8
  108. package/dist/detectors/number-unit.js.map +1 -1
  109. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  110. package/dist/detectors/orthographic-variant.js +4 -3
  111. package/dist/detectors/orthographic-variant.js.map +1 -1
  112. package/dist/detectors/orthography.d.ts.map +1 -1
  113. package/dist/detectors/orthography.js +16 -7
  114. package/dist/detectors/orthography.js.map +1 -1
  115. package/dist/detectors/per-unit-words.d.ts +7 -0
  116. package/dist/detectors/per-unit-words.d.ts.map +1 -0
  117. package/dist/detectors/per-unit-words.js +17 -0
  118. package/dist/detectors/per-unit-words.js.map +1 -0
  119. package/dist/detectors/period-multiple-mismatch.d.ts.map +1 -1
  120. package/dist/detectors/period-multiple-mismatch.js +2 -12
  121. package/dist/detectors/period-multiple-mismatch.js.map +1 -1
  122. package/dist/detectors/reference-number-variant.d.ts.map +1 -1
  123. package/dist/detectors/reference-number-variant.js +21 -3
  124. package/dist/detectors/reference-number-variant.js.map +1 -1
  125. package/dist/detectors/registry/condition-toki.d.ts +3 -0
  126. package/dist/detectors/registry/condition-toki.d.ts.map +1 -0
  127. package/dist/detectors/registry/condition-toki.js +3 -0
  128. package/dist/detectors/registry/condition-toki.js.map +1 -0
  129. package/dist/detectors/registry/formula-amount.d.ts +3 -0
  130. package/dist/detectors/registry/formula-amount.d.ts.map +1 -0
  131. package/dist/detectors/registry/formula-amount.js +3 -0
  132. package/dist/detectors/registry/formula-amount.js.map +1 -0
  133. package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -1
  134. package/dist/detectors/rent-multiple-mismatch.js +2 -12
  135. package/dist/detectors/rent-multiple-mismatch.js.map +1 -1
  136. package/dist/detectors/row-sentence-amounts.d.ts.map +1 -1
  137. package/dist/detectors/row-sentence-amounts.js +5 -10
  138. package/dist/detectors/row-sentence-amounts.js.map +1 -1
  139. package/dist/detectors/stated-amounts.d.ts +4 -0
  140. package/dist/detectors/stated-amounts.d.ts.map +1 -0
  141. package/dist/detectors/stated-amounts.js +12 -0
  142. package/dist/detectors/stated-amounts.js.map +1 -0
  143. package/dist/detectors/step-time-sum.d.ts.map +1 -1
  144. package/dist/detectors/step-time-sum.js +2 -1
  145. package/dist/detectors/step-time-sum.js.map +1 -1
  146. package/dist/detectors/structure-tree.d.ts +1 -1
  147. package/dist/detectors/structure-tree.d.ts.map +1 -1
  148. package/dist/detectors/structure-tree.js +7 -2
  149. package/dist/detectors/structure-tree.js.map +1 -1
  150. package/dist/detectors/team.d.ts.map +1 -1
  151. package/dist/detectors/team.js +10 -6
  152. package/dist/detectors/team.js.map +1 -1
  153. package/dist/detectors/time-order.d.ts.map +1 -1
  154. package/dist/detectors/time-order.js +17 -13
  155. package/dist/detectors/time-order.js.map +1 -1
  156. package/dist/detectors/unknown-word.d.ts +4 -1
  157. package/dist/detectors/unknown-word.d.ts.map +1 -1
  158. package/dist/detectors/unknown-word.js +18 -2
  159. package/dist/detectors/unknown-word.js.map +1 -1
  160. package/dist/detectors/use-interval.d.ts +1 -0
  161. package/dist/detectors/use-interval.d.ts.map +1 -1
  162. package/dist/detectors/use-interval.js +15 -10
  163. package/dist/detectors/use-interval.js.map +1 -1
  164. package/dist/detectors/use-maximum.d.ts.map +1 -1
  165. package/dist/detectors/use-maximum.js +3 -1
  166. package/dist/detectors/use-maximum.js.map +1 -1
  167. package/dist/eval.js +3 -3
  168. package/dist/eval.js.map +1 -1
  169. package/dist/extension/document-view.d.ts +2 -2
  170. package/dist/extension/document-view.d.ts.map +1 -1
  171. package/dist/extension/document-view.js +17 -8
  172. package/dist/extension/document-view.js.map +1 -1
  173. package/dist/extension/entry-genres.d.ts +5 -0
  174. package/dist/extension/entry-genres.d.ts.map +1 -0
  175. package/dist/extension/entry-genres.js +13 -0
  176. package/dist/extension/entry-genres.js.map +1 -0
  177. package/dist/extension/module-detector.d.ts.map +1 -1
  178. package/dist/extension/module-detector.js +4 -4
  179. package/dist/extension/module-detector.js.map +1 -1
  180. package/dist/extension/plugin-parse.d.ts +1 -1
  181. package/dist/extension/plugin-parse.d.ts.map +1 -1
  182. package/dist/extension/plugin-parse.js +24 -7
  183. package/dist/extension/plugin-parse.js.map +1 -1
  184. package/dist/extension/problem-text.d.ts.map +1 -1
  185. package/dist/extension/problem-text.js +2 -0
  186. package/dist/extension/problem-text.js.map +1 -1
  187. package/dist/facts/age-values.d.ts.map +1 -1
  188. package/dist/facts/age-values.js +20 -2
  189. package/dist/facts/age-values.js.map +1 -1
  190. package/dist/facts/column-units.d.ts +31 -0
  191. package/dist/facts/column-units.d.ts.map +1 -0
  192. package/dist/facts/column-units.js +37 -0
  193. package/dist/facts/column-units.js.map +1 -0
  194. package/dist/facts/fact-values.d.ts +4 -0
  195. package/dist/facts/fact-values.d.ts.map +1 -1
  196. package/dist/facts/fact-values.js +3 -1
  197. package/dist/facts/fact-values.js.map +1 -1
  198. package/dist/facts/labelled-facts.d.ts +10 -0
  199. package/dist/facts/labelled-facts.d.ts.map +1 -1
  200. package/dist/facts/labelled-facts.js +6 -1
  201. package/dist/facts/labelled-facts.js.map +1 -1
  202. package/dist/facts/per-unit.d.ts +36 -0
  203. package/dist/facts/per-unit.d.ts.map +1 -0
  204. package/dist/facts/per-unit.js +56 -0
  205. package/dist/facts/per-unit.js.map +1 -0
  206. package/dist/facts/row-sentence-amounts.d.ts +3 -12
  207. package/dist/facts/row-sentence-amounts.d.ts.map +1 -1
  208. package/dist/facts/row-sentence-amounts.js +1 -15
  209. package/dist/facts/row-sentence-amounts.js.map +1 -1
  210. package/dist/facts/table-facts.d.ts +1 -1
  211. package/dist/facts/table-facts.d.ts.map +1 -1
  212. package/dist/facts/table-facts.js +13 -9
  213. package/dist/facts/table-facts.js.map +1 -1
  214. package/dist/facts/term-values.d.ts +13 -1
  215. package/dist/facts/term-values.d.ts.map +1 -1
  216. package/dist/facts/term-values.js +71 -12
  217. package/dist/facts/term-values.js.map +1 -1
  218. package/dist/fix-plan/text.js +2 -2
  219. package/dist/fix-plan/text.js.map +1 -1
  220. package/dist/grade/baseline.d.ts +4 -2
  221. package/dist/grade/baseline.d.ts.map +1 -1
  222. package/dist/grade/baseline.js +13 -11
  223. package/dist/grade/baseline.js.map +1 -1
  224. package/dist/grade/expect-read.d.ts +16 -0
  225. package/dist/grade/expect-read.d.ts.map +1 -0
  226. package/dist/grade/expect-read.js +52 -0
  227. package/dist/grade/expect-read.js.map +1 -0
  228. package/dist/grade/expect.d.ts +50 -0
  229. package/dist/grade/expect.d.ts.map +1 -0
  230. package/dist/grade/expect.js +53 -0
  231. package/dist/grade/expect.js.map +1 -0
  232. package/dist/grade/grade-item.d.ts +1 -1
  233. package/dist/grade/grade-item.d.ts.map +1 -1
  234. package/dist/grade/grade-item.js +13 -3
  235. package/dist/grade/grade-item.js.map +1 -1
  236. package/dist/grade/item.d.ts +9 -1
  237. package/dist/grade/item.d.ts.map +1 -1
  238. package/dist/grade/item.js +32 -7
  239. package/dist/grade/item.js.map +1 -1
  240. package/dist/grade/result.d.ts +5 -0
  241. package/dist/grade/result.d.ts.map +1 -1
  242. package/dist/grade/results-read.d.ts.map +1 -1
  243. package/dist/grade/results-read.js +4 -2
  244. package/dist/grade/results-read.js.map +1 -1
  245. package/dist/grade/rubric-file.d.ts +23 -0
  246. package/dist/grade/rubric-file.d.ts.map +1 -0
  247. package/dist/grade/rubric-file.js +35 -0
  248. package/dist/grade/rubric-file.js.map +1 -0
  249. package/dist/grade/rubric.d.ts +2 -0
  250. package/dist/grade/rubric.d.ts.map +1 -1
  251. package/dist/grade/rubric.js +75 -47
  252. package/dist/grade/rubric.js.map +1 -1
  253. package/dist/grade/setup.d.ts +2 -1
  254. package/dist/grade/setup.d.ts.map +1 -1
  255. package/dist/grade/setup.js +2 -2
  256. package/dist/grade/setup.js.map +1 -1
  257. package/dist/grade/stamp.d.ts +2 -0
  258. package/dist/grade/stamp.d.ts.map +1 -1
  259. package/dist/grade/stamp.js +8 -2
  260. package/dist/grade/stamp.js.map +1 -1
  261. package/dist/grade/task-rubric.d.ts +16 -0
  262. package/dist/grade/task-rubric.d.ts.map +1 -0
  263. package/dist/grade/task-rubric.js +34 -0
  264. package/dist/grade/task-rubric.js.map +1 -0
  265. package/dist/grade/text.d.ts +5 -1
  266. package/dist/grade/text.d.ts.map +1 -1
  267. package/dist/grade/text.js +16 -4
  268. package/dist/grade/text.js.map +1 -1
  269. package/dist/lexicon-genres.d.ts +7 -0
  270. package/dist/lexicon-genres.d.ts.map +1 -0
  271. package/dist/lexicon-genres.js +8 -0
  272. package/dist/lexicon-genres.js.map +1 -0
  273. package/dist/name-char-reading.d.ts +5 -0
  274. package/dist/name-char-reading.d.ts.map +1 -1
  275. package/dist/name-char-reading.js +15 -0
  276. package/dist/name-char-reading.js.map +1 -1
  277. package/dist/name-end-letter.d.ts +19 -0
  278. package/dist/name-end-letter.d.ts.map +1 -0
  279. package/dist/name-end-letter.js +37 -0
  280. package/dist/name-end-letter.js.map +1 -0
  281. package/dist/name-neighbours.d.ts.map +1 -1
  282. package/dist/name-neighbours.js +2 -1
  283. package/dist/name-neighbours.js.map +1 -1
  284. package/dist/name-separator.d.ts +6 -0
  285. package/dist/name-separator.d.ts.map +1 -1
  286. package/dist/name-separator.js +29 -0
  287. package/dist/name-separator.js.map +1 -1
  288. package/dist/name-variants.d.ts +6 -3
  289. package/dist/name-variants.d.ts.map +1 -1
  290. package/dist/name-variants.js +37 -10
  291. package/dist/name-variants.js.map +1 -1
  292. package/dist/number-name.d.ts.map +1 -1
  293. package/dist/number-name.js +11 -2
  294. package/dist/number-name.js.map +1 -1
  295. package/dist/plugin.d.ts +6 -0
  296. package/dist/plugin.d.ts.map +1 -1
  297. package/dist/profile/cue-lines.d.ts +6 -0
  298. package/dist/profile/cue-lines.d.ts.map +1 -0
  299. package/dist/profile/cue-lines.js +13 -0
  300. package/dist/profile/cue-lines.js.map +1 -0
  301. package/dist/profile/parse.d.ts +1 -1
  302. package/dist/profile/parse.d.ts.map +1 -1
  303. package/dist/profile/parse.js +8 -2
  304. package/dist/profile/parse.js.map +1 -1
  305. package/dist/reference-numbers.d.ts +2 -0
  306. package/dist/reference-numbers.d.ts.map +1 -1
  307. package/dist/reference-numbers.js +1 -1
  308. package/dist/reference-numbers.js.map +1 -1
  309. package/dist/run.d.ts.map +1 -1
  310. package/dist/run.js +1 -0
  311. package/dist/run.js.map +1 -1
  312. package/dist/spaced-person-name.d.ts.map +1 -1
  313. package/dist/spaced-person-name.js +26 -1
  314. package/dist/spaced-person-name.js.map +1 -1
  315. package/dist/spacing-code.d.ts.map +1 -1
  316. package/dist/spacing-code.js +10 -1
  317. package/dist/spacing-code.js.map +1 -1
  318. package/dist/spacing-minority.d.ts +7 -0
  319. package/dist/spacing-minority.d.ts.map +1 -1
  320. package/dist/spacing-minority.js +14 -0
  321. package/dist/spacing-minority.js.map +1 -1
  322. package/dist/structure/closing-balance.d.ts +9 -0
  323. package/dist/structure/closing-balance.d.ts.map +1 -1
  324. package/dist/structure/closing-balance.js +34 -7
  325. package/dist/structure/closing-balance.js.map +1 -1
  326. package/dist/structure/date-range.d.ts +3 -2
  327. package/dist/structure/date-range.d.ts.map +1 -1
  328. package/dist/structure/date-range.js +4 -4
  329. package/dist/structure/date-range.js.map +1 -1
  330. package/dist/structure/definition-use.d.ts.map +1 -1
  331. package/dist/structure/definition-use.js +20 -1
  332. package/dist/structure/definition-use.js.map +1 -1
  333. package/dist/structure/document-context.d.ts +2 -0
  334. package/dist/structure/document-context.d.ts.map +1 -0
  335. package/dist/structure/document-context.js +42 -0
  336. package/dist/structure/document-context.js.map +1 -0
  337. package/dist/structure/fenced-lines.d.ts +7 -0
  338. package/dist/structure/fenced-lines.d.ts.map +1 -0
  339. package/dist/structure/fenced-lines.js +27 -0
  340. package/dist/structure/fenced-lines.js.map +1 -0
  341. package/dist/structure/formula-amount.d.ts +38 -0
  342. package/dist/structure/formula-amount.d.ts.map +1 -0
  343. package/dist/structure/formula-amount.js +187 -0
  344. package/dist/structure/formula-amount.js.map +1 -0
  345. package/dist/structure/installment-fee.d.ts +52 -0
  346. package/dist/structure/installment-fee.d.ts.map +1 -0
  347. package/dist/structure/installment-fee.js +110 -0
  348. package/dist/structure/installment-fee.js.map +1 -0
  349. package/dist/structure/installment-total.d.ts +7 -0
  350. package/dist/structure/installment-total.d.ts.map +1 -1
  351. package/dist/structure/installment-total.js +6 -6
  352. package/dist/structure/installment-total.js.map +1 -1
  353. package/dist/structure/labelled-date-order.d.ts +31 -9
  354. package/dist/structure/labelled-date-order.d.ts.map +1 -1
  355. package/dist/structure/labelled-date-order.js +60 -15
  356. package/dist/structure/labelled-date-order.js.map +1 -1
  357. package/dist/structure/labelled-order.d.ts +7 -1
  358. package/dist/structure/labelled-order.d.ts.map +1 -1
  359. package/dist/structure/labelled-order.js +10 -3
  360. package/dist/structure/labelled-order.js.map +1 -1
  361. package/dist/structure/line-amount-label.d.ts.map +1 -1
  362. package/dist/structure/line-amount-label.js +2 -1
  363. package/dist/structure/line-amount-label.js.map +1 -1
  364. package/dist/structure/line-amount.d.ts +0 -5
  365. package/dist/structure/line-amount.d.ts.map +1 -1
  366. package/dist/structure/line-amount.js +1 -10
  367. package/dist/structure/line-amount.js.map +1 -1
  368. package/dist/structure/prose-total.d.ts +8 -0
  369. package/dist/structure/prose-total.d.ts.map +1 -1
  370. package/dist/structure/prose-total.js +70 -2
  371. package/dist/structure/prose-total.js.map +1 -1
  372. package/dist/structure/runs.d.ts.map +1 -1
  373. package/dist/structure/runs.js +9 -1
  374. package/dist/structure/runs.js.map +1 -1
  375. package/dist/structure/signed-date-line.d.ts +15 -0
  376. package/dist/structure/signed-date-line.d.ts.map +1 -0
  377. package/dist/structure/signed-date-line.js +68 -0
  378. package/dist/structure/signed-date-line.js.map +1 -0
  379. package/dist/structure/start-end-range.d.ts +30 -0
  380. package/dist/structure/start-end-range.d.ts.map +1 -0
  381. package/dist/structure/start-end-range.js +79 -0
  382. package/dist/structure/start-end-range.js.map +1 -0
  383. package/dist/structure/tax.d.ts.map +1 -1
  384. package/dist/structure/tax.js +5 -3
  385. package/dist/structure/tax.js.map +1 -1
  386. package/dist/structure/total-line.d.ts.map +1 -1
  387. package/dist/structure/total-line.js +10 -6
  388. package/dist/structure/total-line.js.map +1 -1
  389. package/dist/structure/total.d.ts +2 -0
  390. package/dist/structure/total.d.ts.map +1 -1
  391. package/dist/structure/total.js +34 -3
  392. package/dist/structure/total.js.map +1 -1
  393. package/dist/subject-codes.d.ts +21 -0
  394. package/dist/subject-codes.d.ts.map +1 -0
  395. package/dist/subject-codes.js +79 -0
  396. package/dist/subject-codes.js.map +1 -0
  397. package/dist/unlabelled-references.d.ts +6 -2
  398. package/dist/unlabelled-references.d.ts.map +1 -1
  399. package/dist/unlabelled-references.js +11 -10
  400. package/dist/unlabelled-references.js.map +1 -1
  401. package/genres.yaml +17 -0
  402. package/package.json +4 -4
  403. package/profiles/listing.yaml +1 -1
  404. package/profiles/property-sale.yaml +21 -0
  405. package/rules/closing-balance-mismatch.yaml +3 -3
  406. package/rules/date-range-reversed.yaml +4 -4
  407. package/rules/due-before-issue.yaml +9 -5
  408. package/rules/fact-conflict.yaml +2 -2
  409. package/rules/formula-amount-mismatch.yaml +67 -0
  410. package/rules/heading-echo.yaml +2 -2
  411. package/rules/installment-total-mismatch.yaml +9 -4
  412. package/rules/internal-jargon.yaml +2 -2
  413. package/rules/latin-spacing.yaml +4 -4
  414. package/rules/list-item-form-mix.yaml +1 -1
  415. package/rules/name-variant.yaml +5 -2
  416. package/rules/no-lead.yaml +2 -2
  417. package/rules/number-without-unit.yaml +2 -2
  418. package/rules/orthographic-variant.yaml +3 -3
  419. package/rules/reference-number-variant.yaml +3 -3
  420. package/rules/toki-as-condition.yaml +71 -0
  421. package/rules/total-mismatch.yaml +4 -4
  422. package/rules/unknown-word.yaml +2 -2
  423. package/rules/use-before-definition.yaml +2 -2
  424. package/src/api.ts +11 -2
  425. package/src/cli-text.ts +6 -4
  426. package/src/commands/grade.ts +75 -23
  427. package/src/company-kana-spelling.ts +22 -2
  428. package/src/company-name-ending.ts +50 -0
  429. package/src/company-names.ts +52 -17
  430. package/src/cross-run.ts +1 -0
  431. package/src/custom/no-rewrite.ts +2 -0
  432. package/src/custom/parse.ts +18 -2
  433. package/src/detectors/age-words.ts +12 -0
  434. package/src/detectors/agreement-slip.ts +3 -0
  435. package/src/detectors/annual-pay-mismatch.ts +3 -18
  436. package/src/detectors/blank-structure.ts +4 -3
  437. package/src/detectors/closing-balance-mismatch.ts +1 -0
  438. package/src/detectors/code-fences.ts +10 -0
  439. package/src/detectors/condition-toki.ts +60 -0
  440. package/src/detectors/conjugated-entry.ts +53 -0
  441. package/src/detectors/content-morphemes.ts +2 -1
  442. package/src/detectors/cross-facts.ts +2 -1
  443. package/src/detectors/custom.ts +9 -2
  444. package/src/detectors/document-shape.ts +8 -0
  445. package/src/detectors/due-date.ts +17 -7
  446. package/src/detectors/fact-consistency.ts +13 -17
  447. package/src/detectors/form-line.ts +47 -0
  448. package/src/detectors/formula-amount.ts +43 -0
  449. package/src/detectors/heading-claim.ts +76 -0
  450. package/src/detectors/heading-echo.ts +32 -1
  451. package/src/detectors/installment-total-mismatch.ts +35 -24
  452. package/src/detectors/list-parallel.ts +46 -7
  453. package/src/detectors/name-variant.ts +45 -8
  454. package/src/detectors/number-unit.ts +35 -9
  455. package/src/detectors/orthographic-variant.ts +5 -3
  456. package/src/detectors/orthography.ts +17 -8
  457. package/src/detectors/per-unit-words.ts +24 -0
  458. package/src/detectors/period-multiple-mismatch.ts +2 -18
  459. package/src/detectors/reference-number-variant.ts +27 -5
  460. package/src/detectors/registry/condition-toki.ts +4 -0
  461. package/src/detectors/registry/formula-amount.ts +4 -0
  462. package/src/detectors/rent-multiple-mismatch.ts +2 -18
  463. package/src/detectors/row-sentence-amounts.ts +5 -20
  464. package/src/detectors/stated-amounts.ts +20 -0
  465. package/src/detectors/step-time-sum.ts +2 -1
  466. package/src/detectors/structure-tree.ts +7 -2
  467. package/src/detectors/team.ts +13 -7
  468. package/src/detectors/time-order.ts +6 -3
  469. package/src/detectors/unknown-word.ts +20 -2
  470. package/src/detectors/use-interval.ts +8 -4
  471. package/src/detectors/use-maximum.ts +3 -1
  472. package/src/eval.ts +3 -3
  473. package/src/extension/document-view.ts +18 -9
  474. package/src/extension/entry-genres.ts +15 -0
  475. package/src/extension/module-detector.ts +9 -4
  476. package/src/extension/plugin-parse.ts +29 -7
  477. package/src/extension/problem-text.ts +4 -0
  478. package/src/facts/age-values.ts +24 -2
  479. package/src/facts/column-units.ts +67 -0
  480. package/src/facts/fact-values.ts +11 -1
  481. package/src/facts/labelled-facts.ts +16 -2
  482. package/src/facts/per-unit.ts +105 -0
  483. package/src/facts/row-sentence-amounts.ts +4 -26
  484. package/src/facts/table-facts.ts +7 -4
  485. package/src/facts/term-values.ts +94 -13
  486. package/src/fix-plan/text.ts +2 -2
  487. package/src/grade/baseline.ts +20 -12
  488. package/src/grade/expect-read.ts +65 -0
  489. package/src/grade/expect.ts +74 -0
  490. package/src/grade/grade-item.ts +14 -3
  491. package/src/grade/item.ts +48 -8
  492. package/src/grade/result.ts +5 -0
  493. package/src/grade/results-read.ts +7 -2
  494. package/src/grade/rubric-file.ts +46 -0
  495. package/src/grade/rubric.ts +77 -47
  496. package/src/grade/setup.ts +3 -3
  497. package/src/grade/stamp.ts +10 -2
  498. package/src/grade/task-rubric.ts +39 -0
  499. package/src/grade/text.ts +27 -5
  500. package/src/lexicon-genres.ts +11 -0
  501. package/src/name-char-reading.ts +16 -0
  502. package/src/name-end-letter.ts +52 -0
  503. package/src/name-neighbours.ts +2 -1
  504. package/src/name-separator.ts +32 -0
  505. package/src/name-variants.ts +54 -13
  506. package/src/number-name.ts +12 -3
  507. package/src/plugin.ts +6 -0
  508. package/src/profile/cue-lines.ts +14 -0
  509. package/src/profile/parse.ts +11 -2
  510. package/src/reference-numbers.ts +1 -1
  511. package/src/run.ts +1 -0
  512. package/src/spaced-person-name.ts +27 -2
  513. package/src/spacing-code.ts +13 -2
  514. package/src/spacing-minority.ts +17 -0
  515. package/src/structure/closing-balance.ts +46 -8
  516. package/src/structure/date-range.ts +4 -3
  517. package/src/structure/definition-use.ts +24 -1
  518. package/src/structure/document-context.ts +46 -0
  519. package/src/structure/fenced-lines.ts +28 -0
  520. package/src/structure/formula-amount.ts +236 -0
  521. package/src/structure/installment-fee.ts +201 -0
  522. package/src/structure/installment-total.ts +6 -6
  523. package/src/structure/labelled-date-order.ts +105 -21
  524. package/src/structure/labelled-order.ts +21 -10
  525. package/src/structure/line-amount-label.ts +2 -1
  526. package/src/structure/line-amount.ts +1 -11
  527. package/src/structure/prose-total.ts +81 -2
  528. package/src/structure/runs.ts +10 -1
  529. package/src/structure/signed-date-line.ts +78 -0
  530. package/src/structure/start-end-range.ts +120 -0
  531. package/src/structure/tax.ts +6 -3
  532. package/src/structure/total-line.ts +12 -6
  533. package/src/structure/total.ts +42 -3
  534. package/src/subject-codes.ts +105 -0
  535. package/src/unlabelled-references.ts +24 -16
@@ -0,0 +1,120 @@
1
+ import { escapeRegExp } from "../orthography.ts";
2
+ import type { Lexicon } from "../plugin.ts";
3
+ import { bare, withoutTrailingWeekday, type DatedSpan } from "./date-range.ts";
4
+
5
+ /**
6
+ * 始まりの語と終わりの語で書いた期間(The term starts on 1 April 2026 and ends on 31 March 2027、2026年4月1日から開始し、2027年3月31日に終了する)。
7
+ * 一つ目の日付に始まりの語が、二つ目の日付に終わりの語が付き、二つの日付の間は付いた語とつなぎの語だけ。
8
+ * 文に始まりの語か終わりの語が二つあれば、どの日付がどの期間のものか決まらないので読まない。語は語彙表(range-start-end)から受け取る。
9
+ */
10
+ export type StartEndWord = { readonly word: string; readonly position: "before" | "after" };
11
+
12
+ export type StartEndWords = {
13
+ readonly starts: readonly StartEndWord[];
14
+ readonly ends: readonly StartEndWord[];
15
+ /** 始まりの語と終わりの語の間に立つ語(and、and shall、し、)。 */
16
+ readonly joints: readonly string[];
17
+ /**
18
+ * 終わりの日付の後ろに付く終わりの語(に終了)の、さらに後ろに来てよい語(する、し、)。「、」で終わる語は文が続くので頭だけを照らし、
19
+ * ほかは文の終わりまでがその語だけ。「に終了した旧契約」は名詞にかかる語で、期間の終わりではない。
20
+ */
21
+ readonly tails: readonly string[];
22
+ readonly weekdays: readonly string[];
23
+ };
24
+
25
+ /** 文の終わり。英語は後ろが大文字のときだけ("in Jan. 2026" は切らない)。段落の切れ目も文の終わり。 */
26
+ const SENTENCE_END = /[。!?]|[.!?]\s+(?=[A-Z])|\n\s*\n/gu;
27
+
28
+ const flat = (text: string): string => text.replace(/\s+/gu, " ").toLowerCase();
29
+
30
+ const LATIN = /[a-z]/u;
31
+
32
+ /** 英字の語は語の切れ目で照らす(ends が depends に当たらない)。英字で始まらない・終わらない語は、その側を照らさない。 */
33
+ const wordPattern = (word: string, flags: string): RegExp => {
34
+ const head = LATIN.test(word.charAt(0)) ? "(?<![a-z])" : "";
35
+ const tail = LATIN.test(word.at(-1) ?? "") ? "(?![a-z])" : "";
36
+ return new RegExp(`${head}${escapeRegExp(word)}${tail}`, flags);
37
+ };
38
+
39
+ const endsWithWord = (text: string, word: string): boolean => new RegExp(`${wordPattern(word, "u").source}$`, "u").test(text);
40
+
41
+ const startsWithWord = (text: string, word: string): boolean => new RegExp(`^${wordPattern(word, "u").source}`, "u").test(text);
42
+
43
+ /** 文の中で、語のどれかに当たる所の数。重なる当たり(starts と starts on)は一つ。 */
44
+ export const occurrences = (text: string, words: readonly StartEndWord[]): number => {
45
+ const spans = words
46
+ .flatMap(({ word }) => [...text.matchAll(wordPattern(word, "gu"))].map((match) => ({ start: match.index, end: match.index + match[0].length })))
47
+ .toSorted((left, right) => left.start - right.start || right.end - left.end);
48
+ return spans.reduce(
49
+ (acc, span) => (span.start >= acc.end ? { count: acc.count + 1, end: span.end } : { count: acc.count, end: Math.max(acc.end, span.end) }),
50
+ {
51
+ count: 0,
52
+ end: -1,
53
+ },
54
+ ).count;
55
+ };
56
+
57
+ type Sentence = { readonly before: string; readonly between: string; readonly after: string; readonly whole: string };
58
+
59
+ /** 二つの日付を含む文を、始まりの日付の前、二つの日付の間、終わりの日付の後ろに分ける。間に文の終わりがあれば undefined。 */
60
+ const sentenceOf = (source: string, start: DatedSpan, end: DatedSpan): Sentence | undefined => {
61
+ const between = source.slice(start.end, end.offset);
62
+ if (new RegExp(SENTENCE_END.source, "u").test(between)) return undefined;
63
+ const from = [...source.slice(0, start.offset).matchAll(SENTENCE_END)].map((match) => match.index + match[0].length).at(-1) ?? 0;
64
+ const tail = source.slice(end.end);
65
+ const to = end.end + ([...tail.matchAll(SENTENCE_END)][0]?.index ?? tail.length);
66
+ const before = flat(source.slice(from, start.offset)).trimEnd();
67
+ const after = bare(source.slice(end.end, to));
68
+ return { before, between: bare(between), after, whole: flat(source.slice(from, to)) };
69
+ };
70
+
71
+ const beforeWord = (text: string, words: readonly StartEndWord[]): string | undefined =>
72
+ words.find((entry) => entry.position === "before" && endsWithWord(text, entry.word))?.word;
73
+
74
+ const afterWord = (text: string, words: readonly StartEndWord[]): string | undefined =>
75
+ words.find((entry) => entry.position === "after" && startsWithWord(text, entry.word))?.word;
76
+
77
+ const endsPredicate = (rest: string, tails: readonly string[]): boolean => tails.some((tail) => (tail.endsWith("、") ? rest.startsWith(tail) : rest === tail));
78
+
79
+ /** 終わりの日付の後ろの終わりの語で、その後ろが述語の終わり(に終了する。に終了し、)。 */
80
+ const endAfterWord = (after: string, words: StartEndWords): string | undefined => {
81
+ const word = afterWord(after, words.ends);
82
+ return word !== undefined && endsPredicate(after.slice(word.length).trim(), words.tails) ? word : undefined;
83
+ };
84
+
85
+ /** 間の語から、始まりの日付の後ろの語と終わりの日付の前の語を除いた残りが、つなぎの語か。 */
86
+ const joinedBetween = (between: string, words: StartEndWords, startAfter: string | undefined, endBefore: string | undefined): boolean => {
87
+ const head = startAfter === undefined ? between : between.slice(startAfter.length).trimStart();
88
+ const rest = endBefore === undefined ? head : head.slice(0, head.length - endBefore.length).trimEnd();
89
+ return words.joints.some((joint) => joint.toLowerCase() === rest);
90
+ };
91
+
92
+ /** 二つの日付が、始まりの語と終わりの語で書いた一つの期間か。 */
93
+ export const isStartEndRange = (source: string, start: DatedSpan, end: DatedSpan, words: StartEndWords): boolean => {
94
+ const sentence = sentenceOf(source, start, end);
95
+ if (sentence === undefined) return false;
96
+ if (occurrences(sentence.whole, words.starts) !== 1 || occurrences(sentence.whole, words.ends) !== 1) return false;
97
+ const between = withoutTrailingWeekday(sentence.between, words.weekdays);
98
+ const startBefore = beforeWord(withoutTrailingWeekday(sentence.before, words.weekdays), words.starts);
99
+ const startAfter = afterWord(between, words.starts);
100
+ const endBefore = beforeWord(between, words.ends);
101
+ const endAfter = endAfterWord(sentence.after, words);
102
+ if ((startBefore ?? startAfter) === undefined || (endBefore ?? endAfter) === undefined) return false;
103
+ return joinedBetween(between, words, startAfter, endBefore);
104
+ };
105
+
106
+ const wordsIn = (entries: Lexicon, group: "start" | "end"): StartEndWord[] =>
107
+ entries
108
+ .filter((entry) => entry.group === group)
109
+ .map((entry) => ({ word: entry.pattern.toLowerCase(), position: entry.position === "after" ? "after" : "before" }));
110
+
111
+ const patternsIn = (entries: Lexicon, group: string): string[] => entries.filter((entry) => entry.group === group).map((entry) => entry.pattern.toLowerCase());
112
+
113
+ /** 語彙表 range-start-end の行を、group(start、end、joint、tail)で分ける。 */
114
+ export const startEndWordsOf = (entries: Lexicon, weekdays: readonly string[]): StartEndWords => ({
115
+ starts: wordsIn(entries, "start"),
116
+ ends: wordsIn(entries, "end"),
117
+ joints: patternsIn(entries, "joint"),
118
+ tails: patternsIn(entries, "tail"),
119
+ weekdays,
120
+ });
@@ -1,6 +1,6 @@
1
1
  import type { StructureIssue } from "./issues.ts";
2
2
  import { runsOf, type Line } from "./runs.ts";
3
- import { columnOf, isTotalLabel, shownTotal, type Amount } from "./total.ts";
3
+ import { columnOf, isTotalLabel, shownTotal, withoutLeadingBlankCells, type Amount } from "./total.ts";
4
4
 
5
5
  /**
6
6
  * 税の行と、その上の金額に率を掛けた額。表か箇条書きの並びで、税の語(消費税・Sales tax)で始まり率(10%)を書いた行の
@@ -42,8 +42,11 @@ const rateOf = (text: string): number | undefined => {
42
42
  return Number.isFinite(rate) && rate > 0 && rate < MAX_RATE ? rate : undefined;
43
43
  };
44
44
 
45
+ /** A tax or total row may leave its first cells blank and put its word in the first cell that is not (| | 小計 | 12,000円 |). */
46
+ const startsWithLabel = (row: Row, labels: readonly string[]): boolean => isTotalLabel(withoutLeadingBlankCells(row.text), labels);
47
+
45
48
  const isTaxRow = (row: Row, words: TaxWords): boolean =>
46
- isTotalLabel(row.text, words.labels) && !words.included.some((word) => row.text.toLowerCase().includes(word.toLowerCase()));
49
+ startsWithLabel(row, words.labels) && !words.included.some((word) => row.text.toLowerCase().includes(word.toLowerCase()));
47
50
 
48
51
  const isChargeRow = (row: Row, words: TaxWords): boolean => isTotalLabel(row.text, words.charges) && rateOf(row.text) !== undefined;
49
52
 
@@ -89,7 +92,7 @@ const basesOf = (source: string, above: readonly Row[], tax: Amount, column: num
89
92
  const charges = valuesAt(source, above.slice(chargeStart), column, tax.unit);
90
93
  if (charges === undefined) return [];
91
94
  const before = above.slice(0, chargeStart);
92
- const lastTotalIndex = before.findLastIndex((row) => isTotalLabel(row.text, words.totals));
95
+ const lastTotalIndex = before.findLastIndex((row) => startsWithLabel(row, words.totals));
93
96
  const lastTotal = before[lastTotalIndex];
94
97
  if (lastTotal !== undefined) {
95
98
  const total = amountAt(source, lastTotal, column, tax.unit);
@@ -1,4 +1,4 @@
1
- import { isTotalLabel, leadingText } from "./total.ts";
1
+ import { isTotalLabel, leadingText, withoutLeadingBlankCells } from "./total.ts";
2
2
  import { CELL_SEPARATOR } from "./bare-numbers.ts";
3
3
  import { outsideBrackets } from "./connection-times.ts";
4
4
 
@@ -45,7 +45,13 @@ const labelText = (lead: string): string => {
45
45
  };
46
46
 
47
47
  /** 行頭の見出しを語に分ける。括弧の注記((incl. tax))と強調の印は除く。 */
48
- const labelWords = (text: string): string[] => wordsOf(outsideBrackets(labelText(leadingText(text))).replace(EMPHASIS, ""));
48
+ const leadingLabelWords = (text: string): string[] => wordsOf(outsideBrackets(labelText(leadingText(text))).replace(EMPHASIS, ""));
49
+
50
+ /** 行の見出しの語。表の行は、前の升がどれも空なら、最初の空でない升の見出しを読む。 */
51
+ const labelWords = (text: string): string[] => leadingLabelWords(withoutLeadingBlankCells(text));
52
+
53
+ /** 見出しの語で始まる行か。表の行は、前の升がどれも空なら、最初の空でない升に書いた見出しも読む(| | 合計 | 12,000円 |)。 */
54
+ const startsWithLabel = (text: string, labels: readonly string[]): boolean => isTotalLabel(withoutLeadingBlankCells(text), labels);
49
55
 
50
56
  const startsAt = (words: readonly string[], label: readonly string[], index: number): boolean => label.every((word, offset) => words[index + offset] === word);
51
57
 
@@ -153,8 +159,8 @@ const isUnsummed = (nouns: readonly string[], unsummedNouns: readonly string[]):
153
159
  */
154
160
  const isNamedTotal = (text: string, words: TotalLineWords, header: string): boolean => {
155
161
  const label = labelWords(text).join(" ");
156
- const firstColumn = labelWords(header);
157
- const columns = summedColumnHeaders(text, header).map(labelWords);
162
+ const firstColumn = leadingLabelWords(header);
163
+ const columns = summedColumnHeaders(text, header).map(leadingLabelWords);
158
164
  const namesTotal = (nouns: readonly string[]): boolean =>
159
165
  !isUnsummed(nouns, words.unsummedNouns ?? []) &&
160
166
  (namesEachOther(nouns, firstColumn) || (columns.length > 0 && columns.every((headerWords) => namesEachOther(nouns, headerWords))));
@@ -163,7 +169,7 @@ const isNamedTotal = (text: string, words: TotalLineWords, header: string): bool
163
169
 
164
170
  /** 合計の語だけか、合計の語に添える語だけを前後に置いた見出しか。 */
165
171
  const isPlainTotal = (text: string, words: TotalLineWords): boolean => {
166
- if (isTotalLabel(text, words.labels)) return true;
172
+ if (startsWithLabel(text, words.labels)) return true;
167
173
  if (words.qualifiers.length === 0) return false;
168
174
  const label = labelWords(text);
169
175
  return words.labels.some((total) => isQualifiedLabel(label, wordsOf(total), words.qualifiers));
@@ -189,7 +195,7 @@ export const withoutPeriod = (label: string, qualifier: TotalQualifier): string
189
195
  * 前の年の合計(前年合計、Prior-year total)は、期間の語が頭にも尻にも無いので読まない。
190
196
  */
191
197
  export const isPeriodTotalLine = (text: string, words: TotalLineWords): boolean => {
192
- if (isTotalLabel(text, words.periodLabels ?? [])) return true;
198
+ if (startsWithLabel(text, words.periodLabels ?? [])) return true;
193
199
  const label = labelWords(text).join(" ");
194
200
  return (words.periodQualifiers ?? []).some((qualifier) => {
195
201
  const rest = withoutPeriod(label, qualifier);
@@ -53,15 +53,54 @@ export const leadingText = (text: string): string =>
53
53
  .replace(/^[*_]+/u, "")
54
54
  .trimStart();
55
55
 
56
+ /** 表の行の頭に並ぶ空の升(| | 合計 |)。最初の区切りは残す。 */
57
+ const LEADING_BLANK_CELLS = /^([ \t]*\|)(?:[ \t]*\|)+/u;
58
+
59
+ /** 表の行から頭の空の升を除いたもの。合計の語を最初の空でない升に書いた行を、最初の升に書いた行と同じに読むため。 */
60
+ export const withoutLeadingBlankCells = (row: string): string => row.replace(LEADING_BLANK_CELLS, "$1");
61
+
56
62
  /**
57
- * 合計の語のすぐ後ろ。行や列の終わり、区切り(: |)、括弧の注記(合計(税込))、金額。
63
+ * 合計の語のすぐ後ろ。行や列の終わり、区切り(: |)、金額。括弧の注記は endsLabel が飛ばす。
58
64
  * 「計画」や「Total conversion: 25%」のように語が続くものは合計の行ではない。
59
65
  */
60
- const AFTER_LABEL = /^[*_]*[ \t\u3000]*(?:$|[::|((]|[-−▲△$€£¥¥\p{N}])/u;
66
+ const AFTER_LABEL = /^[*_]*[ \t\u3000]*(?:$|[::|]|[-−▲△$€£¥¥\p{N}])/u;
67
+
68
+ /** 見出しのすぐ後ろの括弧の注記(合計(税込)、Total (incl. tax))の開き。 */
69
+ const NOTE_OPEN = /^[*_]*[ \t\u3000]*[((]/u;
70
+ const OPENERS = new Set(["(", "("]);
71
+ const CLOSERS = new Set([")", ")"]);
72
+
73
+ type NoteScan = { readonly depth: number; readonly end: number | undefined; readonly stopped: boolean };
74
+
75
+ /** 開きの括弧(start)に対応する閉じの次の位置。中の括弧は何段でもよい。升の区切り(\| でない |)までに閉じなければ undefined。 */
76
+ const noteEnd = (text: string, start: number): number | undefined =>
77
+ text
78
+ .slice(start)
79
+ .split("")
80
+ .reduce<NoteScan>(
81
+ (scan, char, offset) => {
82
+ if (scan.end !== undefined || scan.stopped) return scan;
83
+ if (char === "|" && text[start + offset - 1] !== "\\") return { ...scan, stopped: true };
84
+ const depth = scan.depth + (OPENERS.has(char) ? 1 : 0) - (CLOSERS.has(char) ? 1 : 0);
85
+ return { depth, end: depth === 0 ? start + offset + 1 : undefined, stopped: false };
86
+ },
87
+ { depth: 0, end: undefined, stopped: false },
88
+ ).end;
89
+
90
+ /**
91
+ * 見出しの語の後ろが見出しの終わりか。括弧の注記は飛ばして、その後ろを同じに読む。注記の後ろに語が続けば(Tax (10%) workshop、
92
+ * 合計(第1期)設計)、注記の付いた項目の名前。升の中で閉じない括弧は、そこからを注記とみなす。
93
+ */
94
+ const endsLabel = (rest: string): boolean => {
95
+ const open = NOTE_OPEN.exec(rest);
96
+ if (open === null) return AFTER_LABEL.test(rest);
97
+ const end = noteEnd(rest, open[0].length - 1);
98
+ return end === undefined || endsLabel(rest.slice(end));
99
+ };
61
100
 
62
101
  export const isTotalLabel = (text: string, labels: readonly string[]): boolean => {
63
102
  const lead = leadingText(text).toLowerCase();
64
- return labels.some((label) => lead.startsWith(label.toLowerCase()) && AFTER_LABEL.test(lead.slice(label.length)));
103
+ return labels.some((label) => lead.startsWith(label.toLowerCase()) && endsLabel(lead.slice(label.length)));
65
104
  };
66
105
 
67
106
  const centsOf = (source: string, line: Line, amount: Amount): number | undefined => {
@@ -0,0 +1,105 @@
1
+ // The one code a product document is about (a recall's or a warranty's model): named in its title, or labelled on a line
2
+ // of its own or in a table (型番:NH-805, | Model | MW-4120S |), and written once elsewhere with two neighbours swapped
3
+ // (NH-850). Only when the document names no other code in those places: a catalog naming two is about two items. Pure.
4
+ import type { LexiconEntry, Span } from "./plugin.ts";
5
+ import { labelBefore, swapsNeighbours, withoutUrls } from "./phone-numbers.ts";
6
+ import { charactersOf, LABEL_TO_NUMBER, REFERENCE_SHAPE, type ReferenceNumber, type ReferenceVariant } from "./reference-numbers.ts";
7
+ import { isCodeWith } from "./unlabelled-references.ts";
8
+
9
+ /** Digits enough for a model's code (FT-38); the two letters a short code must hold keep out form names (A-12, B-21). */
10
+ export const MIN_SUBJECT_DIGITS = 2;
11
+ const MIN_SUBJECT_LETTERS = 2;
12
+ const LETTER = /[A-Z]/gu;
13
+ const FIRST_PART = /^[^-‐−-]*/u;
14
+ const TITLE_GROUP = "title";
15
+
16
+ /** A short code: two letters or more before its hyphen, and two digits or more (FT-38, KR-M230; not A-12B). */
17
+ export const isSubjectCode = (written: string): boolean =>
18
+ isCodeWith(MIN_SUBJECT_DIGITS)(written) && (charactersOf(FIRST_PART.exec(written)?.[0] ?? "").match(LETTER)?.length ?? 0) >= MIN_SUBJECT_LETTERS;
19
+
20
+ /** What the document gives: its prose (tables masked), its title as written (the prose masks it), its labels, the groups that name an item, the codes its tables label, and every code in a table. */
21
+ export type SubjectCodeInput = {
22
+ readonly prose: string;
23
+ readonly title: { readonly start: number; readonly text: string } | undefined;
24
+ readonly labels: readonly LexiconEntry[];
25
+ readonly listed: ReadonlySet<string>;
26
+ readonly tabled: readonly ReferenceNumber[];
27
+ readonly known: ReadonlySet<string>;
28
+ };
29
+
30
+ type CodeWriting = ReferenceNumber & { readonly anchor: boolean; readonly group: string | undefined };
31
+
32
+ const lineAround = (text: string, offset: number): Span => {
33
+ const start = text.lastIndexOf("\n", offset - 1) + 1;
34
+ const end = text.indexOf("\n", offset);
35
+ return { start, end: end === -1 ? text.length : end };
36
+ };
37
+
38
+ /** Whether a code's line holds a label of the given labels, the separators and the code, and nothing else (型番:NH-805). */
39
+ const isFieldLine = (text: string, offset: number, written: string, between: string, labels: readonly LexiconEntry[]): boolean => {
40
+ const line = lineAround(text, offset);
41
+ const head = text
42
+ .slice(line.start, offset - between.length)
43
+ .trim()
44
+ .toLowerCase();
45
+ const rest = text.slice(offset + written.length, line.end).trim();
46
+ return rest === "" && labels.some((label) => label.pattern.toLowerCase() === head);
47
+ };
48
+
49
+ const shortCodes = (text: string): RegExpExecArray[] => [...text.matchAll(REFERENCE_SHAPE)].filter((match) => isSubjectCode(match[0]));
50
+
51
+ /** Every short code of the title, each an anchor. */
52
+ const titleWritings = (title: SubjectCodeInput["title"]): CodeWriting[] =>
53
+ title === undefined
54
+ ? []
55
+ : shortCodes(title.text).map((match) => ({
56
+ offset: title.start + match.index,
57
+ written: match[0],
58
+ characters: charactersOf(match[0]),
59
+ label: TITLE_GROUP,
60
+ anchor: true,
61
+ group: undefined,
62
+ }));
63
+
64
+ /** Every short code of the prose, as an anchor when a field line of a listed kind labels it. */
65
+ const proseWritings = (text: string, input: SubjectCodeInput): CodeWriting[] =>
66
+ shortCodes(text).map((match) => {
67
+ const written = match[0];
68
+ const offset = match.index;
69
+ const label = labelBefore(text, offset, input.labels);
70
+ const group = label !== undefined && LABEL_TO_NUMBER.test(label.between) ? label.group : undefined;
71
+ const onField = group !== undefined && input.listed.has(group) && isFieldLine(text, offset, written, label?.between ?? "", input.labels);
72
+ return { offset, written, characters: charactersOf(written), label: group ?? TITLE_GROUP, anchor: onField, group };
73
+ });
74
+
75
+ /** The title's codes and the prose's, one writing per place. */
76
+ const writingsOf = (text: string, input: SubjectCodeInput): CodeWriting[] => {
77
+ const inTitle = titleWritings(input.title);
78
+ return [...inTitle, ...proseWritings(text, input).filter((writing) => !inTitle.some((title) => title.offset === writing.offset))];
79
+ };
80
+
81
+ /** The writings of the document's one code with two neighbours swapped, each written once, of a listed kind or unlabelled, on a line that does not name the code itself. */
82
+ export const subjectCodeVariants = (input: SubjectCodeInput): ReferenceVariant[] => {
83
+ const text = withoutUrls(input.prose);
84
+ const writings = writingsOf(text, input);
85
+ const anchors = [
86
+ ...writings.filter((writing) => writing.anchor),
87
+ ...input.tabled.filter((code) => input.listed.has(code.label) && isSubjectCode(code.written)),
88
+ ];
89
+ const subject = anchors[0];
90
+ if (subject === undefined || anchors.some((anchor) => anchor.characters !== subject.characters)) return [];
91
+ const timesWritten = (characters: string): number => writings.filter((writing) => writing.characters === characters).length;
92
+ return writings.flatMap((writing) => {
93
+ const line = lineAround(text, writing.offset);
94
+ const slip =
95
+ !writing.anchor &&
96
+ (writing.group === undefined || input.listed.has(writing.group)) &&
97
+ !input.known.has(writing.characters) &&
98
+ timesWritten(writing.characters) === 1 &&
99
+ swapsNeighbours(subject.characters, writing.characters) &&
100
+ !writings.some((other) => other.characters === subject.characters && other.offset >= line.start && other.offset < line.end);
101
+ if (!slip) return [];
102
+ const { offset, written, characters } = writing;
103
+ return [{ number: { offset, written, characters, label: subject.label }, other: subject }];
104
+ });
105
+ };
@@ -8,16 +8,22 @@ import { withoutEdgeMarks } from "./facts/trim-marks.ts";
8
8
  import { charactersOf, MAX_CHARACTERS, MIN_DIGITS, REFERENCE_SHAPE, type ReferenceNumber, type ReferenceVariant } from "./reference-numbers.ts";
9
9
 
10
10
  const SEPARATOR = /[-‐−-]/u;
11
+
12
+ type CodeShape = (written: string) => boolean;
11
13
  const LATIN_LETTER = /[A-Z]/u;
12
14
  const DIGIT = /\d/gu;
13
15
 
14
- /** A code: letters, a hyphen and four digits or more (GP-0512). A plain number (48215, 2026-118) is never one. */
15
- const isCode = (written: string): boolean => {
16
- const characters = charactersOf(written);
17
- return (
18
- SEPARATOR.test(written) && LATIN_LETTER.test(characters) && (characters.match(DIGIT)?.length ?? 0) >= MIN_DIGITS && characters.length <= MAX_CHARACTERS
19
- );
20
- };
16
+ /** A code: letters, a hyphen and the given digits or more (GP-0512). A plain number (48215, 2026-118) is never one. */
17
+ export const isCodeWith =
18
+ (minDigits: number) =>
19
+ (written: string): boolean => {
20
+ const characters = charactersOf(written);
21
+ return (
22
+ SEPARATOR.test(written) && LATIN_LETTER.test(characters) && (characters.match(DIGIT)?.length ?? 0) >= minDigits && characters.length <= MAX_CHARACTERS
23
+ );
24
+ };
25
+
26
+ const isCode = isCodeWith(MIN_DIGITS);
21
27
 
22
28
  const keyOf = (text: string): string => withoutEdgeMarks(text).normalize("NFKC").toLowerCase();
23
29
 
@@ -29,10 +35,10 @@ const labelOfCell = (cell: Cell | undefined, labels: readonly LexiconEntry[]): s
29
35
  };
30
36
 
31
37
  /** A cell holding one code and nothing else, as a reference number of the group. */
32
- const codeOfCell = (cell: Cell | undefined, group: string): ReferenceNumber[] => {
38
+ const codeOfCell = (cell: Cell | undefined, group: string, isCodeShape: CodeShape): ReferenceNumber[] => {
33
39
  if (cell === undefined) return [];
34
40
  const written = withoutEdgeMarks(cell.text);
35
- if (!isCode(written) || [...written.matchAll(REFERENCE_SHAPE)].length !== 1) return [];
41
+ if (!isCodeShape(written) || [...written.matchAll(REFERENCE_SHAPE)].length !== 1) return [];
36
42
  return [{ offset: cell.start + cell.text.indexOf(written), written, characters: charactersOf(written), label: group }];
37
43
  };
38
44
 
@@ -40,28 +46,30 @@ const codeOfCell = (cell: Cell | undefined, group: string): ReferenceNumber[] =>
40
46
  * The codes a table labels: in a row whose first cell is a label (| 品番 | GP-0512 |), and in a column whose heading is
41
47
  * one (| 品番 | 品名 | over | GP-0512 | …). Pass the text with its tables (proseAndTablesOf).
42
48
  */
43
- export const tableLabelledCodes = (text: string, labels: readonly LexiconEntry[]): ReferenceNumber[] =>
49
+ export const tableLabelledCodes = (text: string, labels: readonly LexiconEntry[], isCodeShape: CodeShape = isCode): ReferenceNumber[] =>
44
50
  tablesOf(linesOf(text)).flatMap((table) => {
45
51
  const headings = cellsOf(table.header).map((cell) => labelOfCell(cell, labels));
46
52
  return table.rows.flatMap((row) => {
47
53
  const cells = cellsOf(row);
48
54
  const rowGroup = labelOfCell(cells[0], labels);
49
- const byRow = rowGroup === undefined ? [] : codeOfCell(cells[1], rowGroup);
55
+ const byRow = rowGroup === undefined ? [] : codeOfCell(cells[1], rowGroup, isCodeShape);
50
56
  const byColumn = cells.flatMap((cell, index) => {
51
57
  const group = headings[index];
52
- return group === undefined ? [] : codeOfCell(cell, group);
58
+ return group === undefined ? [] : codeOfCell(cell, group, isCodeShape);
53
59
  });
54
60
  return [...byRow, ...byColumn];
55
61
  });
56
62
  });
57
63
 
58
64
  /** The letters and digits of every code a cell holds. */
59
- const codesInCell = (cell: Cell): string[] =>
60
- [...cell.text.matchAll(REFERENCE_SHAPE)].filter((match) => isCode(match[0])).map((match) => charactersOf(match[0]));
65
+ const codesInCell = (cell: Cell, isCodeShape: CodeShape): string[] =>
66
+ [...cell.text.matchAll(REFERENCE_SHAPE)].filter((match) => isCodeShape(match[0])).map((match) => charactersOf(match[0]));
61
67
 
62
68
  /** Every code written anywhere in a table, its heading row too, labelled or not: a code a table lists is an item of its own, never a slip. */
63
- export const tableCodes = (text: string): Set<string> =>
64
- new Set(tablesOf(linesOf(text)).flatMap((table) => [table.header, ...table.rows].flatMap((row) => cellsOf(row).flatMap(codesInCell))));
69
+ export const tableCodes = (text: string, isCodeShape: CodeShape = isCode): Set<string> =>
70
+ new Set(
71
+ tablesOf(linesOf(text)).flatMap((table) => [table.header, ...table.rows].flatMap((row) => cellsOf(row).flatMap((cell) => codesInCell(cell, isCodeShape)))),
72
+ );
65
73
 
66
74
  /**
67
75
  * Every code of the prose written with no label that is one labelled code with two neighbours swapped. Only when exactly