chaffjs 0.30.0 → 0.31.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 (530) hide show
  1. package/dist/announced-count.d.ts +2 -0
  2. package/dist/announced-count.d.ts.map +1 -1
  3. package/dist/announced-count.js +4 -3
  4. package/dist/announced-count.js.map +1 -1
  5. package/dist/contacts/addresses.d.ts +21 -0
  6. package/dist/contacts/addresses.d.ts.map +1 -0
  7. package/dist/contacts/addresses.js +144 -0
  8. package/dist/contacts/addresses.js.map +1 -0
  9. package/dist/contacts/emails.d.ts +11 -0
  10. package/dist/contacts/emails.d.ts.map +1 -0
  11. package/dist/contacts/emails.js +51 -0
  12. package/dist/contacts/emails.js.map +1 -0
  13. package/dist/contacts/variants.d.ts +14 -0
  14. package/dist/contacts/variants.d.ts.map +1 -0
  15. package/dist/contacts/variants.js +25 -0
  16. package/dist/contacts/variants.js.map +1 -0
  17. package/dist/derived/battery-runtime.d.ts +85 -0
  18. package/dist/derived/battery-runtime.d.ts.map +1 -0
  19. package/dist/derived/battery-runtime.js +217 -0
  20. package/dist/derived/battery-runtime.js.map +1 -0
  21. package/dist/derived/period-counts.d.ts +48 -0
  22. package/dist/derived/period-counts.d.ts.map +1 -0
  23. package/dist/derived/period-counts.js +128 -0
  24. package/dist/derived/period-counts.js.map +1 -0
  25. package/dist/derived/time-lengths.d.ts.map +1 -1
  26. package/dist/derived/time-lengths.js +12 -9
  27. package/dist/derived/time-lengths.js.map +1 -1
  28. package/dist/derived/use-interval.d.ts +33 -0
  29. package/dist/derived/use-interval.d.ts.map +1 -0
  30. package/dist/derived/use-interval.js +93 -0
  31. package/dist/derived/use-interval.js.map +1 -0
  32. package/dist/detectors/announced-count.d.ts.map +1 -1
  33. package/dist/detectors/announced-count.js +1 -0
  34. package/dist/detectors/announced-count.js.map +1 -1
  35. package/dist/detectors/capacity-runtime.d.ts +7 -0
  36. package/dist/detectors/capacity-runtime.d.ts.map +1 -0
  37. package/dist/detectors/capacity-runtime.js +98 -0
  38. package/dist/detectors/capacity-runtime.js.map +1 -0
  39. package/dist/detectors/contact-variant.d.ts +3 -0
  40. package/dist/detectors/contact-variant.d.ts.map +1 -0
  41. package/dist/detectors/contact-variant.js +21 -0
  42. package/dist/detectors/contact-variant.js.map +1 -0
  43. package/dist/detectors/count-over-capacity.d.ts +4 -0
  44. package/dist/detectors/count-over-capacity.d.ts.map +1 -0
  45. package/dist/detectors/count-over-capacity.js +26 -0
  46. package/dist/detectors/count-over-capacity.js.map +1 -0
  47. package/dist/detectors/date-outside-period.d.ts.map +1 -1
  48. package/dist/detectors/date-outside-period.js +2 -1
  49. package/dist/detectors/date-outside-period.js.map +1 -1
  50. package/dist/detectors/date-stamp-order.d.ts +3 -0
  51. package/dist/detectors/date-stamp-order.d.ts.map +1 -0
  52. package/dist/detectors/date-stamp-order.js +24 -0
  53. package/dist/detectors/date-stamp-order.js.map +1 -0
  54. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  55. package/dist/detectors/defined-term-form.js +57 -7
  56. package/dist/detectors/defined-term-form.js.map +1 -1
  57. package/dist/detectors/definition-use.d.ts +1 -1
  58. package/dist/detectors/definition-use.d.ts.map +1 -1
  59. package/dist/detectors/definition-use.js +4 -2
  60. package/dist/detectors/definition-use.js.map +1 -1
  61. package/dist/detectors/derived-numbers.d.ts +9 -1
  62. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  63. package/dist/detectors/derived-numbers.js +2 -2
  64. package/dist/detectors/derived-numbers.js.map +1 -1
  65. package/dist/detectors/due-date.d.ts.map +1 -1
  66. package/dist/detectors/due-date.js +6 -0
  67. package/dist/detectors/due-date.js.map +1 -1
  68. package/dist/detectors/en-shape.d.ts.map +1 -1
  69. package/dist/detectors/en-shape.js +7 -4
  70. package/dist/detectors/en-shape.js.map +1 -1
  71. package/dist/detectors/event-deadline.d.ts +3 -0
  72. package/dist/detectors/event-deadline.d.ts.map +1 -0
  73. package/dist/detectors/event-deadline.js +24 -0
  74. package/dist/detectors/event-deadline.js.map +1 -0
  75. package/dist/detectors/fact-consistency.d.ts +1 -1
  76. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  77. package/dist/detectors/fact-consistency.js +52 -3
  78. package/dist/detectors/fact-consistency.js.map +1 -1
  79. package/dist/detectors/heading-case.d.ts +5 -2
  80. package/dist/detectors/heading-case.d.ts.map +1 -1
  81. package/dist/detectors/heading-case.js +12 -4
  82. package/dist/detectors/heading-case.js.map +1 -1
  83. package/dist/detectors/heading-echo.d.ts +5 -0
  84. package/dist/detectors/heading-echo.d.ts.map +1 -1
  85. package/dist/detectors/heading-echo.js +14 -1
  86. package/dist/detectors/heading-echo.js.map +1 -1
  87. package/dist/detectors/layout-code.d.ts +5 -0
  88. package/dist/detectors/layout-code.d.ts.map +1 -0
  89. package/dist/detectors/layout-code.js +124 -0
  90. package/dist/detectors/layout-code.js.map +1 -0
  91. package/dist/detectors/measured-facts.d.ts +8 -0
  92. package/dist/detectors/measured-facts.d.ts.map +1 -0
  93. package/dist/detectors/measured-facts.js +18 -0
  94. package/dist/detectors/measured-facts.js.map +1 -0
  95. package/dist/detectors/name-variant-model-codes.d.ts +11 -0
  96. package/dist/detectors/name-variant-model-codes.d.ts.map +1 -0
  97. package/dist/detectors/name-variant-model-codes.js +21 -0
  98. package/dist/detectors/name-variant-model-codes.js.map +1 -0
  99. package/dist/detectors/name-variant.d.ts.map +1 -1
  100. package/dist/detectors/name-variant.js +25 -4
  101. package/dist/detectors/name-variant.js.map +1 -1
  102. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  103. package/dist/detectors/orthographic-variant.js +62 -7
  104. package/dist/detectors/orthographic-variant.js.map +1 -1
  105. package/dist/detectors/orthography.d.ts.map +1 -1
  106. package/dist/detectors/orthography.js +12 -6
  107. package/dist/detectors/orthography.js.map +1 -1
  108. package/dist/detectors/quantity-range.d.ts +3 -0
  109. package/dist/detectors/quantity-range.d.ts.map +1 -0
  110. package/dist/detectors/quantity-range.js +35 -0
  111. package/dist/detectors/quantity-range.js.map +1 -0
  112. package/dist/detectors/range-band.d.ts +5 -0
  113. package/dist/detectors/range-band.d.ts.map +1 -0
  114. package/dist/detectors/range-band.js +80 -0
  115. package/dist/detectors/range-band.js.map +1 -0
  116. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts +3 -0
  117. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts.map +1 -0
  118. package/dist/detectors/registry/capacity-runtime-mismatch.js +3 -0
  119. package/dist/detectors/registry/capacity-runtime-mismatch.js.map +1 -0
  120. package/dist/detectors/registry/contact-variant.d.ts +3 -0
  121. package/dist/detectors/registry/contact-variant.d.ts.map +1 -0
  122. package/dist/detectors/registry/contact-variant.js +3 -0
  123. package/dist/detectors/registry/contact-variant.js.map +1 -0
  124. package/dist/detectors/registry/count-over-capacity.d.ts +3 -0
  125. package/dist/detectors/registry/count-over-capacity.d.ts.map +1 -0
  126. package/dist/detectors/registry/count-over-capacity.js +3 -0
  127. package/dist/detectors/registry/count-over-capacity.js.map +1 -0
  128. package/dist/detectors/registry/date-stamp-order.d.ts +3 -0
  129. package/dist/detectors/registry/date-stamp-order.d.ts.map +1 -0
  130. package/dist/detectors/registry/date-stamp-order.js +3 -0
  131. package/dist/detectors/registry/date-stamp-order.js.map +1 -0
  132. package/dist/detectors/registry/event-deadline.d.ts +3 -0
  133. package/dist/detectors/registry/event-deadline.d.ts.map +1 -0
  134. package/dist/detectors/registry/event-deadline.js +3 -0
  135. package/dist/detectors/registry/event-deadline.js.map +1 -0
  136. package/dist/detectors/registry/interval-count-mismatch.d.ts +3 -0
  137. package/dist/detectors/registry/interval-count-mismatch.d.ts.map +1 -0
  138. package/dist/detectors/registry/interval-count-mismatch.js +3 -0
  139. package/dist/detectors/registry/interval-count-mismatch.js.map +1 -0
  140. package/dist/detectors/registry/layout-code-mismatch.d.ts +3 -0
  141. package/dist/detectors/registry/layout-code-mismatch.d.ts.map +1 -0
  142. package/dist/detectors/registry/layout-code-mismatch.js +3 -0
  143. package/dist/detectors/registry/layout-code-mismatch.js.map +1 -0
  144. package/dist/detectors/registry/quantity-range.d.ts +3 -0
  145. package/dist/detectors/registry/quantity-range.d.ts.map +1 -0
  146. package/dist/detectors/registry/quantity-range.js +3 -0
  147. package/dist/detectors/registry/quantity-range.js.map +1 -0
  148. package/dist/detectors/registry/range-band-mismatch.d.ts +3 -0
  149. package/dist/detectors/registry/range-band-mismatch.d.ts.map +1 -0
  150. package/dist/detectors/registry/range-band-mismatch.js +3 -0
  151. package/dist/detectors/registry/range-band-mismatch.js.map +1 -0
  152. package/dist/detectors/registry/rent-multiple-mismatch.d.ts +3 -0
  153. package/dist/detectors/registry/rent-multiple-mismatch.d.ts.map +1 -0
  154. package/dist/detectors/registry/rent-multiple-mismatch.js +3 -0
  155. package/dist/detectors/registry/rent-multiple-mismatch.js.map +1 -0
  156. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts +3 -0
  157. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts.map +1 -0
  158. package/dist/detectors/registry/scaled-quantity-mismatch.js +3 -0
  159. package/dist/detectors/registry/scaled-quantity-mismatch.js.map +1 -0
  160. package/dist/detectors/registry/step-time-sum-mismatch.d.ts +3 -0
  161. package/dist/detectors/registry/step-time-sum-mismatch.d.ts.map +1 -0
  162. package/dist/detectors/registry/step-time-sum-mismatch.js +3 -0
  163. package/dist/detectors/registry/step-time-sum-mismatch.js.map +1 -0
  164. package/dist/detectors/registry/time-range-reversed.d.ts +3 -0
  165. package/dist/detectors/registry/time-range-reversed.d.ts.map +1 -0
  166. package/dist/detectors/registry/time-range-reversed.js +3 -0
  167. package/dist/detectors/registry/time-range-reversed.js.map +1 -0
  168. package/dist/detectors/registry/unlisted-item-used.d.ts +3 -0
  169. package/dist/detectors/registry/unlisted-item-used.d.ts.map +1 -0
  170. package/dist/detectors/registry/unlisted-item-used.js +3 -0
  171. package/dist/detectors/registry/unlisted-item-used.js.map +1 -0
  172. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts +3 -0
  173. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts.map +1 -0
  174. package/dist/detectors/registry/walk-time-distance-mismatch.js +3 -0
  175. package/dist/detectors/registry/walk-time-distance-mismatch.js.map +1 -0
  176. package/dist/detectors/rent-multiple-mismatch.d.ts +3 -0
  177. package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -0
  178. package/dist/detectors/rent-multiple-mismatch.js +39 -0
  179. package/dist/detectors/rent-multiple-mismatch.js.map +1 -0
  180. package/dist/detectors/scaled-quantity.d.ts +3 -0
  181. package/dist/detectors/scaled-quantity.d.ts.map +1 -0
  182. package/dist/detectors/scaled-quantity.js +37 -0
  183. package/dist/detectors/scaled-quantity.js.map +1 -0
  184. package/dist/detectors/signals.d.ts.map +1 -1
  185. package/dist/detectors/signals.js +3 -1
  186. package/dist/detectors/signals.js.map +1 -1
  187. package/dist/detectors/step-time-sum.d.ts +3 -0
  188. package/dist/detectors/step-time-sum.d.ts.map +1 -0
  189. package/dist/detectors/step-time-sum.js +46 -0
  190. package/dist/detectors/step-time-sum.js.map +1 -0
  191. package/dist/detectors/structure-tree.d.ts +2 -0
  192. package/dist/detectors/structure-tree.d.ts.map +1 -1
  193. package/dist/detectors/structure-tree.js +22 -5
  194. package/dist/detectors/structure-tree.js.map +1 -1
  195. package/dist/detectors/structure.js +1 -1
  196. package/dist/detectors/structure.js.map +1 -1
  197. package/dist/detectors/technical-docs.d.ts +5 -0
  198. package/dist/detectors/technical-docs.d.ts.map +1 -1
  199. package/dist/detectors/technical-docs.js +12 -0
  200. package/dist/detectors/technical-docs.js.map +1 -1
  201. package/dist/detectors/time-order.d.ts +1 -0
  202. package/dist/detectors/time-order.d.ts.map +1 -1
  203. package/dist/detectors/time-order.js +3 -1
  204. package/dist/detectors/time-order.js.map +1 -1
  205. package/dist/detectors/unit-consistency.d.ts +1 -1
  206. package/dist/detectors/unit-consistency.d.ts.map +1 -1
  207. package/dist/detectors/unit-consistency.js +46 -38
  208. package/dist/detectors/unit-consistency.js.map +1 -1
  209. package/dist/detectors/unlisted-items.d.ts +3 -0
  210. package/dist/detectors/unlisted-items.d.ts.map +1 -0
  211. package/dist/detectors/unlisted-items.js +57 -0
  212. package/dist/detectors/unlisted-items.js.map +1 -0
  213. package/dist/detectors/use-count-words.d.ts +8 -0
  214. package/dist/detectors/use-count-words.d.ts.map +1 -0
  215. package/dist/detectors/use-count-words.js +20 -0
  216. package/dist/detectors/use-count-words.js.map +1 -0
  217. package/dist/detectors/use-interval.d.ts +3 -0
  218. package/dist/detectors/use-interval.d.ts.map +1 -0
  219. package/dist/detectors/use-interval.js +39 -0
  220. package/dist/detectors/use-interval.js.map +1 -0
  221. package/dist/detectors/walk-time-distance-mismatch.d.ts +4 -0
  222. package/dist/detectors/walk-time-distance-mismatch.d.ts.map +1 -0
  223. package/dist/detectors/walk-time-distance-mismatch.js +38 -0
  224. package/dist/detectors/walk-time-distance-mismatch.js.map +1 -0
  225. package/dist/facts/approximate-tolerance.d.ts +7 -0
  226. package/dist/facts/approximate-tolerance.d.ts.map +1 -0
  227. package/dist/facts/approximate-tolerance.js +16 -0
  228. package/dist/facts/approximate-tolerance.js.map +1 -0
  229. package/dist/facts/item-amounts.d.ts +18 -0
  230. package/dist/facts/item-amounts.d.ts.map +1 -0
  231. package/dist/facts/item-amounts.js +67 -0
  232. package/dist/facts/item-amounts.js.map +1 -0
  233. package/dist/facts/labelled-facts.d.ts +4 -0
  234. package/dist/facts/labelled-facts.d.ts.map +1 -1
  235. package/dist/facts/labelled-facts.js +21 -6
  236. package/dist/facts/labelled-facts.js.map +1 -1
  237. package/dist/facts/measure-units.d.ts +5 -0
  238. package/dist/facts/measure-units.d.ts.map +1 -0
  239. package/dist/facts/measure-units.js +28 -0
  240. package/dist/facts/measure-units.js.map +1 -0
  241. package/dist/facts/measures.d.ts +23 -4
  242. package/dist/facts/measures.d.ts.map +1 -1
  243. package/dist/facts/measures.js +68 -19
  244. package/dist/facts/measures.js.map +1 -1
  245. package/dist/facts/retention-periods.d.ts +51 -0
  246. package/dist/facts/retention-periods.d.ts.map +1 -0
  247. package/dist/facts/retention-periods.js +148 -0
  248. package/dist/facts/retention-periods.js.map +1 -0
  249. package/dist/facts/unit-conflicts.d.ts +3 -2
  250. package/dist/facts/unit-conflicts.d.ts.map +1 -1
  251. package/dist/facts/unit-conflicts.js +15 -7
  252. package/dist/facts/unit-conflicts.js.map +1 -1
  253. package/dist/facts/unit-equivalents.d.ts +13 -0
  254. package/dist/facts/unit-equivalents.d.ts.map +1 -0
  255. package/dist/facts/unit-equivalents.js +56 -0
  256. package/dist/facts/unit-equivalents.js.map +1 -0
  257. package/dist/facts/unit-tolerance.d.ts +4 -0
  258. package/dist/facts/unit-tolerance.d.ts.map +1 -0
  259. package/dist/facts/unit-tolerance.js +13 -0
  260. package/dist/facts/unit-tolerance.js.map +1 -0
  261. package/dist/facts/unit-word.d.ts +8 -0
  262. package/dist/facts/unit-word.d.ts.map +1 -0
  263. package/dist/facts/unit-word.js +34 -0
  264. package/dist/facts/unit-word.js.map +1 -0
  265. package/dist/facts/written-precision.d.ts +10 -0
  266. package/dist/facts/written-precision.d.ts.map +1 -0
  267. package/dist/facts/written-precision.js +20 -0
  268. package/dist/facts/written-precision.js.map +1 -0
  269. package/dist/kana-spelling.d.ts +12 -0
  270. package/dist/kana-spelling.d.ts.map +1 -1
  271. package/dist/kana-spelling.js +23 -0
  272. package/dist/kana-spelling.js.map +1 -1
  273. package/dist/model-codes.d.ts +23 -0
  274. package/dist/model-codes.d.ts.map +1 -0
  275. package/dist/model-codes.js +81 -0
  276. package/dist/model-codes.js.map +1 -0
  277. package/dist/name-spelling-chars.d.ts +16 -0
  278. package/dist/name-spelling-chars.d.ts.map +1 -0
  279. package/dist/name-spelling-chars.js +14 -0
  280. package/dist/name-spelling-chars.js.map +1 -0
  281. package/dist/name-variants.d.ts +3 -2
  282. package/dist/name-variants.d.ts.map +1 -1
  283. package/dist/name-variants.js +4 -7
  284. package/dist/name-variants.js.map +1 -1
  285. package/dist/phone-numbers.d.ts.map +1 -1
  286. package/dist/phone-numbers.js +22 -5
  287. package/dist/phone-numbers.js.map +1 -1
  288. package/dist/place-names.d.ts +12 -5
  289. package/dist/place-names.d.ts.map +1 -1
  290. package/dist/place-names.js +34 -14
  291. package/dist/place-names.js.map +1 -1
  292. package/dist/plugin.d.ts +5 -0
  293. package/dist/plugin.d.ts.map +1 -1
  294. package/dist/preamble-paragraphs.d.ts +2 -1
  295. package/dist/preamble-paragraphs.d.ts.map +1 -1
  296. package/dist/preamble-paragraphs.js +16 -1
  297. package/dist/preamble-paragraphs.js.map +1 -1
  298. package/dist/profile/parse.d.ts.map +1 -1
  299. package/dist/profile/parse.js +11 -0
  300. package/dist/profile/parse.js.map +1 -1
  301. package/dist/spacing-code.d.ts +6 -0
  302. package/dist/spacing-code.d.ts.map +1 -0
  303. package/dist/spacing-code.js +16 -0
  304. package/dist/spacing-code.js.map +1 -0
  305. package/dist/structure/amount-range.d.ts +2 -2
  306. package/dist/structure/amount-range.d.ts.map +1 -1
  307. package/dist/structure/amount-range.js +12 -5
  308. package/dist/structure/amount-range.js.map +1 -1
  309. package/dist/structure/capacity-count.d.ts +59 -0
  310. package/dist/structure/capacity-count.d.ts.map +1 -0
  311. package/dist/structure/capacity-count.js +118 -0
  312. package/dist/structure/capacity-count.js.map +1 -0
  313. package/dist/structure/counted-amounts.d.ts +6 -0
  314. package/dist/structure/counted-amounts.d.ts.map +1 -1
  315. package/dist/structure/counted-amounts.js +4 -4
  316. package/dist/structure/counted-amounts.js.map +1 -1
  317. package/dist/structure/date-outside-period.d.ts +3 -1
  318. package/dist/structure/date-outside-period.d.ts.map +1 -1
  319. package/dist/structure/date-outside-period.js +8 -5
  320. package/dist/structure/date-outside-period.js.map +1 -1
  321. package/dist/structure/date-range.d.ts +1 -0
  322. package/dist/structure/date-range.d.ts.map +1 -1
  323. package/dist/structure/date-range.js +1 -1
  324. package/dist/structure/date-range.js.map +1 -1
  325. package/dist/structure/deadline-after-event.d.ts +23 -0
  326. package/dist/structure/deadline-after-event.d.ts.map +1 -0
  327. package/dist/structure/deadline-after-event.js +42 -0
  328. package/dist/structure/deadline-after-event.js.map +1 -0
  329. package/dist/structure/definition-use.d.ts +1 -1
  330. package/dist/structure/definition-use.d.ts.map +1 -1
  331. package/dist/structure/definition-use.js +55 -2
  332. package/dist/structure/definition-use.js.map +1 -1
  333. package/dist/structure/due-date.d.ts.map +1 -1
  334. package/dist/structure/due-date.js +5 -27
  335. package/dist/structure/due-date.js.map +1 -1
  336. package/dist/structure/labelled-date-order.d.ts +38 -0
  337. package/dist/structure/labelled-date-order.d.ts.map +1 -0
  338. package/dist/structure/labelled-date-order.js +41 -0
  339. package/dist/structure/labelled-date-order.js.map +1 -0
  340. package/dist/structure/layout-codes.d.ts +68 -0
  341. package/dist/structure/layout-codes.d.ts.map +1 -0
  342. package/dist/structure/layout-codes.js +161 -0
  343. package/dist/structure/layout-codes.js.map +1 -0
  344. package/dist/structure/measured-amounts.d.ts +17 -0
  345. package/dist/structure/measured-amounts.d.ts.map +1 -0
  346. package/dist/structure/measured-amounts.js +110 -0
  347. package/dist/structure/measured-amounts.js.map +1 -0
  348. package/dist/structure/quantity-range.d.ts +8 -0
  349. package/dist/structure/quantity-range.d.ts.map +1 -0
  350. package/dist/structure/quantity-range.js +36 -0
  351. package/dist/structure/quantity-range.js.map +1 -0
  352. package/dist/structure/range-bands.d.ts +75 -0
  353. package/dist/structure/range-bands.d.ts.map +1 -0
  354. package/dist/structure/range-bands.js +244 -0
  355. package/dist/structure/range-bands.js.map +1 -0
  356. package/dist/structure/rent-multiple.d.ts +76 -0
  357. package/dist/structure/rent-multiple.d.ts.map +1 -0
  358. package/dist/structure/rent-multiple.js +186 -0
  359. package/dist/structure/rent-multiple.js.map +1 -0
  360. package/dist/structure/scaled-quantities.d.ts +50 -0
  361. package/dist/structure/scaled-quantities.d.ts.map +1 -0
  362. package/dist/structure/scaled-quantities.js +153 -0
  363. package/dist/structure/scaled-quantities.js.map +1 -0
  364. package/dist/structure/stamp-order.d.ts +16 -0
  365. package/dist/structure/stamp-order.d.ts.map +1 -0
  366. package/dist/structure/stamp-order.js +78 -0
  367. package/dist/structure/stamp-order.js.map +1 -0
  368. package/dist/structure/step-times.d.ts +49 -0
  369. package/dist/structure/step-times.d.ts.map +1 -0
  370. package/dist/structure/step-times.js +128 -0
  371. package/dist/structure/step-times.js.map +1 -0
  372. package/dist/structure/term-deadline.d.ts +19 -0
  373. package/dist/structure/term-deadline.d.ts.map +1 -0
  374. package/dist/structure/term-deadline.js +28 -0
  375. package/dist/structure/term-deadline.js.map +1 -0
  376. package/dist/structure/time-range.d.ts +16 -0
  377. package/dist/structure/time-range.d.ts.map +1 -0
  378. package/dist/structure/time-range.js +57 -0
  379. package/dist/structure/time-range.js.map +1 -0
  380. package/dist/structure/total-line.d.ts +16 -0
  381. package/dist/structure/total-line.d.ts.map +1 -0
  382. package/dist/structure/total-line.js +44 -0
  383. package/dist/structure/total-line.js.map +1 -0
  384. package/dist/structure/total.d.ts +7 -1
  385. package/dist/structure/total.d.ts.map +1 -1
  386. package/dist/structure/total.js +28 -5
  387. package/dist/structure/total.js.map +1 -1
  388. package/dist/structure/unlisted-items.d.ts +70 -0
  389. package/dist/structure/unlisted-items.d.ts.map +1 -0
  390. package/dist/structure/unlisted-items.js +152 -0
  391. package/dist/structure/unlisted-items.js.map +1 -0
  392. package/dist/structure/walk-time.d.ts +87 -0
  393. package/dist/structure/walk-time.d.ts.map +1 -0
  394. package/dist/structure/walk-time.js +195 -0
  395. package/dist/structure/walk-time.js.map +1 -0
  396. package/package.json +3 -3
  397. package/profiles/listing.yaml +20 -0
  398. package/rules/announced-count-mismatch.yaml +2 -2
  399. package/rules/capacity-runtime-mismatch.yaml +66 -0
  400. package/rules/contact-variant.yaml +76 -0
  401. package/rules/count-over-capacity.yaml +55 -0
  402. package/rules/date-outside-period.yaml +4 -4
  403. package/rules/date-stamp-order.yaml +82 -0
  404. package/rules/deadline-after-event.yaml +67 -0
  405. package/rules/defined-name-repeated.yaml +2 -2
  406. package/rules/due-before-issue.yaml +11 -7
  407. package/rules/fact-conflict.yaml +9 -4
  408. package/rules/heading-echo.yaml +2 -2
  409. package/rules/interval-count-mismatch.yaml +54 -0
  410. package/rules/latin-spacing.yaml +2 -2
  411. package/rules/layout-code-mismatch.yaml +68 -0
  412. package/rules/name-variant.yaml +8 -2
  413. package/rules/no-em-dash.yaml +2 -2
  414. package/rules/preamble-length.yaml +2 -2
  415. package/rules/quantity-range-reversed.yaml +53 -0
  416. package/rules/range-band-mismatch.yaml +70 -0
  417. package/rules/rent-multiple-mismatch.yaml +61 -0
  418. package/rules/scaled-quantity-mismatch.yaml +69 -0
  419. package/rules/step-time-sum-mismatch.yaml +65 -0
  420. package/rules/time-range-reversed.yaml +48 -0
  421. package/rules/title-case-consistency.yaml +2 -2
  422. package/rules/total-mismatch.yaml +2 -2
  423. package/rules/unit-mismatch.yaml +12 -4
  424. package/rules/unlisted-item-used.yaml +90 -0
  425. package/rules/unused-definition.yaml +2 -2
  426. package/rules/use-before-definition.yaml +2 -2
  427. package/rules/walk-time-distance-mismatch.yaml +55 -0
  428. package/src/announced-count.ts +6 -3
  429. package/src/contacts/addresses.ts +179 -0
  430. package/src/contacts/emails.ts +63 -0
  431. package/src/contacts/variants.ts +33 -0
  432. package/src/derived/battery-runtime.ts +284 -0
  433. package/src/derived/period-counts.ts +178 -0
  434. package/src/derived/time-lengths.ts +12 -9
  435. package/src/derived/use-interval.ts +151 -0
  436. package/src/detectors/announced-count.ts +1 -0
  437. package/src/detectors/capacity-runtime.ts +128 -0
  438. package/src/detectors/contact-variant.ts +24 -0
  439. package/src/detectors/count-over-capacity.ts +37 -0
  440. package/src/detectors/date-outside-period.ts +2 -1
  441. package/src/detectors/date-stamp-order.ts +28 -0
  442. package/src/detectors/defined-term-form.ts +63 -7
  443. package/src/detectors/definition-use.ts +6 -2
  444. package/src/detectors/derived-numbers.ts +3 -3
  445. package/src/detectors/due-date.ts +10 -2
  446. package/src/detectors/en-shape.ts +11 -4
  447. package/src/detectors/event-deadline.ts +29 -0
  448. package/src/detectors/fact-consistency.ts +64 -3
  449. package/src/detectors/heading-case.ts +13 -4
  450. package/src/detectors/heading-echo.ts +17 -1
  451. package/src/detectors/layout-code.ts +174 -0
  452. package/src/detectors/measured-facts.ts +20 -0
  453. package/src/detectors/name-variant-model-codes.ts +27 -0
  454. package/src/detectors/name-variant.ts +32 -6
  455. package/src/detectors/orthographic-variant.ts +78 -9
  456. package/src/detectors/orthography.ts +16 -6
  457. package/src/detectors/quantity-range.ts +47 -0
  458. package/src/detectors/range-band.ts +125 -0
  459. package/src/detectors/registry/capacity-runtime-mismatch.ts +4 -0
  460. package/src/detectors/registry/contact-variant.ts +4 -0
  461. package/src/detectors/registry/count-over-capacity.ts +4 -0
  462. package/src/detectors/registry/date-stamp-order.ts +4 -0
  463. package/src/detectors/registry/event-deadline.ts +4 -0
  464. package/src/detectors/registry/interval-count-mismatch.ts +4 -0
  465. package/src/detectors/registry/layout-code-mismatch.ts +4 -0
  466. package/src/detectors/registry/quantity-range.ts +4 -0
  467. package/src/detectors/registry/range-band-mismatch.ts +4 -0
  468. package/src/detectors/registry/rent-multiple-mismatch.ts +4 -0
  469. package/src/detectors/registry/scaled-quantity-mismatch.ts +4 -0
  470. package/src/detectors/registry/step-time-sum-mismatch.ts +4 -0
  471. package/src/detectors/registry/time-range-reversed.ts +4 -0
  472. package/src/detectors/registry/unlisted-item-used.ts +4 -0
  473. package/src/detectors/registry/walk-time-distance-mismatch.ts +4 -0
  474. package/src/detectors/rent-multiple-mismatch.ts +56 -0
  475. package/src/detectors/scaled-quantity.ts +49 -0
  476. package/src/detectors/signals.ts +3 -1
  477. package/src/detectors/step-time-sum.ts +55 -0
  478. package/src/detectors/structure-tree.ts +30 -5
  479. package/src/detectors/structure.ts +1 -1
  480. package/src/detectors/technical-docs.ts +12 -0
  481. package/src/detectors/time-order.ts +5 -1
  482. package/src/detectors/unit-consistency.ts +50 -38
  483. package/src/detectors/unlisted-items.ts +72 -0
  484. package/src/detectors/use-count-words.ts +32 -0
  485. package/src/detectors/use-interval.ts +51 -0
  486. package/src/detectors/walk-time-distance-mismatch.ts +45 -0
  487. package/src/facts/approximate-tolerance.ts +19 -0
  488. package/src/facts/item-amounts.ts +94 -0
  489. package/src/facts/labelled-facts.ts +29 -7
  490. package/src/facts/measure-units.ts +31 -0
  491. package/src/facts/measures.ts +113 -25
  492. package/src/facts/retention-periods.ts +216 -0
  493. package/src/facts/unit-conflicts.ts +22 -9
  494. package/src/facts/unit-equivalents.ts +70 -0
  495. package/src/facts/unit-tolerance.ts +17 -0
  496. package/src/facts/unit-word.ts +35 -0
  497. package/src/facts/written-precision.ts +23 -0
  498. package/src/kana-spelling.ts +25 -0
  499. package/src/model-codes.ts +104 -0
  500. package/src/name-spelling-chars.ts +34 -0
  501. package/src/name-variants.ts +8 -7
  502. package/src/phone-numbers.ts +26 -6
  503. package/src/place-names.ts +53 -14
  504. package/src/plugin.ts +2 -0
  505. package/src/preamble-paragraphs.ts +21 -2
  506. package/src/profile/parse.ts +10 -0
  507. package/src/spacing-code.ts +26 -0
  508. package/src/structure/amount-range.ts +16 -7
  509. package/src/structure/capacity-count.ts +196 -0
  510. package/src/structure/counted-amounts.ts +5 -4
  511. package/src/structure/date-outside-period.ts +11 -6
  512. package/src/structure/date-range.ts +1 -1
  513. package/src/structure/deadline-after-event.ts +67 -0
  514. package/src/structure/definition-use.ts +59 -2
  515. package/src/structure/due-date.ts +8 -32
  516. package/src/structure/labelled-date-order.ts +84 -0
  517. package/src/structure/layout-codes.ts +210 -0
  518. package/src/structure/measured-amounts.ts +138 -0
  519. package/src/structure/quantity-range.ts +54 -0
  520. package/src/structure/range-bands.ts +316 -0
  521. package/src/structure/rent-multiple.ts +277 -0
  522. package/src/structure/scaled-quantities.ts +225 -0
  523. package/src/structure/stamp-order.ts +100 -0
  524. package/src/structure/step-times.ts +169 -0
  525. package/src/structure/term-deadline.ts +35 -0
  526. package/src/structure/time-range.ts +64 -0
  527. package/src/structure/total-line.ts +63 -0
  528. package/src/structure/total.ts +41 -6
  529. package/src/structure/unlisted-items.ts +207 -0
  530. package/src/structure/walk-time.ts +285 -0
@@ -0,0 +1,17 @@
1
+ import type { Tolerance } from "./measures.ts";
2
+
3
+ /** 丸めた書き方(1 mile と 1.6 km、300 kPa と 44 psi)を合うとみなす差。大きいほうの値の 2%。 */
4
+ const ROUNDING_SHARE = 0.02;
5
+
6
+ /**
7
+ * 温度は割合では比べられない(0 ℃ の 2% は 0)。オーブンの換算表は 25 °F 刻みの目盛りに丸める(170 ℃ と 325 °F は 13 °F 違う)ので、
8
+ * 一目盛りより小さい 15 °F までの差は合うとみなす。
9
+ */
10
+ const OVEN_ROUNDING_FAHRENHEIT = 15;
11
+ const CELSIUS_PER_FAHRENHEIT_DEGREE = 5 / 9;
12
+
13
+ const DEFAULT_TOLERANCE: Tolerance = { relative: ROUNDING_SHARE, absolute: 0 };
14
+ const TEMPERATURE_TOLERANCE: Tolerance = { relative: 0, absolute: OVEN_ROUNDING_FAHRENHEIT * CELSIUS_PER_FAHRENHEIT_DEGREE };
15
+
16
+ /** 量の種類(語彙表の id)ごとの、換算して合うとみなす差。 */
17
+ export const toleranceOf = (dimension: string): Tolerance => (dimension === "unit-temperature" ? TEMPERATURE_TOLERANCE : DEFAULT_TOLERANCE);
@@ -0,0 +1,35 @@
1
+ const LATIN_WORD = /^\p{Script=Latin}+$/u;
2
+ const WORD_CHAR = /[\p{L}\p{N}]/u;
3
+ /** 単位のすぐ後ろに、数(空白一つまで)か、ハイフンでつながる語が続く。 */
4
+ const CONTINUED = /^(?:[ \t]?[0-90-9]|-[\p{L}\p{N}])/u;
5
+
6
+ const isLatinWord = (word: string): boolean => LATIN_WORD.test(word);
7
+
8
+ /** 英字の語が、語として行にあるか("x" は "box" の中では数えない)。 */
9
+ const hasWord = (line: string, word: string): boolean => {
10
+ let from = line.indexOf(word);
11
+ while (from !== -1) {
12
+ const end = from + word.length;
13
+ if (!WORD_CHAR.test(line.charAt(from - 1)) && !WORD_CHAR.test(line.charAt(end))) return true;
14
+ from = line.indexOf(word, from + 1);
15
+ }
16
+ return false;
17
+ };
18
+
19
+ /** 単位に要る文脈の語が、行にどれかあるか。英字の語は語として探し、ほかの字(オーブン、×)は字の並びとして探す。大文字と小文字は区別しない。 */
20
+ export const lineHasContext = (line: string, words: readonly string[]): boolean => {
21
+ const lowered = line.toLowerCase();
22
+ return words.some((word) => {
23
+ const target = word.toLowerCase();
24
+ return isLatinWord(target) ? hasWord(lowered, target) : lowered.includes(target);
25
+ });
26
+ };
27
+
28
+ /**
29
+ * 文脈の要る英字の単位("in")は、その言語のふつうの語でもある。単位として読むのは、語がそこで終わり("5 interns" の in ではない)、
30
+ * 後ろに数もハイフンでつながる語も続かない("1 in 3"、"2-in-1")ときだけ。文脈の要らない単位と、英字でない単位(度)は、この形を見ない。
31
+ */
32
+ export const standsAsUnitWord = (pattern: string, needsContext: boolean, after: string): boolean => {
33
+ if (!needsContext || !isLatinWord(pattern)) return true;
34
+ return !WORD_CHAR.test(after.charAt(0)) && !CONTINUED.test(after);
35
+ };
@@ -0,0 +1,23 @@
1
+ import type { Tolerance } from "./measures.ts";
2
+
3
+ const HALF = 0.5;
4
+ const DECIMAL_BASE = 10;
5
+ const DECIMALS = /[..]([0-90-9]+)/u;
6
+ /** 端の値(1.2 kg と 1,250 g)が浮動小数の誤差で外れないための余り。 */
7
+ const FLOAT_SLACK = 1 + 1e-9;
8
+
9
+ /**
10
+ * 小数で書いた量が言える幅の半分を、基準の単位で(1.2 kg は 1.15〜1.25 kg なので 0.05 kg)。書き手は小数点の後ろの最後の桁で丸めて
11
+ * 書いている。整数(410 g、1 mile)は 0:末尾の 0 が丸めか(1,200 g)は書き方から分からず、仕様の整数はふつう書いたとおりの値なので、
12
+ * 量の種類ごとの差(unit-tolerance)だけで比べる。
13
+ */
14
+ export const writtenHalfStep = (written: string, factors: readonly number[]): number => {
15
+ const decimals = DECIMALS.exec(written)?.[1]?.length;
16
+ return decimals === undefined || factors.length === 0 ? 0 : HALF * DECIMAL_BASE ** -decimals * Math.max(...factors) * FLOAT_SLACK;
17
+ };
18
+
19
+ /** 二つの量を比べるときの、合うとみなす差。粗いほうの量の書いた桁の半分が、量の種類ごとの差より広ければそちら。 */
20
+ export const precisionTolerance = (tolerance: Tolerance, halfSteps: readonly number[]): Tolerance => ({
21
+ relative: tolerance.relative,
22
+ absolute: Math.max(tolerance.absolute, ...halfSteps),
23
+ });
@@ -22,6 +22,31 @@ export const kanjiSkeleton = (text: string): string => [...text.matchAll(KANJI)]
22
22
  export const dropsOkurigana = (spelling: string, spellings: readonly string[]): boolean =>
23
23
  spelling !== "" && kanjiSkeleton(spelling) === spelling && spellings.some((other) => other !== spelling && kanjiSkeleton(other) === spelling);
24
24
 
25
+ const KANJI_OR_KANA_RUN = /[\p{Script=Han}々]|[^\p{Script=Han}々]+/gu;
26
+
27
+ /**
28
+ * Whether a spelling writes in kana part of the word another spells with more kanji (かたくり粉 beside 片栗粉, とり扱い beside
29
+ * 取り扱い): every kanji it keeps stands where the other has it, each kana run stands for something there, and the other keeps
30
+ * more kanji. Only the letters are compared; the caller has already matched the readings.
31
+ */
32
+ export const isKanaForKanji = (partial: string, full: string): boolean => {
33
+ const kept = kanjiSkeleton(partial);
34
+ if (kept === "" || kept === partial || [...kanjiSkeleton(full)].length <= [...kept].length) return false;
35
+ const pattern = [...partial.matchAll(KANJI_OR_KANA_RUN)].map(([part]) => (kanjiSkeleton(part) === part ? part : ".+")).join("");
36
+ return new RegExp(`^${pattern}$`, "u").test(full);
37
+ };
38
+
39
+ /**
40
+ * The kanji a spelling is keyed by among spellings of one reading: its own, or, when it writes part of the word in kana
41
+ * (かたくり粉), the kanji of the spelling with more kanji it stands for (片栗粉). When it could stand for two (two words of one
42
+ * reading), its own.
43
+ */
44
+ export const kanjiKeyAmong = (spelling: string, spellings: readonly string[]): string => {
45
+ const fuller = new Set(spellings.filter((other) => isKanaForKanji(spelling, other)).map(kanjiSkeleton));
46
+ const [only] = fuller;
47
+ return fuller.size === 1 && only !== undefined ? only : kanjiSkeleton(spelling);
48
+ };
49
+
25
50
  /** The length, in UTF-16 units, of the start both strings share, compared character by character (𠮟 is one character, two units). */
26
51
  const sharedPrefixLength = (left: string, right: string): number => {
27
52
  const [leftChars, rightChars] = [[...left], [...right]];
@@ -0,0 +1,104 @@
1
+ // 型番の書き分け(KM-SP300 と KM-SP-300、KMSP300、km-sp300)。型番と読むのは、文書の題か型番の欄(語彙表 model-code-label)の後ろに
2
+ // 書いた、英大文字と数字を合わせた語だけ。ハイフン・空白・大小・幅を除いて同じ字の並びになるものを同じ型番と見る。字や数の違う
3
+ // 型番(KM-SP300 と KM-SP310)は別の型番なので比べない。
4
+
5
+ export type ModelCode = { readonly offset: number; readonly surface: string };
6
+ export type ModelCodeVariant = { readonly code: ModelCode; readonly usual: string };
7
+
8
+ /** 型番と読む字の数(ハイフンと空白を除く)の下限。S3 や v2 は短すぎて型番か分からない。 */
9
+ const MIN_KEY_LENGTH = 4;
10
+
11
+ const ALNUM = "A-Za-z0-9A-Za-z0-9";
12
+ const HYPHENS = "\\-‐‑-";
13
+ /** 型番の字の間に書いてよい区切り。 */
14
+ const SEPARATOR = `[${HYPHENS} ]?`;
15
+ /** 型番の前に来れば、より長い語・パス・URL・ファイル名・色(#FF0000)の一部。 */
16
+ const BEFORE_OTHER = new RegExp(`[${ALNUM}${HYPHENS}_./\\\\@=#]$`, "u");
17
+ /** 型番の後ろに来れば、より長い語・パス・ファイル名(.pdf)・版(.2)の一部。 */
18
+ const AFTER_OTHER = new RegExp(`^(?:[${ALNUM}${HYPHENS}_/\\\\]|\\.[${ALNUM}])`, "u");
19
+ const CODE_SHAPE = new RegExp(`[${ALNUM}]+(?:[${HYPHENS}][${ALNUM}]+)*`, "gu");
20
+ const URL = /(?:https?:\/\/|www\.)[^\s<>"'))」]+/gu;
21
+
22
+ /** ハイフン・空白・大小・幅を除いた字の並び。同じ並びなら同じ型番。 */
23
+ export const modelCodeKey = (surface: string): string =>
24
+ surface
25
+ .normalize("NFKC")
26
+ .toLowerCase()
27
+ .replaceAll(/[\s\-‐‑]/gu, "");
28
+
29
+ /** 英大文字と数字を両方含み、英小文字を含まない、短すぎない語。iOS17 や v1 は型番と読まない。 */
30
+ export const isModelCodeShape = (surface: string): boolean => {
31
+ const key = modelCodeKey(surface);
32
+ const ascii = surface.normalize("NFKC");
33
+ return key.length >= MIN_KEY_LENGTH && /[A-Z]/u.test(ascii) && /\d/u.test(ascii) && !/[a-z]/u.test(ascii);
34
+ };
35
+
36
+ const standsAlone = (text: string, start: number, end: number): boolean =>
37
+ !BEFORE_OTHER.test(text.slice(Math.max(0, start - 1), start)) && !AFTER_OTHER.test(text.slice(end, end + 2));
38
+
39
+ /** 範囲の中の、型番の形をした語。 */
40
+ export const codesInTitle = (text: string, start: number, end: number): ModelCode[] =>
41
+ [...text.slice(start, end).matchAll(CODE_SHAPE)]
42
+ .map((match) => ({ offset: start + match.index, surface: match[0] }))
43
+ .filter((code) => isModelCodeShape(code.surface) && standsAlone(text, code.offset, code.offset + code.surface.length));
44
+
45
+ const escaped = (pattern: string): string => pattern.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
46
+
47
+ /** 欄の語の直後(「:」や空白を挟んでよい)に書いた、型番の形をした語。英字の欄の語は大小を問わず、語の途中からは読まない。 */
48
+ export const codesAfterLabels = (text: string, labels: readonly string[]): ModelCode[] => {
49
+ if (labels.length === 0) return [];
50
+ const alternatives = labels.toSorted((left, right) => right.length - left.length).map(escaped);
51
+ const pattern = new RegExp(
52
+ `(?<![A-Za-z])(?:${alternatives.join("|")})(?![A-Za-z])[ \\t]*[::]?[ \\t\\u3000]*([${ALNUM}]+(?:[${HYPHENS}][${ALNUM}]+)*)`,
53
+ "giu",
54
+ );
55
+ return [...text.matchAll(pattern)]
56
+ .map((match) => ({ offset: match.index + match[0].length - (match[1] ?? "").length, surface: match[1] ?? "" }))
57
+ .filter((code) => isModelCodeShape(code.surface) && standsAlone(text, code.offset, code.offset + code.surface.length));
58
+ };
59
+
60
+ /** 一つの字の、大小と全角の形。 */
61
+ const charClass = (char: string): string => {
62
+ const forms = new Set([char, char.toUpperCase(), char.toLowerCase()].flatMap((form) => [form, String.fromCodePoint((form.codePointAt(0) ?? 0) + 0xfee0)]));
63
+ return `[${[...forms].join("")}]`;
64
+ };
65
+
66
+ const urlSpans = (text: string): { start: number; end: number }[] =>
67
+ [...text.matchAll(URL)].map((match) => ({ start: match.index, end: match.index + match[0].length }));
68
+
69
+ /** 字の並びが key と同じになる語のすべて。字の間にはハイフンか空白を一つまで挟んでよい。URL の中は読まない。 */
70
+ export const codesWithKey = (text: string, key: string): ModelCode[] => {
71
+ const pattern = new RegExp([...key].map(charClass).join(SEPARATOR), "gu");
72
+ const urls = urlSpans(text);
73
+ return [...text.matchAll(pattern)]
74
+ .map((match) => ({ offset: match.index, surface: match[0] }))
75
+ .filter((code) => standsAlone(text, code.offset, code.offset + code.surface.length))
76
+ .filter((code) => !urls.some((url) => url.start <= code.offset && code.offset < url.end));
77
+ };
78
+
79
+ const countsOf = (codes: readonly ModelCode[]): Map<string, number> =>
80
+ codes.reduce((counts, code) => counts.set(code.surface, (counts.get(code.surface) ?? 0) + 1), new Map<string, number>());
81
+
82
+ /** 一番多く書いた形。数が同じなら、題か欄に先に書いた形。 */
83
+ const usualOf = (codes: readonly ModelCode[], anchor: string): string => {
84
+ const counts = countsOf(codes);
85
+ const most = Math.max(...counts.values());
86
+ return (counts.get(anchor) ?? 0) === most ? anchor : ([...counts].find(([, count]) => count === most)?.[0] ?? anchor);
87
+ };
88
+
89
+ /** 一つの型番の、少ないほうの形。型番が二度以上出てこなければ、比べるものが無い。 */
90
+ const variantsOfKey = (text: string, anchor: ModelCode): ModelCodeVariant[] => {
91
+ const codes = codesWithKey(text, modelCodeKey(anchor.surface));
92
+ if (codes.length < 2) return [];
93
+ const usual = usualOf(codes, anchor.surface);
94
+ return codes.filter((code) => code.surface !== usual).map((code) => ({ code, usual }));
95
+ };
96
+
97
+ /**
98
+ * 文書の型番(題か欄に書いたもの)を、ほかの所で別の形に書いた所。anchors は文書の順。同じ字の並びの型番は一度だけ見る。
99
+ */
100
+ export const modelCodeVariants = (text: string, anchors: readonly ModelCode[]): ModelCodeVariant[] => {
101
+ const keys = anchors.map((anchor) => modelCodeKey(anchor.surface));
102
+ const firstPerKey = anchors.filter((_, index) => keys.indexOf(keys[index] ?? "") === index);
103
+ return firstPerKey.flatMap((anchor) => variantsOfKey(text, anchor)).toSorted((left, right) => left.code.offset - right.code.offset);
104
+ };
@@ -0,0 +1,34 @@
1
+ import type { NameMention } from "./name-variants.ts";
2
+
3
+ // 一語の名前の中で、同じ音を二通りに書く字(桜ヶ丘・桜ケ丘・桜が丘、霞ヶ関・霞が関)。どの字が同じかは語彙表 name-spelling-char が
4
+ // 組(group)ごとに言う。漢字に挟まれた字だけを寄せる。かなの語の中の字は寄せない。名前は解析器が一語と読んだもの
5
+ // (霞が関)に限る。桜・が・丘 と読む書き方を本文にそのまま探すと、助詞の が(桜が丘を彩る)を名前と取り違える。
6
+
7
+ /** 字から、その組の代表の字へ。 */
8
+ export type SpellingChars = ReadonlyMap<string, string>;
9
+
10
+ /**
11
+ * 名前の中の同じ音の字を比べる材料。chars はその字の組。alsoWritten は、この比べ方にだけ加える名前の現れ(表の升の名前)。升の名前をほかの見方に加えると、表の識別子(contentType)を本文の名前(Content-Type)と比べてしまう。
12
+ */
13
+ export type SpellingInput = { readonly chars: SpellingChars; readonly alsoWritten: readonly NameMention[] };
14
+
15
+ export const NO_SPELLING: SpellingInput = { chars: new Map(), alsoWritten: [] };
16
+
17
+ const HAN = /^\p{Script=Han}$/u;
18
+
19
+ const isBetweenHan = (chars: readonly string[], index: number): boolean => HAN.test(chars[index - 1] ?? "") && HAN.test(chars[index + 1] ?? "");
20
+
21
+ /** 漢字に挟まれた、組のある字の位置。 */
22
+ const foldableAt = (chars: readonly string[], spelling: SpellingChars): number[] =>
23
+ chars.flatMap((char, index) => (spelling.has(char) && isBetweenHan(chars, index) ? [index] : []));
24
+
25
+ /** 漢字に挟まれた組の字を、組の代表に寄せた形。寄せる字が無ければ書いたまま。 */
26
+ export const foldSpelling = (surface: string, spelling: SpellingChars): string => {
27
+ const chars = [...surface];
28
+ const at = new Set(foldableAt(chars, spelling));
29
+ return chars.map((char, index) => (at.has(index) ? (spelling.get(char) ?? char) : char)).join("");
30
+ };
31
+
32
+ /** 比べる形。漢字に挟まれた組の字を含む名前だけが、寄せた形を一つ持つ。含まない名前(Content-Type)はこの比べ方に入らない。 */
33
+ export const foldedKeysOf = (surface: string, spelling: SpellingChars): string[] =>
34
+ foldableAt([...surface], spelling).length > 0 ? [foldSpelling(surface, spelling)] : [];
@@ -2,6 +2,7 @@ import type { Token } from "./plugin.ts";
2
2
  import { escapeRegExp } from "./orthography.ts";
3
3
  import { nameCueAt, type NameCue, type NameCues } from "./name-cue.ts";
4
4
  import { isNearSurname } from "./surname-near.ts";
5
+ import { foldedKeysOf, NO_SPELLING, type SpellingInput } from "./name-spelling-chars.ts";
5
6
 
6
7
  // 同じ名前(人・会社・製品)を、文書の中で少しだけ違う形に書いた所。どの形が正しいかは決めず、少ないほうを指す。
7
8
  // 三通りで同じ名前と見る。書き方の違いだけ(GitHub と Github、Mac OS と macOS)、読みが同じ(山田太郎 と 山田太朗)、
@@ -23,7 +24,7 @@ export type NameMention = {
23
24
  export type NameVariant = {
24
25
  readonly mention: NameMention;
25
26
  readonly usual: string;
26
- readonly kind: "spelling" | "reading" | "near" | "surname" | "character";
27
+ readonly kind: "spelling" | "kana" | "reading" | "near" | "surname" | "character";
27
28
  };
28
29
 
29
30
  /** 字体の違う同じ字(斎・斉・齋)。字から、その組の代表の字へ。 */
@@ -405,13 +406,17 @@ const personReadingVariants = (tallies: readonly Tally[]): NameVariant[] =>
405
406
  * 同じ名前の、少ないほうの書き方。書き方ごとに最初の現れを一つ。同じ現れを二つの見方が言えば、先の見方だけ。chars は字体の
406
407
  * 違う同じ字の組(語彙表 name-variant-char)。
407
408
  */
408
- export const nameVariants = (mentions: readonly NameMention[], chars: VariantChars = new Map()): NameVariant[] => {
409
+ export const nameVariants = (mentions: readonly NameMention[], chars: VariantChars = new Map(), spelling: SpellingInput = NO_SPELLING): NameVariant[] => {
409
410
  const tallies = talliesOf(mentions);
410
411
  const found = [
411
412
  ...variantsIn(
412
413
  groupBy(tallies, (tally) => [nameKey(tally.surface)]),
413
414
  "spelling",
414
415
  ),
416
+ ...variantsIn(
417
+ groupBy(talliesOf([...mentions, ...spelling.alsoWritten]), (tally) => foldedKeysOf(tally.surface, spelling.chars).map(nameKey)),
418
+ "kana",
419
+ ),
415
420
  ...variantsIn(
416
421
  groupBy(tallies, (tally) => (tally.first.reading === undefined ? [] : [tally.first.reading])),
417
422
  "reading",
@@ -424,10 +429,6 @@ export const nameVariants = (mentions: readonly NameMention[], chars: VariantCha
424
429
  ];
425
430
  const reported = new Set<string>();
426
431
  return found
427
- .filter((variant) => {
428
- if (reported.has(variant.mention.surface)) return false;
429
- reported.add(variant.mention.surface);
430
- return true;
431
- })
432
+ .filter((variant) => !reported.has(variant.mention.surface) && reported.add(variant.mention.surface).size > 0)
432
433
  .toSorted((left, right) => left.mention.offset - right.mention.offset);
433
434
  };
@@ -20,6 +20,18 @@ const NUMBER_EDGE_AFTER = String.raw`(?![\d\-‐−]|[.,]\d)`;
20
20
  const COUNTRY_CODE = String.raw`(?:\+\d{1,3}[ -]?)?`;
21
21
  /** Groups of digits joined by hyphens or brackets (03-1234-5678, 03(1234)5678, (555) 123-4567, +81-3-1234-5678). */
22
22
  const NUMBER_SHAPE = new RegExp(String.raw`${NUMBER_EDGE_BEFORE}${COUNTRY_CODE}[((]?\d{1,5}(?:${GROUP_JOIN}\d{1,5}){1,3}${NUMBER_EDGE_AFTER}`, "gu");
23
+ /**
24
+ * Groups of digits joined by spaces (020 7946 0186, +44 20 7946 0186, 03 1234 5678). The last group has three digits or
25
+ * more and no such group follows it, so a count after the number ("0186 24 hours") is left out and a run that does not
26
+ * split one way only ("2026 10 09 020 7946 0186") is not read.
27
+ */
28
+ const SPACED_SHAPE = new RegExp(
29
+ String.raw`${NUMBER_EDGE_BEFORE}(?:\+\d{1,3}[ \u00A0])?\d{1,5}(?:[ \u00A0]\d{1,5}){0,2}[ \u00A0]\d{3,5}${NUMBER_EDGE_AFTER}(?![ \u00A0]\d{3})`,
30
+ "gu",
31
+ );
32
+ /** Spaced digits are a phone number only with a country code, or right after their label: dates, amounts and ids are spaced too. */
33
+ const COUNTRY_CODE_START = /^\+/u;
34
+ const LABEL_TO_NUMBER = /^(?:[\s::..]|\uFE0F)*$/u;
23
35
  const NOT_DIGIT = /\D/gu;
24
36
  const LATIN_START = /^[A-Za-z]/u;
25
37
  const LETTER = /\p{L}/u;
@@ -35,8 +47,10 @@ const lastPlace = (before: string, label: string): number => {
35
47
  return whole.at(-1) ?? -1;
36
48
  };
37
49
 
38
- /** The group of the label ending nearest before a number on its line, the longest when two end together (携帯電話 over 電話). */
39
- const labelBefore = (text: string, offset: number, labels: readonly LexiconEntry[]): string | undefined => {
50
+ type LabelBefore = { readonly group: string; readonly between: string };
51
+
52
+ /** The label ending nearest before a number on its line, the longest when two end together (携帯電話 over 電話), and what lies between. */
53
+ const labelBefore = (text: string, offset: number, labels: readonly LexiconEntry[]): LabelBefore | undefined => {
40
54
  const lineStart = text.lastIndexOf("\n", offset - 1) + 1;
41
55
  const before = text.slice(lineStart, offset).toLowerCase();
42
56
  const nearest = labels
@@ -46,7 +60,7 @@ const labelBefore = (text: string, offset: number, labels: readonly LexiconEntry
46
60
  })
47
61
  .filter((hit) => hit.at >= 0 && before.length - hit.end <= MAX_LABEL_GAP)
48
62
  .toSorted((a, b) => b.end - a.end || a.at - b.at)[0];
49
- return nearest?.group;
63
+ return nearest === undefined ? undefined : { group: nearest.group, between: before.slice(nearest.end) };
50
64
  };
51
65
 
52
66
  /** URLs covered with spaces, so neither a path segment (/call/) nor an id in one is read. Offsets stay the same. */
@@ -55,12 +69,18 @@ const withoutUrls = (text: string): string => text.replaceAll(URL, (url) => " ".
55
69
  /** The phone numbers of a text that have a label shortly before them on their line; unlabelled digits and URLs are not read. */
56
70
  export const labelledPhoneNumbers = (source: string, labels: readonly LexiconEntry[]): PhoneNumber[] => {
57
71
  const text = withoutUrls(source);
58
- return [...text.matchAll(NUMBER_SHAPE)].flatMap((match) => {
72
+ const read = (match: RegExpExecArray, spaced: boolean): PhoneNumber[] => {
59
73
  const digits = match[0].replaceAll(NOT_DIGIT, "");
60
74
  if (digits.length < MIN_DIGITS || digits.length > MAX_DIGITS) return [];
61
75
  const label = labelBefore(text, match.index, labels);
62
- return label === undefined ? [] : [{ offset: match.index, written: match[0].trim(), digits, label }];
63
- });
76
+ if (label === undefined) return [];
77
+ if (spaced && !COUNTRY_CODE_START.test(match[0]) && !LABEL_TO_NUMBER.test(label.between)) return [];
78
+ return [{ offset: match.index, written: match[0].trim(), digits, label: label.group }];
79
+ };
80
+ return [
81
+ ...[...text.matchAll(NUMBER_SHAPE)].flatMap((match) => read(match, false)),
82
+ ...[...text.matchAll(SPACED_SHAPE)].flatMap((match) => read(match, true)),
83
+ ].toSorted((a, b) => a.offset - b.offset);
64
84
  };
65
85
 
66
86
  /** Whether two digit strings are the same but for one pair of neighbours swapped. */
@@ -165,14 +165,53 @@ export const placeMentionsIn = (source: string, words: readonly PlaceWord[], rea
165
165
  };
166
166
 
167
167
  const DIGIT = /\p{N}/u;
168
+ const HAN = /^\p{Script=Han}$/u;
168
169
 
169
- /** 漢字・かなの名前の部分が一字だけ違い、その字が、多いほうで固有名詞の語に入る(八重洲 と 八重州)。数字や方角(北口 と 南口)は別の所。 */
170
- const isCjkSlip = (slip: PlaceMention, usual: PlaceMention): boolean => {
170
+ /**
171
+ * 名前の字について語彙表が言うこと。directions は方角や位置の字(東、上、新)で、それだけが違う名前は別の所(下北沢 と 上北沢)。
172
+ * sameReading は読みの同じ字から組の代表へ(洲 と 州)。
173
+ */
174
+ export type PlaceChars = { readonly directions: ReadonlySet<string>; readonly sameReading: ReadonlyMap<string, string> };
175
+
176
+ const NO_PLACE_CHARS: PlaceChars = { directions: new Set(), sameReading: new Map() };
177
+
178
+ type CharDifference = { readonly at: number; readonly slip: string; readonly usual: string };
179
+
180
+ /** 漢字・かなの名前の部分が、字数が同じで一字だけ違うときの、その字。 */
181
+ const oneCharDifference = (slip: PlaceMention, usual: PlaceMention): CharDifference | undefined => {
171
182
  const [slipChars, usualChars] = [[...slip.base], [...usual.base]];
172
- if (slipChars.length !== usualChars.length) return false;
183
+ if (slipChars.length !== usualChars.length) return undefined;
173
184
  const differ = usualChars.flatMap((char, index) => (char === slipChars[index] ? [] : [index]));
174
185
  const [at] = differ;
175
- return differ.length === 1 && at !== undefined && usual.properAt.includes(at) && !DIGIT.test(usualChars[at] ?? "") && !DIGIT.test(slipChars[at] ?? "");
186
+ return differ.length === 1 && at !== undefined ? { at, slip: slipChars[at] ?? "", usual: usualChars[at] ?? "" } : undefined;
187
+ };
188
+
189
+ const isDirectionDifference = (difference: CharDifference | undefined, chars: PlaceChars): boolean =>
190
+ difference !== undefined && (chars.directions.has(difference.slip) || chars.directions.has(difference.usual));
191
+
192
+ /** 漢字どうしの一字違いは、読みの同じ字の組のときだけ(八重洲 と 八重州)。読みの違う字(戸塚 と 戸山)は別の所のことが多い。 */
193
+ const isReadAlike = (difference: CharDifference, chars: PlaceChars): boolean =>
194
+ !HAN.test(difference.slip) || !HAN.test(difference.usual) || isSameGroup(difference, chars);
195
+
196
+ const isSameGroup = (difference: CharDifference, chars: PlaceChars): boolean => {
197
+ const group = chars.sameReading.get(difference.slip);
198
+ return group !== undefined && group === chars.sameReading.get(difference.usual);
199
+ };
200
+
201
+ /** 同じ数を、漢数字と数字で書き分けた一字(第二 と 第2)。どちらの字が同じ数を言うかは語彙表の組が言う。 */
202
+ const isNumeralSpelling = (difference: CharDifference | undefined, chars: PlaceChars): boolean =>
203
+ difference !== undefined && (DIGIT.test(difference.slip) || DIGIT.test(difference.usual)) && isSameGroup(difference, chars);
204
+
205
+ /** 漢字・かなの名前の部分が一字だけ違い、その字が、多いほうで固有名詞の語に入る(八重洲 と 八重州)。数字は別の所。 */
206
+ const isCjkSlip = (slip: PlaceMention, usual: PlaceMention, chars: PlaceChars): boolean => {
207
+ const difference = oneCharDifference(slip, usual);
208
+ return (
209
+ difference !== undefined &&
210
+ usual.properAt.includes(difference.at) &&
211
+ !DIGIT.test(difference.usual) &&
212
+ !DIGIT.test(difference.slip) &&
213
+ isReadAlike(difference, chars)
214
+ );
176
215
  };
177
216
 
178
217
  const latinWords = (base: string): string[] =>
@@ -208,19 +247,19 @@ const isSlipOf = (slip: Counted, usual: Counted): boolean => slip.count === 1 &&
208
247
 
209
248
  /**
210
249
  * 二つの書き方が、同じ所を二通りに書いたものか。場所の語の組が同じで、名前と語の全体が記号・幅・大小だけ違うか、場所の語の
211
- * 書き方だけが違う(11th St. と 11th Street)か。名前の部分の読みが同じか、かなで書いた読み(筑紫口 と ちくし口)か、一字違い
212
- * (八重洲 と 八重州)なのは、
213
- * 少ないほうが一度だけ、多いほうが二度以上のときに限る。
250
+ * 書き方だけが違う(11th St. と 11th Street)か。名前の部分の読みが同じか、同じ数の漢数字と数字(第二 と 第2)か、かなで書いた読み(筑紫口 と ちくし口)か、一字違い
251
+ * (八重洲 と 八重州)なのは、少ないほうが一度だけ、多いほうが二度以上のときに限る。違う一字が方角や位置の字なら別の所。
214
252
  */
215
- export const placeRelation = (slip: Counted, usual: Counted): PlaceRelation | undefined => {
253
+ export const placeRelation = (slip: Counted, usual: Counted, chars: PlaceChars = NO_PLACE_CHARS): PlaceRelation | undefined => {
216
254
  const [left, right] = [slip.mention, usual.mention];
217
255
  if (left.surface === right.surface || left.place !== right.place) return undefined;
218
256
  if (nameKey(left.surface) === nameKey(right.surface)) return "spelling";
219
257
  if (nameKey(left.base) === nameKey(right.base)) return "place-word";
220
- if (!isSlipOf(slip, usual)) return undefined;
221
- if ((left.reading !== undefined && left.reading === right.reading) || isKanaSpellingOf(left, right)) return "reading";
258
+ const difference = oneCharDifference(left, right);
259
+ if (!isSlipOf(slip, usual) || isDirectionDifference(difference, chars)) return undefined;
260
+ if ((left.reading !== undefined && left.reading === right.reading) || isKanaSpellingOf(left, right) || isNumeralSpelling(difference, chars)) return "reading";
222
261
  const latin = LATIN.test(left.base) && LATIN.test(right.base);
223
- const slipped = latin ? isLatinSlip(left, right) : isCjkSlip(left, right);
262
+ const slipped = latin ? isLatinSlip(left, right) : isCjkSlip(left, right, chars);
224
263
  return slipped ? "near" : undefined;
225
264
  };
226
265
 
@@ -237,12 +276,12 @@ const talliesOf = (mentions: readonly PlaceMention[]): Counted[] => {
237
276
  const byUsage = (left: Counted, right: Counted): number => right.count - left.count || left.mention.offset - right.mention.offset;
238
277
 
239
278
  /** 同じ所の、少ないほうの書き方。同じ所と言える相手のうち一番多いものと比べる。書き方ごとに最初の現れを一つ。 */
240
- export const placeVariants = (mentions: readonly PlaceMention[]): PlaceVariant[] => {
279
+ export const placeVariants = (mentions: readonly PlaceMention[], chars: PlaceChars = NO_PLACE_CHARS): PlaceVariant[] => {
241
280
  const tallies = talliesOf(mentions);
242
281
  return tallies.flatMap((tally) => {
243
- const [usual] = tallies.filter((other) => other !== tally && placeRelation(tally, other) !== undefined).toSorted(byUsage);
282
+ const [usual] = tallies.filter((other) => other !== tally && placeRelation(tally, other, chars) !== undefined).toSorted(byUsage);
244
283
  if (usual === undefined || byUsage(usual, tally) > 0) return [];
245
- const kind = placeRelation(tally, usual);
284
+ const kind = placeRelation(tally, usual, chars);
246
285
  return kind === undefined ? [] : [{ mention: tally.mention, usual: usual.mention.surface, kind }];
247
286
  });
248
287
  };
package/src/plugin.ts CHANGED
@@ -277,6 +277,8 @@ export type DocumentProfile = {
277
277
  readonly unnumbered?: { readonly indent: string; readonly inside: string; readonly depth: number } | undefined;
278
278
  /** 定義した語を括弧に入れて、語そのものを指す(「株主」とあるのは、"controller" has the same meaning)。定義し直しでも、括弧の付け忘れでもない。 */
279
279
  readonly quoteMentionsTerm?: boolean | undefined;
280
+ /** 文書に速さが無いときの、徒歩の分数を出す速さ(m/分)と、端数を切り上げるか。不動産の広告は 80m で1分、切り上げ。 */
281
+ readonly walkRate?: { readonly metresPerMinute: number; readonly roundUp: boolean } | undefined;
280
282
  };
281
283
 
282
284
  /**
@@ -1,9 +1,28 @@
1
1
  import type { Paragraph } from "./plugin.ts";
2
2
  import { isClosed } from "./sentence-shape.ts";
3
3
 
4
+ /** A header field on one line: a short label, a colon, and its value (To: …, Quote number: …). */
5
+ const FIELD_LINE = /^[^::。.!?!?\n]{1,30}[::][ \t]*(?<value>\S[^\n]*)$/u;
6
+
7
+ /** Small words a name keeps in lower case (Bank of Minato, Smith and Sons). */
8
+ const NAME_JOINERS = new Set(["of", "and", "&", "the", "for"]);
9
+
10
+ const isNameWord = (word: string): boolean => NAME_JOINERS.has(word) || /^[\p{Lu}\d]/u.test(word);
11
+
12
+ /**
13
+ * A paragraph that is one header field whose value is a name, every word capitalised (To: Minato Manufacturing Inc.): a
14
+ * letter's or an invoice's address block, closed only by the period of Inc. A value with a word in lower case
15
+ * (Summary: Sales up 5%., Note: This guide covers the setup.) is a sentence.
16
+ */
17
+ const isNameField = (paragraph: Paragraph, source: string): boolean => {
18
+ const value = FIELD_LINE.exec(source.slice(paragraph.span.start, paragraph.span.end).trim())?.groups?.["value"];
19
+ return value !== undefined && value.split(/\s+/u).every(isNameWord);
20
+ };
21
+
4
22
  /**
5
23
  * 本題の前にあって、読み手が読み通す段落。文が一つも終止符で閉じない段落は数えない。
6
24
  * 分類の札(「エネルギー・環境」)、著者と所属の行、「印刷」、テンプレートの指示は文ではなく、読ませる前置きではない。
25
+ * 宛先や差出人の欄(To: Minato Manufacturing Inc.)も、値が名前だけなら数えない。source は段落の範囲を読む文書。
7
26
  */
8
- export const preambleParagraphs = (paragraphs: readonly Paragraph[], bodyStart: number): Paragraph[] =>
9
- paragraphs.filter((paragraph) => paragraph.span.start < bodyStart && paragraph.sentences.some(isClosed));
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));
@@ -53,6 +53,14 @@ const unnumberedOf = (value: unknown): DocumentProfile["unnumbered"] => {
53
53
  return indent === undefined || inside === undefined || !Number.isInteger(depth) || typeof depth !== "number" ? undefined : { indent, inside, depth };
54
54
  };
55
55
 
56
+ /** 正の速さが無ければ読まない。 */
57
+ const walkRateOf = (value: unknown): DocumentProfile["walkRate"] => {
58
+ if (!isRecord(value)) return undefined;
59
+ const metresPerMinute = value["metres_per_minute"];
60
+ if (typeof metresPerMinute !== "number" || !Number.isFinite(metresPerMinute) || metresPerMinute <= 0) return undefined;
61
+ return { metresPerMinute, roundUp: value["round_up"] === true };
62
+ };
63
+
56
64
  /** 単位が一つも無ければ読まない。何を指すのか決められない。 */
57
65
  const relativeOf = (value: unknown): RelativeVocabulary | undefined => {
58
66
  if (!isRecord(value)) return undefined;
@@ -88,6 +96,7 @@ const languagesOf = (id: string, raw: Record<string, unknown>): Record<string, D
88
96
  const caption = text(section["caption"]);
89
97
  const relative = relativeOf(section["relative"]);
90
98
  const unnumbered = unnumberedOf(section["unnumbered"]);
99
+ const walkRate = walkRateOf(section["walk_rate"]);
91
100
  return [
92
101
  language,
93
102
  {
@@ -99,6 +108,7 @@ const languagesOf = (id: string, raw: Record<string, unknown>): Record<string, D
99
108
  relative,
100
109
  unnumbered,
101
110
  ...(section["quote_mentions_term"] === true ? { quoteMentionsTerm: true } : {}),
111
+ ...(walkRate === undefined ? {} : { walkRate }),
102
112
  },
103
113
  ];
104
114
  }),
@@ -0,0 +1,26 @@
1
+ import type { Span } from "./plugin.ts";
2
+
3
+ // Text beside which a space is not a spacing choice: a clock time (13:30), which like 3時 names a time rather than a
4
+ // quantity, and an e-mail address or a URL, whose end a reader and an auto-linker find by the space after it. Pure.
5
+
6
+ /** A clock time: hours, a colon, minutes, and seconds optionally (9:05, 13:30, 08:00:15). */
7
+ const CLOCK_TIME = /(?<![\d::.])\d{1,2}[::][0-5]\d(?:[::][0-5]\d)?(?![\d::])/gu;
8
+
9
+ /** A run of printable ASCII without brackets: an address ends where Japanese text, a space or a bracket starts (…/faq で). */
10
+ const ASCII_RUN = /[!-'*-~]+/gu;
11
+
12
+ /** An e-mail address (a local part, @, and a domain with a dot) or a URL with its scheme or www. */
13
+ const EMAIL = /^[\w.+-]+@[\w-]+\.[\w.-]+$/u;
14
+ const URL_START = /^(?:https?:\/\/|www\.)/u;
15
+
16
+ const isAddress = (word: string): boolean => EMAIL.test(word) || URL_START.test(word);
17
+
18
+ const spansOf = (text: string, pattern: RegExp): Span[] =>
19
+ [...text.matchAll(pattern)].map((match) => ({ start: match.index, end: match.index + match[0].length }));
20
+
21
+ export const clockTimeSpans = (text: string): Span[] => spansOf(text, CLOCK_TIME);
22
+
23
+ export const addressSpans = (text: string): Span[] => spansOf(text, ASCII_RUN).filter((span) => isAddress(text.slice(span.start, span.end)));
24
+
25
+ /** Whether the character at `at` is inside one of the spans. */
26
+ export const isInsideSpan = (spans: readonly Span[], at: number): boolean => spans.some((span) => span.start <= at && at < span.end);
@@ -1,11 +1,11 @@
1
- // amount-range-reversed: a range of amounts whose upper end is below its lower end (月給30万円〜25万円, $90,000–$70,000,
2
- // 下限30万円・上限25万円). The amounts come read (currency, value, the word of scale they carry); the words that join a range,
3
- // change a price, and label a bound come from the lexicons. Pure.
1
+ // amount-range-reversed and quantity-range-reversed: a range whose upper end is below its lower end (月給30万円〜25万円,
2
+ // $90,000–$70,000, 下限30万円・上限25万円, 5–2 kg, -10〜-40℃). The ends come read (currency or unit, value, the word of scale
3
+ // they carry); the words that join a range, change a value, and label a bound come from the lexicons. Pure.
4
4
  import { escapeRegExp } from "../orthography.ts";
5
5
  import { amountValue, type ScaleWord } from "./amount-scale.ts";
6
6
  import type { StructureIssue } from "./issues.ts";
7
7
 
8
- /** One amount written with its currency. scale is the value of the word of scale written in it (万, million), if any. */
8
+ /** One amount written with its currency (or a quantity with its unit, in currency). scale is the value of the word of scale written in it (万, million), if any. */
9
9
  export type RangeAmount = {
10
10
  readonly offset: number;
11
11
  readonly end: number;
@@ -27,7 +27,7 @@ export type AmountRangeWords = {
27
27
  readonly lowers: readonly string[];
28
28
  readonly uppers: readonly string[];
29
29
  readonly links: readonly string[];
30
- /** The words of scale (万, million, k) and the digits of a number (the currency reader's pattern). */
30
+ /** The words of scale (万, million, k) and the digits of a number (the reader's pattern; a quantity's may open with a minus sign). */
31
31
  readonly scales: readonly ScaleWord[];
32
32
  readonly number: string;
33
33
  };
@@ -94,11 +94,20 @@ const alternation = (patterns: readonly string[]): string => patterns.toSorted((
94
94
 
95
95
  const scalePart = (words: AmountRangeWords): string => {
96
96
  const scales = alternation(words.scales.map((scale) => escapeRegExp(scale.word)));
97
- return scales === "" ? "" : `(?:\\s?(${scales}))?`;
97
+ // With no words of scale the group still stands, never matching, so the joint keeps its group number.
98
+ return scales === "" ? "((?!))?" : `(?:\\s?(${scales}))?`;
98
99
  };
99
100
 
100
101
  const jointPart = (words: AmountRangeWords): string => `(\\s?(?:${alternation([...words.connectors, ...words.openers].map(wordPattern))})\\s?)`;
101
102
 
103
+ const MINUS = /^[-−-]/u;
104
+
105
+ /** The value of a bare number, negative when it opens with a minus sign (-10〜-40℃). */
106
+ const signedValue = (digits: string): number | undefined => {
107
+ const value = amountValue(digits.replace(MINUS, ""), []);
108
+ return value === undefined || !MINUS.test(digits) ? value : -value;
109
+ };
110
+
102
111
  /** A bare number (no currency) and its word of scale, if any. */
103
112
  const bareReading = (
104
113
  span: { readonly offset: number; readonly end: number },
@@ -106,7 +115,7 @@ const bareReading = (
106
115
  scaleWord: string | undefined,
107
116
  words: AmountRangeWords,
108
117
  ): Reading | undefined => {
109
- const value = amountValue(digits, []);
118
+ const value = signedValue(digits);
110
119
  if (value === undefined) return undefined;
111
120
  const scale = scaleOf(scaleWord, words);
112
121
  return { ...span, value: scale === undefined ? value : value * scale, scale };