chaffjs 0.35.0 → 0.36.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 (700) hide show
  1. package/README.ja.md +12 -0
  2. package/README.md +15 -0
  3. package/dist/announced-count.d.ts +2 -0
  4. package/dist/announced-count.d.ts.map +1 -1
  5. package/dist/announced-count.js +29 -7
  6. package/dist/announced-count.js.map +1 -1
  7. package/dist/chronology-rows.d.ts +9 -0
  8. package/dist/chronology-rows.d.ts.map +1 -0
  9. package/dist/chronology-rows.js +79 -0
  10. package/dist/chronology-rows.js.map +1 -0
  11. package/dist/company-name-run.d.ts +8 -0
  12. package/dist/company-name-run.d.ts.map +1 -0
  13. package/dist/company-name-run.js +30 -0
  14. package/dist/company-name-run.js.map +1 -0
  15. package/dist/compound-head.d.ts +7 -0
  16. package/dist/compound-head.d.ts.map +1 -0
  17. package/dist/compound-head.js +19 -0
  18. package/dist/compound-head.js.map +1 -0
  19. package/dist/date-format.d.ts +8 -3
  20. package/dist/date-format.d.ts.map +1 -1
  21. package/dist/date-format.js +36 -3
  22. package/dist/date-format.js.map +1 -1
  23. package/dist/derived/count-per-period.d.ts +23 -0
  24. package/dist/derived/count-per-period.d.ts.map +1 -0
  25. package/dist/derived/count-per-period.js +14 -0
  26. package/dist/derived/count-per-period.js.map +1 -0
  27. package/dist/detectors/acronym-context.d.ts +4 -0
  28. package/dist/detectors/acronym-context.d.ts.map +1 -1
  29. package/dist/detectors/acronym-context.js +29 -1
  30. package/dist/detectors/acronym-context.js.map +1 -1
  31. package/dist/detectors/acronym-expansion.d.ts +5 -1
  32. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  33. package/dist/detectors/acronym-expansion.js +35 -3
  34. package/dist/detectors/acronym-expansion.js.map +1 -1
  35. package/dist/detectors/acronym-known.d.ts +19 -0
  36. package/dist/detectors/acronym-known.d.ts.map +1 -0
  37. package/dist/detectors/acronym-known.js +32 -0
  38. package/dist/detectors/acronym-known.js.map +1 -0
  39. package/dist/detectors/acronym-link.d.ts +14 -0
  40. package/dist/detectors/acronym-link.d.ts.map +1 -0
  41. package/dist/detectors/acronym-link.js +32 -0
  42. package/dist/detectors/acronym-link.js.map +1 -0
  43. package/dist/detectors/acronym-name.d.ts +8 -1
  44. package/dist/detectors/acronym-name.d.ts.map +1 -1
  45. package/dist/detectors/acronym-name.js +23 -4
  46. package/dist/detectors/acronym-name.js.map +1 -1
  47. package/dist/detectors/addressee-edge.d.ts +13 -0
  48. package/dist/detectors/addressee-edge.d.ts.map +1 -0
  49. package/dist/detectors/addressee-edge.js +13 -0
  50. package/dist/detectors/addressee-edge.js.map +1 -0
  51. package/dist/detectors/announced-count.d.ts.map +1 -1
  52. package/dist/detectors/announced-count.js +1 -0
  53. package/dist/detectors/announced-count.js.map +1 -1
  54. package/dist/detectors/bare-comparative.d.ts +4 -0
  55. package/dist/detectors/bare-comparative.d.ts.map +1 -1
  56. package/dist/detectors/bare-comparative.js +28 -3
  57. package/dist/detectors/bare-comparative.js.map +1 -1
  58. package/dist/detectors/blank-structure.d.ts +7 -1
  59. package/dist/detectors/blank-structure.d.ts.map +1 -1
  60. package/dist/detectors/blank-structure.js +13 -8
  61. package/dist/detectors/blank-structure.js.map +1 -1
  62. package/dist/detectors/bracket-specified.d.ts +6 -0
  63. package/dist/detectors/bracket-specified.d.ts.map +1 -0
  64. package/dist/detectors/bracket-specified.js +23 -0
  65. package/dist/detectors/bracket-specified.js.map +1 -0
  66. package/dist/detectors/broken-link.d.ts +6 -0
  67. package/dist/detectors/broken-link.d.ts.map +1 -1
  68. package/dist/detectors/broken-link.js +23 -2
  69. package/dist/detectors/broken-link.js.map +1 -1
  70. package/dist/detectors/caps-caption.d.ts +7 -6
  71. package/dist/detectors/caps-caption.d.ts.map +1 -1
  72. package/dist/detectors/caps-caption.js +38 -0
  73. package/dist/detectors/caps-caption.js.map +1 -1
  74. package/dist/detectors/caps-phrase.d.ts +6 -0
  75. package/dist/detectors/caps-phrase.d.ts.map +1 -0
  76. package/dist/detectors/caps-phrase.js +16 -0
  77. package/dist/detectors/caps-phrase.js.map +1 -0
  78. package/dist/detectors/char-shape.d.ts +1 -0
  79. package/dist/detectors/char-shape.d.ts.map +1 -1
  80. package/dist/detectors/char-shape.js +29 -16
  81. package/dist/detectors/char-shape.js.map +1 -1
  82. package/dist/detectors/cited-title.d.ts +3 -2
  83. package/dist/detectors/cited-title.d.ts.map +1 -1
  84. package/dist/detectors/cited-title.js +14 -3
  85. package/dist/detectors/cited-title.js.map +1 -1
  86. package/dist/detectors/counter-edge.d.ts +2 -1
  87. package/dist/detectors/counter-edge.d.ts.map +1 -1
  88. package/dist/detectors/counter-edge.js +17 -2
  89. package/dist/detectors/counter-edge.js.map +1 -1
  90. package/dist/detectors/date-any-year.d.ts +7 -3
  91. package/dist/detectors/date-any-year.d.ts.map +1 -1
  92. package/dist/detectors/date-any-year.js +25 -5
  93. package/dist/detectors/date-any-year.js.map +1 -1
  94. package/dist/detectors/date-format.d.ts.map +1 -1
  95. package/dist/detectors/date-format.js +2 -1
  96. package/dist/detectors/date-format.js.map +1 -1
  97. package/dist/detectors/date-year.d.ts +9 -3
  98. package/dist/detectors/date-year.d.ts.map +1 -1
  99. package/dist/detectors/date-year.js +45 -9
  100. package/dist/detectors/date-year.js.map +1 -1
  101. package/dist/detectors/deadline-unit.d.ts +20 -0
  102. package/dist/detectors/deadline-unit.d.ts.map +1 -0
  103. package/dist/detectors/deadline-unit.js +19 -0
  104. package/dist/detectors/deadline-unit.js.map +1 -0
  105. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  106. package/dist/detectors/defined-term-form.js +33 -7
  107. package/dist/detectors/defined-term-form.js.map +1 -1
  108. package/dist/detectors/definition-use.d.ts.map +1 -1
  109. package/dist/detectors/definition-use.js +6 -3
  110. package/dist/detectors/definition-use.js.map +1 -1
  111. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  112. package/dist/detectors/derived-numbers.js +17 -4
  113. package/dist/detectors/derived-numbers.js.map +1 -1
  114. package/dist/detectors/doubled-across-aside.d.ts +9 -0
  115. package/dist/detectors/doubled-across-aside.d.ts.map +1 -0
  116. package/dist/detectors/doubled-across-aside.js +27 -0
  117. package/dist/detectors/doubled-across-aside.js.map +1 -0
  118. package/dist/detectors/doubled-punctuation.d.ts +3 -3
  119. package/dist/detectors/doubled-punctuation.d.ts.map +1 -1
  120. package/dist/detectors/doubled-punctuation.js +23 -4
  121. package/dist/detectors/doubled-punctuation.js.map +1 -1
  122. package/dist/detectors/doubled-word.d.ts.map +1 -1
  123. package/dist/detectors/doubled-word.js +2 -1
  124. package/dist/detectors/doubled-word.js.map +1 -1
  125. package/dist/detectors/ellipsis.d.ts.map +1 -1
  126. package/dist/detectors/ellipsis.js +13 -1
  127. package/dist/detectors/ellipsis.js.map +1 -1
  128. package/dist/detectors/email-letter.d.ts +31 -1
  129. package/dist/detectors/email-letter.d.ts.map +1 -1
  130. package/dist/detectors/email-letter.js +76 -14
  131. package/dist/detectors/email-letter.js.map +1 -1
  132. package/dist/detectors/field-label.d.ts +7 -0
  133. package/dist/detectors/field-label.d.ts.map +1 -0
  134. package/dist/detectors/field-label.js +23 -0
  135. package/dist/detectors/field-label.js.map +1 -0
  136. package/dist/detectors/footnote-mismatch.d.ts +6 -1
  137. package/dist/detectors/footnote-mismatch.d.ts.map +1 -1
  138. package/dist/detectors/footnote-mismatch.js +28 -7
  139. package/dist/detectors/footnote-mismatch.js.map +1 -1
  140. package/dist/detectors/graded-step.d.ts +20 -0
  141. package/dist/detectors/graded-step.d.ts.map +1 -0
  142. package/dist/detectors/graded-step.js +33 -0
  143. package/dist/detectors/graded-step.js.map +1 -0
  144. package/dist/detectors/gram-openers.d.ts +8 -0
  145. package/dist/detectors/gram-openers.d.ts.map +1 -0
  146. package/dist/detectors/gram-openers.js +16 -0
  147. package/dist/detectors/gram-openers.js.map +1 -0
  148. package/dist/detectors/gram-place.d.ts +3 -1
  149. package/dist/detectors/gram-place.d.ts.map +1 -1
  150. package/dist/detectors/gram-place.js +8 -0
  151. package/dist/detectors/gram-place.js.map +1 -1
  152. package/dist/detectors/gram-predicate.d.ts.map +1 -1
  153. package/dist/detectors/gram-predicate.js +37 -3
  154. package/dist/detectors/gram-predicate.js.map +1 -1
  155. package/dist/detectors/heading-echo.d.ts.map +1 -1
  156. package/dist/detectors/heading-echo.js +28 -2
  157. package/dist/detectors/heading-echo.js.map +1 -1
  158. package/dist/detectors/ime-residue.d.ts +1 -1
  159. package/dist/detectors/ime-residue.d.ts.map +1 -1
  160. package/dist/detectors/ime-residue.js +4 -3
  161. package/dist/detectors/ime-residue.js.map +1 -1
  162. package/dist/detectors/kanji-compound-slip.d.ts +3 -0
  163. package/dist/detectors/kanji-compound-slip.d.ts.map +1 -0
  164. package/dist/detectors/kanji-compound-slip.js +50 -0
  165. package/dist/detectors/kanji-compound-slip.js.map +1 -0
  166. package/dist/detectors/kutoten-consistency.d.ts +10 -2
  167. package/dist/detectors/kutoten-consistency.d.ts.map +1 -1
  168. package/dist/detectors/kutoten-consistency.js +76 -12
  169. package/dist/detectors/kutoten-consistency.js.map +1 -1
  170. package/dist/detectors/lexicon.d.ts.map +1 -1
  171. package/dist/detectors/lexicon.js +8 -2
  172. package/dist/detectors/lexicon.js.map +1 -1
  173. package/dist/detectors/link-shape.d.ts.map +1 -1
  174. package/dist/detectors/link-shape.js +9 -3
  175. package/dist/detectors/link-shape.js.map +1 -1
  176. package/dist/detectors/list-item-ending.d.ts +18 -0
  177. package/dist/detectors/list-item-ending.d.ts.map +1 -0
  178. package/dist/detectors/list-item-ending.js +35 -0
  179. package/dist/detectors/list-item-ending.js.map +1 -0
  180. package/dist/detectors/list-parallel.d.ts +18 -5
  181. package/dist/detectors/list-parallel.d.ts.map +1 -1
  182. package/dist/detectors/list-parallel.js +116 -36
  183. package/dist/detectors/list-parallel.js.map +1 -1
  184. package/dist/detectors/list-sentence.d.ts +4 -0
  185. package/dist/detectors/list-sentence.d.ts.map +1 -1
  186. package/dist/detectors/list-sentence.js +1 -1
  187. package/dist/detectors/list-sentence.js.map +1 -1
  188. package/dist/detectors/mark-shape.d.ts +11 -4
  189. package/dist/detectors/mark-shape.d.ts.map +1 -1
  190. package/dist/detectors/mark-shape.js +58 -10
  191. package/dist/detectors/mark-shape.js.map +1 -1
  192. package/dist/detectors/mentioned-phrase.d.ts +9 -0
  193. package/dist/detectors/mentioned-phrase.d.ts.map +1 -0
  194. package/dist/detectors/mentioned-phrase.js +25 -0
  195. package/dist/detectors/mentioned-phrase.js.map +1 -0
  196. package/dist/detectors/middle-dot.d.ts +2 -2
  197. package/dist/detectors/middle-dot.d.ts.map +1 -1
  198. package/dist/detectors/middle-dot.js +4 -2
  199. package/dist/detectors/middle-dot.js.map +1 -1
  200. package/dist/detectors/modal-conflict.d.ts.map +1 -1
  201. package/dist/detectors/modal-conflict.js +5 -2
  202. package/dist/detectors/modal-conflict.js.map +1 -1
  203. package/dist/detectors/notice-paragraph.d.ts +9 -0
  204. package/dist/detectors/notice-paragraph.d.ts.map +1 -0
  205. package/dist/detectors/notice-paragraph.js +21 -0
  206. package/dist/detectors/notice-paragraph.js.map +1 -0
  207. package/dist/detectors/number-style.d.ts.map +1 -1
  208. package/dist/detectors/number-style.js +15 -2
  209. package/dist/detectors/number-style.js.map +1 -1
  210. package/dist/detectors/number-unit.d.ts.map +1 -1
  211. package/dist/detectors/number-unit.js +24 -1
  212. package/dist/detectors/number-unit.js.map +1 -1
  213. package/dist/detectors/numeral-opener.d.ts +6 -0
  214. package/dist/detectors/numeral-opener.d.ts.map +1 -1
  215. package/dist/detectors/numeral-opener.js +24 -1
  216. package/dist/detectors/numeral-opener.js.map +1 -1
  217. package/dist/detectors/obligation-without-deadline.d.ts +5 -1
  218. package/dist/detectors/obligation-without-deadline.d.ts.map +1 -1
  219. package/dist/detectors/obligation-without-deadline.js +44 -23
  220. package/dist/detectors/obligation-without-deadline.js.map +1 -1
  221. package/dist/detectors/open-end-class.d.ts +16 -0
  222. package/dist/detectors/open-end-class.d.ts.map +1 -0
  223. package/dist/detectors/open-end-class.js +65 -0
  224. package/dist/detectors/open-end-class.js.map +1 -0
  225. package/dist/detectors/open-end-condition.d.ts +7 -0
  226. package/dist/detectors/open-end-condition.d.ts.map +1 -0
  227. package/dist/detectors/open-end-condition.js +35 -0
  228. package/dist/detectors/open-end-condition.js.map +1 -0
  229. package/dist/detectors/open-end-names.d.ts +9 -3
  230. package/dist/detectors/open-end-names.d.ts.map +1 -1
  231. package/dist/detectors/open-end-names.js +5 -2
  232. package/dist/detectors/open-end-names.js.map +1 -1
  233. package/dist/detectors/open-end-question.d.ts +15 -0
  234. package/dist/detectors/open-end-question.d.ts.map +1 -0
  235. package/dist/detectors/open-end-question.js +37 -0
  236. package/dist/detectors/open-end-question.js.map +1 -0
  237. package/dist/detectors/open-end-scope.d.ts +16 -0
  238. package/dist/detectors/open-end-scope.d.ts.map +1 -0
  239. package/dist/detectors/open-end-scope.js +47 -0
  240. package/dist/detectors/open-end-scope.js.map +1 -0
  241. package/dist/detectors/opening-date.d.ts +10 -0
  242. package/dist/detectors/opening-date.d.ts.map +1 -0
  243. package/dist/detectors/opening-date.js +33 -0
  244. package/dist/detectors/opening-date.js.map +1 -0
  245. package/dist/detectors/ordinary-phrase.d.ts +20 -0
  246. package/dist/detectors/ordinary-phrase.d.ts.map +1 -0
  247. package/dist/detectors/ordinary-phrase.js +54 -0
  248. package/dist/detectors/ordinary-phrase.js.map +1 -0
  249. package/dist/detectors/organisation-run.d.ts +5 -1
  250. package/dist/detectors/organisation-run.d.ts.map +1 -1
  251. package/dist/detectors/organisation-run.js +7 -0
  252. package/dist/detectors/organisation-run.js.map +1 -1
  253. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  254. package/dist/detectors/orthographic-variant.js +56 -20
  255. package/dist/detectors/orthographic-variant.js.map +1 -1
  256. package/dist/detectors/orthography.d.ts.map +1 -1
  257. package/dist/detectors/orthography.js +6 -1
  258. package/dist/detectors/orthography.js.map +1 -1
  259. package/dist/detectors/oxford-comma.d.ts.map +1 -1
  260. package/dist/detectors/oxford-comma.js +38 -1
  261. package/dist/detectors/oxford-comma.js.map +1 -1
  262. package/dist/detectors/person-consistency.d.ts +2 -1
  263. package/dist/detectors/person-consistency.d.ts.map +1 -1
  264. package/dist/detectors/person-consistency.js +5 -3
  265. package/dist/detectors/person-consistency.js.map +1 -1
  266. package/dist/detectors/phrase-match.d.ts +2 -0
  267. package/dist/detectors/phrase-match.d.ts.map +1 -1
  268. package/dist/detectors/phrase-match.js +11 -4
  269. package/dist/detectors/phrase-match.js.map +1 -1
  270. package/dist/detectors/placeholder-text.d.ts.map +1 -1
  271. package/dist/detectors/placeholder-text.js +18 -3
  272. package/dist/detectors/placeholder-text.js.map +1 -1
  273. package/dist/detectors/placeholder.d.ts +3 -1
  274. package/dist/detectors/placeholder.d.ts.map +1 -1
  275. package/dist/detectors/placeholder.js +8 -3
  276. package/dist/detectors/placeholder.js.map +1 -1
  277. package/dist/detectors/postcode-end.d.ts +3 -0
  278. package/dist/detectors/postcode-end.d.ts.map +1 -0
  279. package/dist/detectors/postcode-end.js +15 -0
  280. package/dist/detectors/postcode-end.js.map +1 -0
  281. package/dist/detectors/prose-total-words.d.ts +5 -0
  282. package/dist/detectors/prose-total-words.d.ts.map +1 -0
  283. package/dist/detectors/prose-total-words.js +14 -0
  284. package/dist/detectors/prose-total-words.js.map +1 -0
  285. package/dist/detectors/registry/kanji-compound-slip.d.ts +3 -0
  286. package/dist/detectors/registry/kanji-compound-slip.d.ts.map +1 -0
  287. package/dist/detectors/registry/kanji-compound-slip.js +3 -0
  288. package/dist/detectors/registry/kanji-compound-slip.js.map +1 -0
  289. package/dist/detectors/registry/phrase-in-use.d.ts +3 -0
  290. package/dist/detectors/registry/phrase-in-use.d.ts.map +1 -0
  291. package/dist/detectors/registry/phrase-in-use.js +3 -0
  292. package/dist/detectors/registry/phrase-in-use.js.map +1 -0
  293. package/dist/detectors/requirement-smell.d.ts.map +1 -1
  294. package/dist/detectors/requirement-smell.js +29 -5
  295. package/dist/detectors/requirement-smell.js.map +1 -1
  296. package/dist/detectors/scaled-figure-unit.d.ts +28 -0
  297. package/dist/detectors/scaled-figure-unit.d.ts.map +1 -0
  298. package/dist/detectors/scaled-figure-unit.js +78 -0
  299. package/dist/detectors/scaled-figure-unit.js.map +1 -0
  300. package/dist/detectors/sentence-counts.d.ts +2 -0
  301. package/dist/detectors/sentence-counts.d.ts.map +1 -1
  302. package/dist/detectors/sentence-counts.js +18 -7
  303. package/dist/detectors/sentence-counts.js.map +1 -1
  304. package/dist/detectors/sentence-ending.d.ts +2 -0
  305. package/dist/detectors/sentence-ending.d.ts.map +1 -1
  306. package/dist/detectors/sentence-ending.js +10 -3
  307. package/dist/detectors/sentence-ending.js.map +1 -1
  308. package/dist/detectors/sentence-load.d.ts +4 -1
  309. package/dist/detectors/sentence-load.d.ts.map +1 -1
  310. package/dist/detectors/sentence-load.js +9 -2
  311. package/dist/detectors/sentence-load.js.map +1 -1
  312. package/dist/detectors/signals.d.ts.map +1 -1
  313. package/dist/detectors/signals.js +30 -50
  314. package/dist/detectors/signals.js.map +1 -1
  315. package/dist/detectors/structure-tree.d.ts.map +1 -1
  316. package/dist/detectors/structure-tree.js +14 -13
  317. package/dist/detectors/structure-tree.js.map +1 -1
  318. package/dist/detectors/structure.d.ts.map +1 -1
  319. package/dist/detectors/structure.js +2 -1
  320. package/dist/detectors/structure.js.map +1 -1
  321. package/dist/detectors/subject-name-line.d.ts +8 -0
  322. package/dist/detectors/subject-name-line.d.ts.map +1 -0
  323. package/dist/detectors/subject-name-line.js +53 -0
  324. package/dist/detectors/subject-name-line.js.map +1 -0
  325. package/dist/detectors/table-header.d.ts.map +1 -1
  326. package/dist/detectors/table-header.js +13 -1
  327. package/dist/detectors/table-header.js.map +1 -1
  328. package/dist/detectors/technical-docs.d.ts.map +1 -1
  329. package/dist/detectors/technical-docs.js +4 -1
  330. package/dist/detectors/technical-docs.js.map +1 -1
  331. package/dist/detectors/term-mention-sentence.d.ts +4 -0
  332. package/dist/detectors/term-mention-sentence.d.ts.map +1 -0
  333. package/dist/detectors/term-mention-sentence.js +28 -0
  334. package/dist/detectors/term-mention-sentence.js.map +1 -0
  335. package/dist/detectors/titled-run.d.ts +2 -1
  336. package/dist/detectors/titled-run.d.ts.map +1 -1
  337. package/dist/detectors/titled-run.js +9 -4
  338. package/dist/detectors/titled-run.js.map +1 -1
  339. package/dist/detectors/token-shape.d.ts.map +1 -1
  340. package/dist/detectors/token-shape.js +13 -5
  341. package/dist/detectors/token-shape.js.map +1 -1
  342. package/dist/detectors/vague-density.d.ts +5 -2
  343. package/dist/detectors/vague-density.d.ts.map +1 -1
  344. package/dist/detectors/vague-density.js +17 -4
  345. package/dist/detectors/vague-density.js.map +1 -1
  346. package/dist/document-paragraphs.d.ts +4 -0
  347. package/dist/document-paragraphs.d.ts.map +1 -0
  348. package/dist/document-paragraphs.js +24 -0
  349. package/dist/document-paragraphs.js.map +1 -0
  350. package/dist/document.d.ts.map +1 -1
  351. package/dist/document.js +2 -19
  352. package/dist/document.js.map +1 -1
  353. package/dist/figure-references.d.ts +3 -0
  354. package/dist/figure-references.d.ts.map +1 -1
  355. package/dist/figure-references.js +6 -2
  356. package/dist/figure-references.js.map +1 -1
  357. package/dist/format-template.d.ts +3 -0
  358. package/dist/format-template.d.ts.map +1 -0
  359. package/dist/format-template.js +13 -0
  360. package/dist/format-template.js.map +1 -0
  361. package/dist/formula-brackets.d.ts +7 -0
  362. package/dist/formula-brackets.d.ts.map +1 -0
  363. package/dist/formula-brackets.js +29 -0
  364. package/dist/formula-brackets.js.map +1 -0
  365. package/dist/html/html-markdown.d.ts.map +1 -1
  366. package/dist/html/html-markdown.js +2 -1
  367. package/dist/html/html-markdown.js.map +1 -1
  368. package/dist/html/html-site-chrome.d.ts +5 -0
  369. package/dist/html/html-site-chrome.d.ts.map +1 -0
  370. package/dist/html/html-site-chrome.js +33 -0
  371. package/dist/html/html-site-chrome.js.map +1 -0
  372. package/dist/joined-view.d.ts.map +1 -1
  373. package/dist/joined-view.js +1 -0
  374. package/dist/joined-view.js.map +1 -1
  375. package/dist/kanji-compound-slip.d.ts +32 -0
  376. package/dist/kanji-compound-slip.d.ts.map +1 -0
  377. package/dist/kanji-compound-slip.js +63 -0
  378. package/dist/kanji-compound-slip.js.map +1 -0
  379. package/dist/latin-company-form.d.ts +6 -0
  380. package/dist/latin-company-form.d.ts.map +1 -0
  381. package/dist/latin-company-form.js +32 -0
  382. package/dist/latin-company-form.js.map +1 -0
  383. package/dist/latin-word-boundary.d.ts +15 -0
  384. package/dist/latin-word-boundary.d.ts.map +1 -0
  385. package/dist/latin-word-boundary.js +40 -0
  386. package/dist/latin-word-boundary.js.map +1 -0
  387. package/dist/lexicon-genres.d.ts +2 -0
  388. package/dist/lexicon-genres.d.ts.map +1 -1
  389. package/dist/lexicon-genres.js +1 -1
  390. package/dist/lexicon-genres.js.map +1 -1
  391. package/dist/link-shape.d.ts +5 -0
  392. package/dist/link-shape.d.ts.map +1 -1
  393. package/dist/link-shape.js +18 -0
  394. package/dist/link-shape.js.map +1 -1
  395. package/dist/link-title.d.ts +4 -0
  396. package/dist/link-title.d.ts.map +1 -0
  397. package/dist/link-title.js +33 -0
  398. package/dist/link-title.js.map +1 -0
  399. package/dist/list-item-paragraphs.d.ts +15 -0
  400. package/dist/list-item-paragraphs.d.ts.map +1 -0
  401. package/dist/list-item-paragraphs.js +19 -0
  402. package/dist/list-item-paragraphs.js.map +1 -0
  403. package/dist/literal-quote.d.ts +12 -0
  404. package/dist/literal-quote.d.ts.map +1 -0
  405. package/dist/literal-quote.js +43 -0
  406. package/dist/literal-quote.js.map +1 -0
  407. package/dist/nado-closed-list.d.ts.map +1 -1
  408. package/dist/nado-closed-list.js +39 -5
  409. package/dist/nado-closed-list.js.map +1 -1
  410. package/dist/name-separator.d.ts.map +1 -1
  411. package/dist/name-separator.js +25 -2
  412. package/dist/name-separator.js.map +1 -1
  413. package/dist/nominal-stem.d.ts +14 -1
  414. package/dist/nominal-stem.d.ts.map +1 -1
  415. package/dist/nominal-stem.js +42 -4
  416. package/dist/nominal-stem.js.map +1 -1
  417. package/dist/person-forms.d.ts +8 -0
  418. package/dist/person-forms.d.ts.map +1 -1
  419. package/dist/person-forms.js +12 -0
  420. package/dist/person-forms.js.map +1 -1
  421. package/dist/plugin.d.ts +5 -0
  422. package/dist/plugin.d.ts.map +1 -1
  423. package/dist/preamble-paragraphs.d.ts +3 -2
  424. package/dist/preamble-paragraphs.d.ts.map +1 -1
  425. package/dist/preamble-paragraphs.js +8 -2
  426. package/dist/preamble-paragraphs.js.map +1 -1
  427. package/dist/spacing-code.d.ts +1 -0
  428. package/dist/spacing-code.d.ts.map +1 -1
  429. package/dist/spacing-code.js +4 -1
  430. package/dist/spacing-code.js.map +1 -1
  431. package/dist/split-noun.d.ts +10 -0
  432. package/dist/split-noun.d.ts.map +1 -0
  433. package/dist/split-noun.js +50 -0
  434. package/dist/split-noun.js.map +1 -0
  435. package/dist/structure/attached-parts.d.ts +11 -0
  436. package/dist/structure/attached-parts.d.ts.map +1 -0
  437. package/dist/structure/attached-parts.js +31 -0
  438. package/dist/structure/attached-parts.js.map +1 -0
  439. package/dist/structure/build.d.ts.map +1 -1
  440. package/dist/structure/build.js +7 -4
  441. package/dist/structure/build.js.map +1 -1
  442. package/dist/structure/contents-lines.d.ts +7 -0
  443. package/dist/structure/contents-lines.d.ts.map +1 -0
  444. package/dist/structure/contents-lines.js +51 -0
  445. package/dist/structure/contents-lines.js.map +1 -0
  446. package/dist/structure/defining-names.d.ts +37 -0
  447. package/dist/structure/defining-names.d.ts.map +1 -0
  448. package/dist/structure/defining-names.js +122 -0
  449. package/dist/structure/defining-names.js.map +1 -0
  450. package/dist/structure/definition-use.d.ts +3 -2
  451. package/dist/structure/definition-use.d.ts.map +1 -1
  452. package/dist/structure/definition-use.js +99 -31
  453. package/dist/structure/definition-use.js.map +1 -1
  454. package/dist/structure/issues.d.ts +4 -1
  455. package/dist/structure/issues.d.ts.map +1 -1
  456. package/dist/structure/issues.js +5 -2
  457. package/dist/structure/issues.js.map +1 -1
  458. package/dist/structure/list-lead-in.d.ts +19 -0
  459. package/dist/structure/list-lead-in.d.ts.map +1 -0
  460. package/dist/structure/list-lead-in.js +85 -0
  461. package/dist/structure/list-lead-in.js.map +1 -0
  462. package/dist/structure/modal-conflict.d.ts +7 -2
  463. package/dist/structure/modal-conflict.d.ts.map +1 -1
  464. package/dist/structure/modal-conflict.js +36 -7
  465. package/dist/structure/modal-conflict.js.map +1 -1
  466. package/dist/structure/negated-act.d.ts +17 -0
  467. package/dist/structure/negated-act.d.ts.map +1 -0
  468. package/dist/structure/negated-act.js +32 -0
  469. package/dist/structure/negated-act.js.map +1 -0
  470. package/dist/structure/numbering.d.ts +3 -1
  471. package/dist/structure/numbering.d.ts.map +1 -1
  472. package/dist/structure/numbering.js +20 -8
  473. package/dist/structure/numbering.js.map +1 -1
  474. package/dist/structure/prose-total.d.ts +4 -0
  475. package/dist/structure/prose-total.d.ts.map +1 -1
  476. package/dist/structure/prose-total.js +16 -6
  477. package/dist/structure/prose-total.js.map +1 -1
  478. package/dist/structure/repeated-number.d.ts +24 -0
  479. package/dist/structure/repeated-number.d.ts.map +1 -0
  480. package/dist/structure/repeated-number.js +89 -0
  481. package/dist/structure/repeated-number.js.map +1 -0
  482. package/dist/structure/rounded-amount.d.ts +8 -0
  483. package/dist/structure/rounded-amount.d.ts.map +1 -0
  484. package/dist/structure/rounded-amount.js +48 -0
  485. package/dist/structure/rounded-amount.js.map +1 -0
  486. package/dist/structure/side-step.d.ts +8 -0
  487. package/dist/structure/side-step.d.ts.map +1 -0
  488. package/dist/structure/side-step.js +21 -0
  489. package/dist/structure/side-step.js.map +1 -0
  490. package/dist/structure/spanned-label-blank.d.ts +8 -0
  491. package/dist/structure/spanned-label-blank.d.ts.map +1 -0
  492. package/dist/structure/spanned-label-blank.js +37 -0
  493. package/dist/structure/spanned-label-blank.js.map +1 -0
  494. package/dist/structure/universal.d.ts +3 -1
  495. package/dist/structure/universal.d.ts.map +1 -1
  496. package/dist/structure/universal.js +9 -2
  497. package/dist/structure/universal.js.map +1 -1
  498. package/dist/structure/unspaced-heading.d.ts +10 -0
  499. package/dist/structure/unspaced-heading.d.ts.map +1 -0
  500. package/dist/structure/unspaced-heading.js +24 -0
  501. package/dist/structure/unspaced-heading.js.map +1 -0
  502. package/dist/structure/written-list-path.d.ts +10 -0
  503. package/dist/structure/written-list-path.d.ts.map +1 -0
  504. package/dist/structure/written-list-path.js +37 -0
  505. package/dist/structure/written-list-path.js.map +1 -0
  506. package/dist/structure/written-resolve.d.ts +1 -1
  507. package/dist/structure/written-resolve.d.ts.map +1 -1
  508. package/dist/structure/written-resolve.js +15 -9
  509. package/dist/structure/written-resolve.js.map +1 -1
  510. package/dist/title-line.d.ts +21 -0
  511. package/dist/title-line.d.ts.map +1 -0
  512. package/dist/title-line.js +27 -0
  513. package/dist/title-line.js.map +1 -0
  514. package/package.json +3 -3
  515. package/rules/all-caps-shouting.yaml +2 -2
  516. package/rules/announced-count-mismatch.yaml +2 -2
  517. package/rules/bare-url-mix.yaml +2 -2
  518. package/rules/bracket-nesting.yaml +2 -2
  519. package/rules/bracket-width-consistency.yaml +2 -2
  520. package/rules/broken-link.yaml +2 -2
  521. package/rules/cliche.yaml +4 -3
  522. package/rules/code-fence-language-mix.yaml +2 -2
  523. package/rules/comparative-without-baseline.yaml +2 -2
  524. package/rules/dangling-figure-reference.yaml +2 -2
  525. package/rules/date-without-year.yaml +10 -2
  526. package/rules/defined-term-form.yaml +2 -2
  527. package/rules/doubled-punctuation.yaml +4 -4
  528. package/rules/doubled-word.yaml +2 -2
  529. package/rules/duplicate-definition.yaml +2 -2
  530. package/rules/duration-mismatch.yaml +2 -2
  531. package/rules/email-greeting-closing.yaml +2 -2
  532. package/rules/email-subject-length.yaml +2 -2
  533. package/rules/empty-table-cell.yaml +2 -2
  534. package/rules/footnote-mismatch.yaml +2 -2
  535. package/rules/heading-echo.yaml +2 -2
  536. package/rules/ime-residue.yaml +2 -2
  537. package/rules/kanji-compound-slip.yaml +67 -0
  538. package/rules/kutoten-consistency.yaml +4 -4
  539. package/rules/latin-spacing.yaml +2 -2
  540. package/rules/list-item-form-mix.yaml +2 -2
  541. package/rules/max-kanji-continuous.yaml +6 -4
  542. package/rules/modal-conflict.yaml +1 -0
  543. package/rules/nado-closed-list.yaml +2 -2
  544. package/rules/ngram-repetition.yaml +2 -2
  545. package/rules/no-doubled-joshi.yaml +2 -2
  546. package/rules/no-mixed-desumasu.yaml +3 -3
  547. package/rules/no-nakaguro-parallel.yaml +2 -0
  548. package/rules/number-without-unit.yaml +9 -4
  549. package/rules/numbering-gap.yaml +7 -2
  550. package/rules/numeral-sentence-start.yaml +2 -2
  551. package/rules/obligation-without-deadline.yaml +2 -2
  552. package/rules/orthographic-variant.yaml +2 -2
  553. package/rules/oxford-comma-consistency.yaml +3 -3
  554. package/rules/preamble-length.yaml +2 -2
  555. package/rules/requirement-smell.yaml +3 -3
  556. package/rules/table-header-variant.yaml +2 -2
  557. package/rules/teki-overuse.yaml +3 -2
  558. package/rules/total-mismatch.yaml +2 -2
  559. package/rules/translationese-density.yaml +2 -2
  560. package/rules/undefined-acronym.yaml +4 -2
  561. package/rules/unfilled-placeholder.yaml +4 -4
  562. package/rules/use-before-definition.yaml +2 -2
  563. package/rules/vague-word-density.yaml +2 -2
  564. package/skills/chaff/SKILL.md +7 -1
  565. package/src/announced-count.ts +35 -8
  566. package/src/chronology-rows.ts +86 -0
  567. package/src/company-name-run.ts +37 -0
  568. package/src/compound-head.ts +25 -0
  569. package/src/date-format.ts +43 -4
  570. package/src/derived/count-per-period.ts +28 -0
  571. package/src/detectors/acronym-context.ts +30 -1
  572. package/src/detectors/acronym-expansion.ts +47 -6
  573. package/src/detectors/acronym-known.ts +40 -0
  574. package/src/detectors/acronym-link.ts +37 -0
  575. package/src/detectors/acronym-name.ts +27 -4
  576. package/src/detectors/addressee-edge.ts +16 -0
  577. package/src/detectors/announced-count.ts +1 -0
  578. package/src/detectors/bare-comparative.ts +44 -3
  579. package/src/detectors/blank-structure.ts +21 -10
  580. package/src/detectors/bracket-specified.ts +25 -0
  581. package/src/detectors/broken-link.ts +26 -2
  582. package/src/detectors/caps-caption.ts +42 -0
  583. package/src/detectors/caps-phrase.ts +17 -0
  584. package/src/detectors/char-shape.ts +30 -9
  585. package/src/detectors/cited-title.ts +15 -3
  586. package/src/detectors/counter-edge.ts +17 -2
  587. package/src/detectors/date-any-year.ts +27 -6
  588. package/src/detectors/date-format.ts +2 -1
  589. package/src/detectors/date-year.ts +64 -16
  590. package/src/detectors/deadline-unit.ts +40 -0
  591. package/src/detectors/defined-term-form.ts +32 -12
  592. package/src/detectors/definition-use.ts +7 -3
  593. package/src/detectors/derived-numbers.ts +19 -4
  594. package/src/detectors/doubled-across-aside.ts +36 -0
  595. package/src/detectors/doubled-punctuation.ts +28 -7
  596. package/src/detectors/doubled-word.ts +2 -1
  597. package/src/detectors/ellipsis.ts +16 -1
  598. package/src/detectors/email-letter.ts +97 -21
  599. package/src/detectors/field-label.ts +25 -0
  600. package/src/detectors/footnote-mismatch.ts +39 -7
  601. package/src/detectors/graded-step.ts +52 -0
  602. package/src/detectors/gram-openers.ts +21 -0
  603. package/src/detectors/gram-place.ts +10 -1
  604. package/src/detectors/gram-predicate.ts +43 -3
  605. package/src/detectors/heading-echo.ts +31 -3
  606. package/src/detectors/ime-residue.ts +4 -3
  607. package/src/detectors/kanji-compound-slip.ts +61 -0
  608. package/src/detectors/kutoten-consistency.ts +93 -14
  609. package/src/detectors/lexicon.ts +12 -3
  610. package/src/detectors/link-shape.ts +10 -3
  611. package/src/detectors/list-item-ending.ts +51 -0
  612. package/src/detectors/list-parallel.ts +158 -33
  613. package/src/detectors/list-sentence.ts +5 -1
  614. package/src/detectors/mark-shape.ts +64 -10
  615. package/src/detectors/mentioned-phrase.ts +38 -0
  616. package/src/detectors/middle-dot.ts +7 -2
  617. package/src/detectors/modal-conflict.ts +5 -2
  618. package/src/detectors/notice-paragraph.ts +33 -0
  619. package/src/detectors/number-style.ts +14 -2
  620. package/src/detectors/number-unit.ts +28 -1
  621. package/src/detectors/numeral-opener.ts +27 -1
  622. package/src/detectors/obligation-without-deadline.ts +29 -4
  623. package/src/detectors/open-end-class.ts +78 -0
  624. package/src/detectors/open-end-condition.ts +40 -0
  625. package/src/detectors/open-end-names.ts +9 -3
  626. package/src/detectors/open-end-question.ts +50 -0
  627. package/src/detectors/open-end-scope.ts +62 -0
  628. package/src/detectors/opening-date.ts +47 -0
  629. package/src/detectors/ordinary-phrase.ts +62 -0
  630. package/src/detectors/organisation-run.ts +13 -2
  631. package/src/detectors/orthographic-variant.ts +85 -25
  632. package/src/detectors/orthography.ts +10 -1
  633. package/src/detectors/oxford-comma.ts +40 -1
  634. package/src/detectors/person-consistency.ts +5 -3
  635. package/src/detectors/phrase-match.ts +16 -5
  636. package/src/detectors/placeholder-text.ts +18 -3
  637. package/src/detectors/placeholder.ts +10 -3
  638. package/src/detectors/postcode-end.ts +16 -0
  639. package/src/detectors/prose-total-words.ts +18 -0
  640. package/src/detectors/registry/kanji-compound-slip.ts +4 -0
  641. package/src/detectors/registry/phrase-in-use.ts +4 -0
  642. package/src/detectors/requirement-smell.ts +38 -5
  643. package/src/detectors/scaled-figure-unit.ts +103 -0
  644. package/src/detectors/sentence-counts.ts +24 -8
  645. package/src/detectors/sentence-ending.ts +12 -3
  646. package/src/detectors/sentence-load.ts +9 -2
  647. package/src/detectors/signals.ts +33 -51
  648. package/src/detectors/structure-tree.ts +14 -14
  649. package/src/detectors/structure.ts +2 -1
  650. package/src/detectors/subject-name-line.ts +66 -0
  651. package/src/detectors/table-header.ts +15 -1
  652. package/src/detectors/technical-docs.ts +5 -1
  653. package/src/detectors/term-mention-sentence.ts +31 -0
  654. package/src/detectors/titled-run.ts +9 -4
  655. package/src/detectors/token-shape.ts +14 -5
  656. package/src/detectors/vague-density.ts +20 -4
  657. package/src/document-paragraphs.ts +27 -0
  658. package/src/document.ts +3 -23
  659. package/src/figure-references.ts +7 -2
  660. package/src/format-template.ts +15 -0
  661. package/src/formula-brackets.ts +36 -0
  662. package/src/html/html-markdown.ts +2 -1
  663. package/src/html/html-site-chrome.ts +47 -0
  664. package/src/joined-view.ts +1 -0
  665. package/src/kanji-compound-slip.ts +98 -0
  666. package/src/latin-company-form.ts +38 -0
  667. package/src/latin-word-boundary.ts +46 -0
  668. package/src/lexicon-genres.ts +1 -1
  669. package/src/link-shape.ts +19 -0
  670. package/src/link-title.ts +38 -0
  671. package/src/list-item-paragraphs.ts +22 -0
  672. package/src/literal-quote.ts +55 -0
  673. package/src/nado-closed-list.ts +48 -5
  674. package/src/name-separator.ts +27 -2
  675. package/src/nominal-stem.ts +62 -5
  676. package/src/person-forms.ts +15 -0
  677. package/src/plugin.ts +5 -0
  678. package/src/preamble-paragraphs.ts +13 -3
  679. package/src/spacing-code.ts +5 -1
  680. package/src/split-noun.ts +63 -0
  681. package/src/structure/attached-parts.ts +32 -0
  682. package/src/structure/build.ts +7 -4
  683. package/src/structure/contents-lines.ts +63 -0
  684. package/src/structure/defining-names.ts +155 -0
  685. package/src/structure/definition-use.ts +116 -25
  686. package/src/structure/issues.ts +7 -3
  687. package/src/structure/list-lead-in.ts +94 -0
  688. package/src/structure/modal-conflict.ts +42 -7
  689. package/src/structure/negated-act.ts +49 -0
  690. package/src/structure/numbering.ts +24 -9
  691. package/src/structure/prose-total.ts +20 -6
  692. package/src/structure/repeated-number.ts +110 -0
  693. package/src/structure/rounded-amount.ts +55 -0
  694. package/src/structure/side-step.ts +21 -0
  695. package/src/structure/spanned-label-blank.ts +39 -0
  696. package/src/structure/universal.ts +11 -2
  697. package/src/structure/unspaced-heading.ts +27 -0
  698. package/src/structure/written-list-path.ts +46 -0
  699. package/src/structure/written-resolve.ts +16 -9
  700. package/src/title-line.ts +52 -0
@@ -0,0 +1,38 @@
1
+ import type { Span } from "./plugin.ts";
2
+ import { linkTextOf } from "./link-text.ts";
3
+
4
+ /**
5
+ * 外のページの題名を引いたリンクの文字。行の頭に置いたリンク(- [達成基準 2.4.4: リンクの目的 (コンテキスト内)](https://…)、
6
+ * 参考: [ロードバランサーにタグを付ける](https://…))は、行き先のページの題名をそのまま写したもので、書き手が言い換えられない。
7
+ * リンクの後ろは行の終わりか、コロンに続く説明([題名](https://…): 説明)だけを許す。
8
+ * 文の中のリンク(詳しくは[こちら](https://…)をご覧ください)は書き手の言葉で、文書の中や手元のファイルへのリンク([README](./README.md))は題名ではない。
9
+ */
10
+
11
+ const EXTERNAL = /^<?https?:\/\//iu;
12
+ const TEXT_END = "](";
13
+ /** リンクの前に書いてよいもの: 行頭の空白と箇条書きの印、その後ろにコロンで終わる短い見出し語(参考:、出典:、See also:)。 */
14
+ const BULLET = /^[ \t]*(?:(?:[-*+]|\d{1,3}[.)])[ \t]+)?/u;
15
+ const LABEL = /^(?:[^\n::。、.,[\]]{1,15}[::][ \t]*)?$/u;
16
+ /** リンクの後ろに書いてよいもの: 行末までの空白と句読点か、説明を始めるコロン。 */
17
+ const LINE_TAIL = /^(?:[ \t.,;。、]*(?:\n|$)|[ \t]*[::])/u;
18
+
19
+ /** リンクの文字の範囲。[文字](行き先) の形で、行き先が外のページのものだけ。位置は source の上。 */
20
+ const externalTextOf = (source: string, link: Span): Span | undefined => {
21
+ const written = source.slice(link.start, link.end);
22
+ const text = linkTextOf(written);
23
+ if (text === undefined) return undefined;
24
+ const destination = written.slice(1 + text.length + TEXT_END.length);
25
+ return EXTERNAL.test(destination) ? { start: link.start + 1, end: link.start + 1 + text.length } : undefined;
26
+ };
27
+
28
+ const headsItsLine = (source: string, link: Span): boolean => {
29
+ const lineStart = source.lastIndexOf("\n", link.start - 1) + 1;
30
+ return LABEL.test(source.slice(lineStart, link.start).replace(BULLET, "")) && LINE_TAIL.test(source.slice(link.end));
31
+ };
32
+
33
+ /** 外のページの題名を引いたリンクの、文字の範囲(角括弧は含まない)。位置は source の上。links は doc.links。 */
34
+ export const externalLinkTitleSpans = (source: string, links: readonly Span[]): Span[] =>
35
+ links.flatMap((link) => {
36
+ const text = externalTextOf(source, link);
37
+ return text !== undefined && headsItsLine(source, link) ? [text] : [];
38
+ });
@@ -0,0 +1,22 @@
1
+ import type { Span } from "./plugin.ts";
2
+
3
+ /** 段落の中の文。listItem は、行頭の印で始まる箇条書きの項目を始める文(adapter が入れる)。 */
4
+ type ItemSentence = { readonly span: Span; readonly listItem?: true };
5
+
6
+ /**
7
+ * 空行を挟まずに行頭の印(①、・、(1))で項目を並べた段落を、項目ごとの段落に割る。
8
+ * 項目の後ろに続く印の無い行は、その項目の続き。割らないと、項目の並び全体が文の多すぎる 1 つの段落に読まれる。
9
+ *
10
+ * sentences は段落の中の文(並び順)。項目が無ければ paragraph 1 つ。
11
+ */
12
+ export const listItemParagraphs = (paragraph: Span, sentences: readonly ItemSentence[]): Span[] => {
13
+ const firsts = sentences.flatMap((sentence, index) => (index === 0 || sentence.listItem === true ? [index] : []));
14
+ if (firsts.length <= 1) return [paragraph];
15
+ return firsts.map((first, index) => {
16
+ const next = firsts[index + 1];
17
+ return {
18
+ start: index === 0 ? paragraph.start : (sentences[first]?.span.start ?? paragraph.start),
19
+ end: next === undefined ? paragraph.end : (sentences[next - 1]?.span.end ?? paragraph.end),
20
+ };
21
+ });
22
+ };
@@ -0,0 +1,55 @@
1
+ // A quotation that holds a string to be typed as it stands: an e-mail subject (件名に「応募(総務課)」と記載), a file name
2
+ // (「【様式】(氏名)」とファイル名を変更), a title (titled “Rates (2026)”). Its brackets are the string's own; the writer cannot
3
+ // take them out. Which words around a quotation say so come from the language's literal-quote-cue lexicon. Pure.
4
+ import { QUOTATION_MARKS, quotedSpans } from "./quoted-span.ts";
5
+ import type { LexiconEntry, Span } from "./plugin.ts";
6
+
7
+ export type LiteralCues = { readonly before: readonly string[]; readonly after: readonly string[] };
8
+
9
+ /** The cues of the lexicon: `position: before` stands in front of the quotation (件名に「…」), the rest right after it (「…」と記載). */
10
+ export const literalCues = (entries: readonly LexiconEntry[]): LiteralCues => ({
11
+ before: entries.filter((entry) => entry.position === "before").map((entry) => entry.pattern.toLowerCase()),
12
+ after: entries.filter((entry) => entry.position !== "before").map((entry) => entry.pattern.toLowerCase()),
13
+ });
14
+
15
+ /** Spaces allowed between a cue and its quotation; farther than this the cue is not the quotation's. */
16
+ const GAP = 8;
17
+ const LATIN_LETTER = /[A-Za-z]/u;
18
+
19
+ /** A cue that begins or ends with a Latin letter is a word: named does not match the end of unnamed. */
20
+ const standsBefore = (window: string, cue: string): boolean =>
21
+ window.endsWith(cue) && !(LATIN_LETTER.test(cue.at(0) ?? "") && LATIN_LETTER.test(window.at(-cue.length - 1) ?? ""));
22
+
23
+ const standsAfter = (window: string, cue: string): boolean =>
24
+ window.startsWith(cue) && !(LATIN_LETTER.test(cue.at(-1) ?? "") && LATIN_LETTER.test(window.at(cue.length) ?? ""));
25
+
26
+ /** How far from a quotation to read: the longest cue, one letter more to tell a word from part of one, and the gap. */
27
+ const reachOf = (cues: LiteralCues): number => Math.max(0, ...[...cues.before, ...cues.after].map((cue) => cue.length)) + 1 + GAP;
28
+
29
+ /** Only the few characters next to the quotation are read, so a sentence of many quotations stays linear. */
30
+ const isLiteral = (text: string, quoted: Span, cues: LiteralCues, reach: number): boolean => {
31
+ const opener = quoted.start - 1;
32
+ const closerEnd = quoted.end + 1;
33
+ const before = text
34
+ .slice(Math.max(0, opener - reach), opener)
35
+ .trimEnd()
36
+ .toLowerCase();
37
+ const after = text
38
+ .slice(closerEnd, closerEnd + reach)
39
+ .trimStart()
40
+ .toLowerCase();
41
+ return cues.before.some((cue) => standsBefore(before, cue)) || cues.after.some((cue) => standsAfter(after, cue));
42
+ };
43
+
44
+ /** The insides of the quotations the cues mark as a literal string, without the quotation marks. Positions count from 0 in `text`. */
45
+ export const literalQuoteSpans = (text: string, cues: LiteralCues): Span[] => {
46
+ const reach = reachOf(cues);
47
+ return quotedSpans(text, QUOTATION_MARKS).filter((quoted) => isLiteral(text, quoted, cues, reach));
48
+ };
49
+
50
+ /** The text with each literal quotation's inside blanked to spaces, so a measure of the sentence's own brackets skips it. */
51
+ export const withoutLiteralQuotes = (text: string, cues: LiteralCues): string => {
52
+ const units = text.split("");
53
+ literalQuoteSpans(text, cues).forEach((span) => units.fill(" ", span.start, span.end));
54
+ return units.join("");
55
+ };
@@ -38,6 +38,8 @@ const CLAUSE_POS: ReadonlySet<string> = new Set(["AUX", "VERB"]);
38
38
  /** A verb form that modifies a noun inside a list (a driving licence, used cars). */
39
39
  const NOUN_MODIFYING_FORMS: ReadonlySet<string> = new Set(["Ger", "Part"]);
40
40
  const LIST_SEPARATOR = /[,、]/u;
41
+ /** The parts of speech a list item is made of. */
42
+ const NAME_POS: ReadonlySet<string> = new Set(["NOUN", "PROPN"]);
41
43
 
42
44
  /** Where a word occurs in a text; a Latin word only as whole words, any case. */
43
45
  const placesOf = (text: string, word: string): number[] => {
@@ -59,12 +61,53 @@ const standsAsOpenWord = (tokens: readonly Token[], at: number, open: string): b
59
61
  return token !== undefined && open.toLowerCase().startsWith(token.surface.toLowerCase()) && token.features?.NounType !== CLASS_NOUN;
60
62
  };
61
63
 
62
- const limitRightAfter = (rest: string, words: ClosedListWords): string | undefined => {
63
- const after = rest.slice(BETWEEN_OPEN_AND_LIMIT.exec(rest)?.[0].length ?? 0);
64
- if (words.notLimits.some((phrase) => startsWithWord(after, phrase))) return undefined;
64
+ const BRACKET = /[(())]/gu;
65
+ const CLOSE_BRACKET = /[))]/u;
66
+
67
+ /** Where the bracket still open at offset opened, or -1 when none is. */
68
+ const openBracketBefore = (text: string, at: number): number => {
69
+ const stillOpen = [...text.slice(0, at).matchAll(BRACKET)].reduce<number[]>(
70
+ (open, bracket) => (CLOSE_BRACKET.test(bracket[0]) ? open.slice(0, -1) : [...open, bracket.index]),
71
+ [],
72
+ );
73
+ return stillOpen.at(-1) ?? -1;
74
+ };
75
+
76
+ /**
77
+ * The open word ends an aside on the word before the bracket, and the limit stands outside it (従業員(エンジニア、デザイナー等)のみ):
78
+ * the limit governs that word, and the list only gives examples of it.
79
+ */
80
+ const closesAsideBeforeLimit = (text: string, at: number, skipped: string): boolean => {
81
+ if (!CLOSE_BRACKET.test(skipped)) return false;
82
+ const bracket = openBracketBefore(text, at);
83
+ return LETTER.test(text.slice(0, Math.max(bracket, 0)).trimEnd().at(-1) ?? "");
84
+ };
85
+
86
+ const limitRightAfter = (text: string, at: number, open: string, words: ClosedListWords): string | undefined => {
87
+ const rest = text.slice(at + open.length);
88
+ const skipped = BETWEEN_OPEN_AND_LIMIT.exec(rest)?.[0] ?? "";
89
+ const after = rest.slice(skipped.length);
90
+ if (words.notLimits.some((phrase) => startsWithWord(after, phrase)) || closesAsideBeforeLimit(text, at, skipped)) return undefined;
65
91
  return longestFirst(words.limitsAfter).find((limit) => startsWithWord(after, limit));
66
92
  };
67
93
 
94
+ const isItemToken = (token: Token | undefined): token is Token => token !== undefined && NAME_POS.has(token.pos);
95
+
96
+ /** Where the run of nouns written right before end starts (監査 in 監査指示 before 等), or end when no noun ends there. */
97
+ const itemStart = (tokens: readonly Token[], end: number): number => {
98
+ const last = tokens.find((token) => token.span.end === end);
99
+ return isItemToken(last) ? itemStart(tokens, last.span.start) : end;
100
+ };
101
+
102
+ /** The nouns written right before the open word (指示 in 指示等), by the tokens; empty when none are. */
103
+ const itemBefore = (text: string, tokens: readonly Token[], at: number): string => text.slice(itemStart(tokens, at), at);
104
+
105
+ /** The same list written earlier in the sentence (個別の指示等が … 当該指示等に限り): the open word refers back to it, and leaves nothing new open. */
106
+ const repeatsEarlierList = (text: string, tokens: readonly Token[], at: number, open: string): boolean => {
107
+ const item = itemBefore(text, tokens, at);
108
+ return item !== "" && placesOf(text.slice(0, at - item.length), open).some((earlier) => itemBefore(text, tokens, earlier) === item);
109
+ };
110
+
68
111
  const longestFirst = (words: readonly string[]): string[] => [...words].sort((a, b) => b.length - a.length);
69
112
 
70
113
  const insideNotLimit = (text: string, at: number, notLimits: readonly string[]): boolean =>
@@ -97,9 +140,9 @@ const limitBefore = (text: string, tokens: readonly Token[], at: number, words:
97
140
  export const openWordsOnClosedLists = (text: string, tokens: readonly Token[], words: ClosedListWords): OpenOnClosed[] =>
98
141
  words.open
99
142
  .flatMap((open) => placesOf(text, open).map((at) => ({ open, at })))
100
- .filter(({ open, at }) => standsAsOpenWord(tokens, at, open))
143
+ .filter(({ open, at }) => standsAsOpenWord(tokens, at, open) && !repeatsEarlierList(text, tokens, at, open))
101
144
  .flatMap(({ open, at }) => {
102
- const limit = limitRightAfter(text.slice(at + open.length), words) ?? limitBefore(text, tokens, at, words);
145
+ const limit = limitRightAfter(text, at, open, words) ?? limitBefore(text, tokens, at, words);
103
146
  return limit === undefined ? [] : [{ offset: at, open, limit }];
104
147
  })
105
148
  .sort((a, b) => a.offset - b.offset || b.open.length - a.open.length)
@@ -63,6 +63,29 @@ const PAIR_LEAD = /^[ \t\u3000]*(?:(?:[-*+]|\d{1,3}[..))])[ \t\u3000]+)?/u;
63
63
  const PAIR_LABEL = /^[^\s\d、,,()()||::。!?]{2,12}$/u;
64
64
  const PAIR_VALUE = /^\d(?:[^\s、,,()()||。!?]|(?<=\d),(?=\d))*$/u;
65
65
 
66
+ /**
67
+ * 値の後ろに括弧で添えた説明(10万円 (税込 11万円))の、開く字と閉じる字。囲みは閉じ括弧の手前で終わるので、最後の開き括弧から後ろが説明。
68
+ * 入れ子の括弧((税込 (内訳)))は読まない。
69
+ */
70
+ const ASIDE_PAIRS: ReadonlyMap<string, string> = new Map([
71
+ ["(", ")"],
72
+ ["(", ")"],
73
+ ]);
74
+
75
+ /** 説明の括弧の後ろに残ってよいもの: 空白と強調の印。語が続けば(で募集します)文の中の値。 */
76
+ const LINE_REST = /^[\s*_]*$/u;
77
+
78
+ /**
79
+ * 組の値。値の後ろに同じ形の括弧で閉じた説明を添えて行が終わるなら、説明を除いた値。閉じていないか、
80
+ * 説明の後ろに語が続けば undefined。fromEnclosureEnd は囲みの終わり(閉じ括弧か行末)から行末まで。
81
+ */
82
+ const valueOf = (written: string, fromEnclosureEnd: string): string | undefined => {
83
+ const aside = Math.max(...[...ASIDE_PAIRS.keys()].map((open) => written.lastIndexOf(open)));
84
+ if (aside < 0) return written;
85
+ const closed = fromEnclosureEnd[0] === ASIDE_PAIRS.get(written[aside] ?? "");
86
+ return closed && LINE_REST.test(fromEnclosureEnd.slice(1)) ? written.slice(0, aside).trimEnd() : undefined;
87
+ };
88
+
66
89
  /** 項目名に出る品詞。名詞だけで、助詞・副詞(およそ)・接頭の語(約)を含めば、量の前置きか文。 */
67
90
  const PAIR_LABEL_POS = new Set(["NOUN", "PROPN"]);
68
91
 
@@ -92,10 +115,12 @@ const pairAround = (text: string, enclosure: Span, at: number): Span => {
92
115
  * 項目名が 1 字(約 30名)か名詞でない語を含む組は、文の中の空白として数える。
93
116
  */
94
117
  const isLabelledValue = (text: string, boundary: Boundary, tokens: readonly Token[], base: number): boolean => {
95
- const enclosure = enclosureAround(text, lineAround(text, boundary.offset), boundary.offset);
118
+ const line = lineAround(text, boundary.offset);
119
+ const enclosure = enclosureAround(text, line, boundary.offset);
96
120
  const pair = pairAround(text, enclosure, boundary.offset);
97
121
  const label = { start: pair.start, end: boundary.offset };
98
- if (!PAIR_LABEL.test(text.slice(label.start, label.end)) || !PAIR_VALUE.test(text.slice(boundary.offset + 1, pair.end))) return false;
122
+ const value = valueOf(text.slice(boundary.offset + 1, pair.end), text.slice(enclosure.end, line.end));
123
+ if (!PAIR_LABEL.test(text.slice(label.start, label.end)) || value === undefined || !PAIR_VALUE.test(value)) return false;
99
124
  if (SENTENCE_END.test(text.slice(enclosure.start, enclosure.end))) return false;
100
125
  const labelTokens = tokensIn(tokens, label, base);
101
126
  return (
@@ -1,4 +1,5 @@
1
1
  import type { Token } from "./plugin.ts";
2
+ import { kanjiSkeleton } from "./kana-spelling.ts";
2
3
 
3
4
  /** The last kana of a verb's dictionary form, and the kana its stem form (連用形) ends in instead: 払い込む → 払い込み. */
4
5
  const STEM_ENDING: Readonly<Record<string, string>> = { う: "い", く: "き", ぐ: "ぎ", す: "し", つ: "ち", ぬ: "に", ぶ: "び", む: "み", る: "り" };
@@ -17,20 +18,76 @@ export const isStemForm = (surface: string, lemma: string): boolean => {
17
18
  /** What carries a verb on (払い込み、払い込んで, 払い込みます, 払い込みやすい): after these the stem form is still the verb. */
18
19
  const VERB_GOES_ON = new Set(["AUX", "SCONJ", "VERB", "ADJ"]);
19
20
  const CLAUSE_COMMA = new Set(["、", ",", ","]);
21
+ /** The verbs that make a verb of a noun written right before them (申出する, 申出できる): the stem form there is the noun. */
22
+ const LIGHT_VERBS = new Set(["する", "できる", "出来る"]);
23
+
24
+ const carriesTheVerb = (next: Token): boolean => VERB_GOES_ON.has(next.pos) && !(next.pos === "VERB" && LIGHT_VERBS.has(next.lemma ?? ""));
25
+
26
+ /** A word's kanji and reading, one key for 申出 and 申し出 (申出|モウシデ); undefined for a word with no kanji or no reading. */
27
+ export const nounKeyOf = (token: Token): string | undefined => {
28
+ const kanji = kanjiSkeleton(token.surface);
29
+ return kanji === "" || token.reading === undefined ? undefined : `${kanji}|${token.reading}`;
30
+ };
31
+
32
+ const isAmong = (token: Token, nouns: ReadonlySet<string>): boolean => {
33
+ const key = nounKeyOf(token);
34
+ return key !== undefined && nouns.has(key);
35
+ };
20
36
 
21
37
  /**
22
38
  * A free noun after the stem form may begin the next clause (疾病にかかり療養のために), so the stem form stays a verb; a suffix
23
- * (払込み等), or a noun the caller lists as one (受取り前), may not.
39
+ * (払込み等), a noun the caller lists as one (受取り前), or any noun after a word the document writes as a noun elsewhere
40
+ * (申し出期限 beside 申出の撤回) may not.
24
41
  */
25
- const verbGoesOn = (next: Token, suffixes: ReadonlySet<string>): boolean =>
26
- VERB_GOES_ON.has(next.pos) || CLAUSE_COMMA.has(next.surface) || (next.pos === "NOUN" && next.features?.["Bound"] !== "Yes" && !suffixes.has(next.surface));
42
+ const verbGoesOn = (token: Token, next: Token, suffixes: ReadonlySet<string>, nouns: ReadonlySet<string>): boolean =>
43
+ carriesTheVerb(next) ||
44
+ CLAUSE_COMMA.has(next.surface) ||
45
+ (next.pos === "NOUN" && next.features?.["Bound"] !== "Yes" && !suffixes.has(next.surface) && !isAmong(token, nouns));
27
46
 
28
47
  /**
29
48
  * A verb's stem form that the verb does not go on from (払込みの期日, 申出により, 株式の買取り)): a noun the analyser read as the
30
49
  * verb, returned as the noun it is, spelled as written. Official Japanese spells the noun and the verb apart (払込み, 払い込む),
31
50
  * so the two are not one word in two spellings; the noun is compared with nouns. Any other token is returned as it is.
51
+ * nouns holds the document's nouns (documentNounKeys).
32
52
  */
33
- export const asNounWhereNominal = (token: Token, next: Token | undefined, suffixes: ReadonlySet<string> = new Set()): Token => {
53
+ export const asNounWhereNominal = (
54
+ token: Token,
55
+ next: Token | undefined,
56
+ suffixes: ReadonlySet<string> = new Set(),
57
+ nouns: ReadonlySet<string> = new Set(),
58
+ ): Token => {
34
59
  if (token.pos !== "VERB" || token.lemma === undefined || !isStemForm(token.surface, token.lemma)) return token;
35
- return next !== undefined && verbGoesOn(next, suffixes) ? token : { ...token, pos: "NOUN", lemma: token.surface };
60
+ return next !== undefined && verbGoesOn(token, next, suffixes, nouns) ? token : { ...token, pos: "NOUN", lemma: token.surface };
36
61
  };
62
+
63
+ /** A particle that ties what is before it to a noun after it (の, での, に関する, における): what follows it is a noun. */
64
+ const ATTRIBUTIVE_PARTICLE = /[のる]$/u;
65
+
66
+ const isStemAfterParticle = (token: Token, previous: Token | undefined, next: Token | undefined): boolean =>
67
+ previous?.pos === "ADP" &&
68
+ ATTRIBUTIVE_PARTICLE.test(previous.surface) &&
69
+ CLAUSE_COMMA.has(next?.surface ?? "") &&
70
+ token.pos === "VERB" &&
71
+ token.lemma !== undefined &&
72
+ isStemForm(token.surface, token.lemma);
73
+
74
+ /**
75
+ * The sentence with a stem form between a particle that ties it to a noun and a comma (地域の取組み、) read as the noun it is,
76
+ * spelled as written: before a comma the stem form is otherwise the verb (課題に取り組み、成果を上げた).
77
+ */
78
+ export const withNounsAfterParticles = (tokens: readonly Token[]): readonly Token[] =>
79
+ tokens.map((token, at) => (isStemAfterParticle(token, tokens[at - 1], tokens[at + 1]) ? { ...token, pos: "NOUN", lemma: token.surface } : token));
80
+
81
+ /**
82
+ * The keys (nounKeyOf) of the nouns a document writes, stem forms the verb does not go on from included (申出 before の): what
83
+ * tells asNounWhereNominal that a stem form before a noun makes a compound (申し出期限) rather than ends a clause (疾病にかかり療養).
84
+ */
85
+ export const documentNounKeys = (sentences: readonly (readonly Token[])[], suffixes: ReadonlySet<string> = new Set()): Set<string> =>
86
+ new Set(
87
+ sentences.map(withNounsAfterParticles).flatMap((tokens) =>
88
+ tokens.flatMap((token, at) => {
89
+ const key = asNounWhereNominal(token, tokens[at + 1], suffixes).pos === "NOUN" ? nounKeyOf(token) : undefined;
90
+ return key === undefined ? [] : [key];
91
+ }),
92
+ ),
93
+ );
@@ -104,6 +104,21 @@ const usualForm = (forms: readonly string[]): string | undefined => {
104
104
  return [...counts.entries()].reduce<[string, number] | undefined>((best, next) => (best === undefined || next[1] > best[1] ? next : best), undefined)?.[0];
105
105
  };
106
106
 
107
+ /**
108
+ * Pure: entries without the places of a form that also names something else (本社 is also the head office), unless that
109
+ * form is the slot's usual one. A document that calls itself 当社 writes 本社 for its head office; one that calls itself
110
+ * 本社 still has its stray 当社 pointed at.
111
+ */
112
+ export const withoutOtherSenses = <T extends { readonly mention: PersonMention }>(entries: readonly T[], otherSenses: ReadonlySet<string>): T[] => {
113
+ const usual = new Map(
114
+ [...new Set(entries.map((entry) => entry.mention.slot))].map((slot) => [
115
+ slot,
116
+ usualForm(entries.filter((entry) => entry.mention.slot === slot).map((entry) => entry.mention.form)),
117
+ ]),
118
+ );
119
+ return entries.filter((entry) => !otherSenses.has(entry.mention.form) || usual.get(entry.mention.slot) === entry.mention.form);
120
+ };
121
+
107
122
  /**
108
123
  * Pure: for each slot, the places that use another form than the document's usual one. Entries are in document order.
109
124
  * Nothing when the slot has one form, when the usual form has fewer than MIN_USUAL places, or when the other forms pass
package/src/plugin.ts CHANGED
@@ -24,6 +24,11 @@ export type Sentence = {
24
24
  * 長さの rule はこの単位で数え、この言語の上限と比べる。
25
25
  */
26
26
  readonly embeddedLanguage?: EmbeddedLanguage;
27
+ /**
28
+ * 行頭の印で始まる箇条書きの項目を始める文(「①誓約書」「・〜に関する業務」)。adapter が入れる。
29
+ * core は項目ごとに段落を改める。
30
+ */
31
+ readonly listItem?: true;
27
32
  };
28
33
 
29
34
  export type EmbeddedLanguage = { readonly id: string; readonly lengthUnit: LengthUnit };
@@ -19,10 +19,20 @@ const isNameField = (paragraph: Paragraph, source: string): boolean => {
19
19
  return value !== undefined && value.split(/\s+/u).every(isNameWord);
20
20
  };
21
21
 
22
+ /** A note the text attaches to one of its words (※1 特定の走行環境条件), opened by a mark of the note-mark lexicon. */
23
+ const isNote = (paragraph: Paragraph, source: string, noteMarks: readonly string[]): boolean => {
24
+ const text = source.slice(paragraph.span.start, paragraph.span.end).trimStart();
25
+ return noteMarks.some((mark) => mark !== "" && text.startsWith(mark));
26
+ };
27
+
22
28
  /**
23
29
  * 本題の前にあって、読み手が読み通す段落。文が一つも終止符で閉じない段落は数えない。
24
30
  * 分類の札(「エネルギー・環境」)、著者と所属の行、「印刷」、テンプレートの指示は文ではなく、読ませる前置きではない。
25
- * 宛先や差出人の欄(To: Minato Manufacturing Inc.)も、値が名前だけなら数えない。source は段落の範囲を読む文書。
31
+ * 宛先や差出人の欄(To: Minato Manufacturing Inc.)も、値が名前だけなら数えない。本文の語に付けた注(※1 …)は、
32
+ * 前置きの段落ではなく注なので数えない。source は段落の範囲を読む文書、noteMarks は語彙表 note-mark の印。
26
33
  */
27
- export const preambleParagraphs = (paragraphs: readonly Paragraph[], bodyStart: number, source = ""): Paragraph[] =>
28
- paragraphs.filter((paragraph) => paragraph.span.start < bodyStart && paragraph.sentences.some(isClosed) && !isNameField(paragraph, source));
34
+ export const preambleParagraphs = (paragraphs: readonly Paragraph[], bodyStart: number, source = "", noteMarks: readonly string[] = []): Paragraph[] =>
35
+ paragraphs.filter(
36
+ (paragraph) =>
37
+ paragraph.span.start < bodyStart && paragraph.sentences.some(isClosed) && !isNameField(paragraph, source) && !isNote(paragraph, source, noteMarks),
38
+ );
@@ -7,6 +7,8 @@ import { HYPHEN_CHARS } from "./orthography.ts";
7
7
 
8
8
  /** A clock time: hours, a colon, minutes, and seconds optionally (9:05, 13:30, 08:00:15). */
9
9
  const CLOCK_TIME = /(?<![\d::.])\d{1,2}[::][0-5]\d(?:[::][0-5]\d)?(?![\d::])/gu;
10
+ /** AM or PM written after a clock time, with a space or without (9:30AM, 9:30 p.m.). */
11
+ const MERIDIEM = /^ ?[AaPp]\.?[Mm]\.?(?![A-Za-z])/u;
10
12
 
11
13
  /** A run of printable ASCII without brackets: an address ends where Japanese text, a space or a bracket starts (…/faq で). */
12
14
  const ASCII_RUN = /[!-'*-~]+/gu;
@@ -43,7 +45,9 @@ const SERIAL_DIGITS = /\d{3}/u;
43
45
  const spansOf = (text: string, pattern: RegExp): Span[] =>
44
46
  [...text.matchAll(pattern)].map((match) => ({ start: match.index, end: match.index + match[0].length }));
45
47
 
46
- export const clockTimeSpans = (text: string): Span[] => spansOf(text, CLOCK_TIME);
48
+ /** Clock times, each with the AM or PM written after it (9:30AM). */
49
+ export const clockTimeSpans = (text: string): Span[] =>
50
+ spansOf(text, CLOCK_TIME).map((span) => ({ start: span.start, end: span.end + (MERIDIEM.exec(text.slice(span.end))?.[0].length ?? 0) }));
47
51
 
48
52
  export const addressSpans = (text: string): Span[] => spansOf(text, ASCII_RUN).filter((span) => isAddress(text.slice(span.start, span.end)));
49
53
 
@@ -0,0 +1,63 @@
1
+ import type { Sentence, Token } from "./plugin.ts";
2
+ import { kanjiSkeleton } from "./kana-spelling.ts";
3
+
4
+ const ALL_KANJI = /^[\p{Script=Han}々]+$/u;
5
+ /** A bound noun that is kanji and then kana (込み): the okurigana half of a word the tokenizer cut (申 and 込み). */
6
+ const KANJI_THEN_KANA = /^[\p{Script=Han}々]+[ぁ-ゖ]+$/u;
7
+
8
+ const isTouching = (head: Token, tail: Token): boolean => head.span.end === tail.span.start;
9
+
10
+ const isKanjiNoun = (token: Token): boolean => token.pos === "NOUN" && ALL_KANJI.test(token.surface);
11
+
12
+ /** 申 and 込み: a kanji noun and a bound noun of kanji and kana written onto it. */
13
+ const isCutAtKanji = (head: Token, tail: Token): boolean =>
14
+ isKanjiNoun(head) && tail.pos === "NOUN" && tail.features?.["Bound"] === "Yes" && KANJI_THEN_KANA.test(tail.surface) && isTouching(head, tail);
15
+
16
+ /**
17
+ * 申立 and て, the noun's reading already ending in テ (モウシタテ): its last kana cut off as a particle. て never follows a noun as
18
+ * a particle; other kana (の after 長野, ナガノ) do, so only て is read back.
19
+ */
20
+ const isCutBeforeTe = (head: Token, tail: Token): boolean =>
21
+ isKanjiNoun(head) && tail.surface === "て" && (tail.pos === "ADP" || tail.pos === "SCONJ") && head.reading?.endsWith("テ") === true && isTouching(head, tail);
22
+
23
+ /** The one reading the document gives a noun of these kanji (申込|モウシコミ for 申込); undefined for none or for two. */
24
+ const readingAmong = (kanji: string, nouns: ReadonlySet<string>): string | undefined => {
25
+ const readings = [...nouns].filter((key) => key.startsWith(`${kanji}|`)).map((key) => key.slice(kanji.length + 1));
26
+ return readings.length === 1 ? readings[0] : undefined;
27
+ };
28
+
29
+ const readingOfJoined = (head: Token, tail: Token, nouns: ReadonlySet<string>): string | undefined => {
30
+ if (isCutAtKanji(head, tail)) return readingAmong(kanjiSkeleton(head.surface + tail.surface), nouns);
31
+ return isCutBeforeTe(head, tail) ? head.reading : undefined;
32
+ };
33
+
34
+ const joinedOf = (head: Token, tail: Token | undefined, nouns: ReadonlySet<string>): Token | undefined => {
35
+ const reading = tail === undefined ? undefined : readingOfJoined(head, tail, nouns);
36
+ if (tail === undefined || reading === undefined) return undefined;
37
+ const surface = head.surface + tail.surface;
38
+ return { span: { start: head.span.start, end: tail.span.end }, surface, pos: "NOUN", lemma: surface, reading };
39
+ };
40
+
41
+ const joinedTokens = (tokens: readonly Token[], nouns: ReadonlySet<string>): Token[] =>
42
+ tokens.reduce<{ readonly out: Token[]; readonly skip: boolean }>(
43
+ ({ out, skip }, token, at) => {
44
+ if (skip) return { out, skip: false };
45
+ const joined = joinedOf(token, tokens[at + 1], nouns);
46
+ out.push(joined ?? token);
47
+ return { out, skip: joined !== undefined };
48
+ },
49
+ { out: [], skip: false },
50
+ ).out;
51
+
52
+ /**
53
+ * The sentence with a noun the tokenizer cut in two read whole: a kanji noun and a bound noun of kanji and kana written onto it
54
+ * (申 and 込み at the head of a sentence) are one noun (申込み) when the document writes a noun of the same kanji elsewhere
55
+ * (申し込み, 申込|モウシコミ in nouns), which gives the reading. With no such noun, or two readings for it, the tokens stay as cut.
56
+ * A kanji noun read with its last kana and that kana cut off as て (申立 and て) is one noun too (申立て).
57
+ * nouns holds the document's nouns (documentNounKeys).
58
+ */
59
+ export const withCutNounsJoined = (sentence: Sentence, nouns: ReadonlySet<string>): Sentence => {
60
+ const tokens = sentence.tokens ?? [];
61
+ const joined = joinedTokens(tokens, nouns);
62
+ return joined.length === tokens.length ? sentence : { ...sentence, tokens: joined };
63
+ };
@@ -0,0 +1,32 @@
1
+ // Where an instrument attached to the document starts: a title line naming it (Exhibit A to Merger Agreement, Annex 1).
2
+ // What is attached is a separate document (a restated charter, a form of note), so its definitions are its own. Pure.
3
+ import { escapeRegExp } from "../orthography.ts";
4
+ import { isTitleLine, type TitleNotion } from "../title-line.ts";
5
+
6
+ /** An instrument's title: any case (EXHIBIT A to the merger agreement), but no `: ; ,` after it, and only blank lines around. */
7
+ export const INSTRUMENT_TITLE: TitleNotion = { sentenceEnd: /[.!?。.?!:;,]$/u, capitalised: false, edge: "blank-line" };
8
+
9
+ const linesOf = (source: string): { readonly start: number; readonly text: string }[] => {
10
+ const starts = [0, ...[...source.matchAll(/\n/gu)].map((match) => match.index + 1)];
11
+ return starts.map((start, index) => ({ start, text: source.slice(start, (starts[index + 1] ?? source.length + 1) - 1) }));
12
+ };
13
+
14
+ /**
15
+ * The offsets of the lines that open an attached instrument: the line holds a label (Exhibit, Annex), its letter or
16
+ * number and at most a short title with no full stop, and a blank line or the document's edge is on both sides.
17
+ */
18
+ export const attachedPartStarts = (source: string, labels: readonly string[]): number[] => {
19
+ const words = labels.filter((label) => label !== "").map(escapeRegExp);
20
+ if (words.length === 0) return [];
21
+ const title = new RegExp(String.raw`^[ \t]*(?:#{1,6}[ \t]+)?(?:${words.join("|")})[ \t]+[A-Z0-9](?:[A-Z0-9\-]|\.(?=[A-Z0-9]))*(?=[ \t\r]|$)`, "u");
22
+ const lines = linesOf(source);
23
+ return lines.flatMap((line, index) => {
24
+ const label = title.exec(line.text);
25
+ if (label === null) return [];
26
+ const lineEnd = index === lines.length - 1 ? -1 : line.start + line.text.length;
27
+ return isTitleLine(source, line.start, lineEnd, line.text.slice(label[0].length), INSTRUMENT_TITLE) ? [line.start] : [];
28
+ });
29
+ };
30
+
31
+ /** Which part an offset falls in: 0 for the document itself, n for the n-th attached instrument. */
32
+ export const partAt = (starts: readonly number[], offset: number): number => starts.filter((start) => start <= offset).length;
@@ -9,6 +9,7 @@ import { lineNumberAt, linesOf, type Line } from "./lines.ts";
9
9
  import { universalNumber } from "./universal.ts";
10
10
  import { unnumberedUnit, type OpenUnit } from "./unnumbered.ts";
11
11
  import { readsOnHeading } from "./heading-leaves.ts";
12
+ import { unspacedHeadings } from "./unspaced-heading.ts";
12
13
  import { onLine, wrappedLine, type WrappedLine } from "./wrapped-tail.ts";
13
14
  import { foldPostOrder } from "../tree-walk.ts";
14
15
 
@@ -260,10 +261,11 @@ const headingsByLine = (lines: readonly Line[], headings: readonly Heading[]): M
260
261
  * 見出しの行から見出しの言葉を取る。コードを覆った後の行から取るので、見出しの中のコードも読まない。
261
262
  * ATX(行頭が #)なら印と閉じの # を外す。setext(下線で書く見出し)の末尾の # は言葉なので残す。
262
263
  */
263
- const headingLineText = (text: string): string => {
264
+ const headingLineText = (text: string, atx: boolean): string => {
264
265
  const line = text.trim();
265
266
  if (!line.startsWith("#")) return headingText(line);
266
- return atxHeadingText(line.replace(/^#{1,6}(?=[ \t]|$)/u, "").trim());
267
+ // A heading that ends on its own line is ATX, and its hashes go even with no space after them (unspaced-heading.ts).
268
+ return atxHeadingText(line.replace(atx ? /^#{1,6}/u : /^#{1,6}(?=[ \t]|$)/u, "").trim());
267
269
  };
268
270
 
269
271
  const captionPatterns = new WeakMap<DocumentProfile, RegExp | undefined>();
@@ -319,7 +321,7 @@ const withCaption = (state: State, line: Line, numbered: NumberedLine | undefine
319
321
  type Around = { readonly next: string | undefined; readonly previous: string | undefined; readonly inTable: boolean };
320
322
 
321
323
  const readLine = (state: State, patterns: StructurePatterns, line: Line, heading: Heading | undefined, around: Around): void => {
322
- const text = heading === undefined ? line.text : headingLineText(line.text);
324
+ const text = heading === undefined ? line.text : headingLineText(line.text, heading.end <= line.start + line.text.length);
323
325
  const openNumbers = state.stack.flatMap((frame) => (frame.numbered === undefined ? [] : [frame.numbered]));
324
326
  const context = { open: openNumbers, isHeading: heading !== undefined };
325
327
  const found = around.inTable ? undefined : (numberedLine(state, patterns, text, context) ?? codeTitledNumber(state, patterns, text, heading, context));
@@ -361,7 +363,8 @@ export const buildTree = (input: StructureInput, patterns: StructurePatterns): S
361
363
  // コードは同じ長さの空白で覆ってから読む。コードブロックの行は空になって飛ばされ、
362
364
  // 文中の `第99条` は参照として拾われない。位置は元の文書のまま。
363
365
  const lines = linesOf(maskSpans(input.source, outline.opaque));
364
- const headings = headingsByLine(lines, outline.headings);
366
+ const marked = headingsByLine(lines, outline.headings);
367
+ const headings = input.markdown ? new Map([...marked, ...unspacedHeadings(lines, marked, patterns)]) : marked;
365
368
  const attrs = { path: input.path, language: input.language, ...(input.profile === undefined ? {} : { profile: input.profile.id }) };
366
369
  const doc = draftOf("doc", "", { text: "", start: 0, number: 1 }, attrs);
367
370
  doc.end = input.source.length;
@@ -0,0 +1,63 @@
1
+ import type { MarkupHeading } from "../plugin.ts";
2
+ import { lineNumberAt, type Line } from "./lines.ts";
3
+
4
+ // 目次・索引の行と、番号に題だけを続けた行。どれも本文の文ではなく、文書の案内。純関数: 行と Markdown の見出しを受け取る。
5
+
6
+ /** 項目の終わり(ページ番号、条の番地 2.04(c))の後ろに、次の項目の大文字が続く所。 */
7
+ const ENTRY_BREAK = /(?<![\p{L}\p{N}$.,])\d+(?:\.\d+)*(?:\([\p{L}\p{N}]{1,4}\))*\s+(?=\p{Lu})/gu;
8
+ /** 一行に詰めた目次・索引と読むのに要る、項目の切れ目の数(Closing 2 Section 1.2 The Merger 2 ARTICLE II)。 */
9
+ const MIN_ENTRY_BREAKS = 3;
10
+ /** 目次・索引の行の言葉は、ほとんどが大文字で始まる。短くない語のうち小文字で始まる語がこの割合以上なら文。 */
11
+ const MAX_LOWERCASE_SHARE = 1 / 3;
12
+ /** これより短い語(of、and、the、番地の (c)、iii)は、題の中でも小文字で書く。 */
13
+ const MIN_CAPITALISED_LENGTH = 4;
14
+ const WORD = /\p{L}[\p{L}'’-]*/gu;
15
+ const LOWERCASE_START = /^\p{Ll}/u;
16
+
17
+ /** 短くない語のうち、小文字で始まる語の割合。 */
18
+ const lowercaseShare = (text: string): number => {
19
+ const words = (text.match(WORD) ?? []).filter((word) => word.length >= MIN_CAPITALISED_LENGTH);
20
+ return words.length === 0 ? 1 : words.filter((word) => LOWERCASE_START.test(word)).length / words.length;
21
+ };
22
+
23
+ /** 項目と番号を一行に詰めた目次・索引(Section 1.1 Closing 2 Section 1.2 The Merger 2、Company RSU 2.04(b) Company SEC Documents 4.07(a))。 */
24
+ const isPackedContents = (text: string): boolean => [...text.matchAll(ENTRY_BREAK)].length >= MIN_ENTRY_BREAKS && lowercaseShare(text) < MAX_LOWERCASE_SHARE;
25
+
26
+ /** 番号に題を続けただけの行(1 Supply of Ingredients)。題の語は大文字で始まり、文の終わりの印を持たない。 */
27
+ const NUMBERED_TITLE = /^\s*\d+(?:\.\d+)*\.?\s+\p{Lu}[^.。:;,!?]*$/u;
28
+ /** 題と読む語の数の上限。 */
29
+ const MAX_TITLE_WORDS = 8;
30
+
31
+ const NOT_LATIN_LETTER = /[^\P{L}\p{Script=Latin}]/u;
32
+
33
+ /** 英語の題の言葉か: 語が多すぎず、短くない語がどれも大文字で始まる(Supply of Ingredients、Extension; Waiver)。 */
34
+ export const isTitleWords = (text: string): boolean => {
35
+ const words = text.match(WORD) ?? [];
36
+ return (
37
+ words.length > 0 &&
38
+ !NOT_LATIN_LETTER.test(text) &&
39
+ words.length <= MAX_TITLE_WORDS &&
40
+ words.every((word) => word.length < MIN_CAPITALISED_LENGTH || !LOWERCASE_START.test(word))
41
+ );
42
+ };
43
+
44
+ const isNumberedTitle = (text: string): boolean => NUMBERED_TITLE.test(text) && isTitleWords(text);
45
+
46
+ /** 目次の見出し(語彙表 toc-heading)の下、次の見出しまでの行の番号。 */
47
+ const linesUnderContentsHeading = (lines: readonly Line[], headings: readonly MarkupHeading[], tocWords: readonly string[]): number[] => {
48
+ const names = new Set(tocWords.map((word) => word.toLowerCase()));
49
+ return headings.flatMap((heading, index) => {
50
+ if (!names.has(heading.text.trim().toLowerCase())) return [];
51
+ const from = lineNumberAt(lines, heading.start) ?? 0;
52
+ const next = headings[index + 1];
53
+ const to = next === undefined ? lines.length + 1 : (lineNumberAt(lines, next.start) ?? lines.length + 1);
54
+ return lines.filter((line) => line.number > from && line.number < to).map((line) => line.number);
55
+ });
56
+ };
57
+
58
+ /** 目次・索引の行と、番号に題だけを続けた行の番号(1 始まり)。 */
59
+ export const contentsLineNumbers = (lines: readonly Line[], headings: readonly MarkupHeading[], tocWords: readonly string[]): ReadonlySet<number> =>
60
+ new Set([
61
+ ...linesUnderContentsHeading(lines, headings, tocWords),
62
+ ...lines.filter((line) => isPackedContents(line.text) || isNumberedTitle(line.text)).map((line) => line.number),
63
+ ]);