chaffjs 0.34.0 → 0.35.0

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