hyper-html-api 0.13.0 → 0.15.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 (449) hide show
  1. package/README.md +4 -1
  2. package/conformance/MANIFEST.json +2 -2
  3. package/conformance/cases/all-matches-read-quoted.expected.json +3 -0
  4. package/conformance/cases/all-matches-read-quoted.html +5 -0
  5. package/conformance/cases/all-matches-read-quoted.meta +7 -0
  6. package/conformance/cases/all-matches-read-quoted.parsed.json +3 -0
  7. package/conformance/cases/all-matches-read.expected.json +3 -0
  8. package/conformance/cases/all-matches-read.html +6 -0
  9. package/conformance/cases/all-matches-read.meta +7 -0
  10. package/conformance/cases/all-matches-read.parsed.json +3 -0
  11. package/conformance/cases/codec-form-nested.error.json +5 -0
  12. package/conformance/cases/codec-form-nested.html +4 -0
  13. package/conformance/cases/codec-form-nested.meta +6 -0
  14. package/conformance/cases/codec-form-nested.parsed.json +5 -0
  15. package/conformance/cases/codec-form-star.error.json +5 -0
  16. package/conformance/cases/codec-form-star.html +4 -0
  17. package/conformance/cases/codec-form-star.meta +6 -0
  18. package/conformance/cases/codec-form-star.parsed.json +3 -0
  19. package/conformance/cases/codec-form-under-v1.error.json +5 -0
  20. package/conformance/cases/codec-form-under-v1.html +4 -0
  21. package/conformance/cases/codec-form-under-v1.meta +6 -0
  22. package/conformance/cases/codec-form-under-v1.parsed.json +3 -0
  23. package/conformance/cases/codec-unknown-name.error.json +5 -0
  24. package/conformance/cases/codec-unknown-name.html +4 -0
  25. package/conformance/cases/codec-unknown-name.meta +6 -0
  26. package/conformance/cases/codec-unknown-name.parsed.json +3 -0
  27. package/conformance/cases/tag-version-2-plain.expected.json +7 -0
  28. package/conformance/cases/tag-version-2-plain.html +5 -0
  29. package/conformance/cases/tag-version-2-plain.meta +6 -0
  30. package/conformance/cases/tag-version-2-plain.parsed.json +4 -0
  31. package/conformance/cases/tag-wrong-version.error.json +1 -1
  32. package/conformance/cases/tag-wrong-version.html +1 -1
  33. package/conformance/cases/write-all-matches-attr.after.html +1 -0
  34. package/conformance/cases/write-all-matches-attr.after.json +3 -0
  35. package/conformance/cases/write-all-matches-attr.data.json +1 -0
  36. package/conformance/cases/write-all-matches-attr.html +1 -0
  37. package/conformance/cases/write-all-matches-attr.meta +6 -0
  38. package/conformance/cases/write-all-matches-attr.write.json +4 -0
  39. package/conformance/cases/write-all-matches-group.after.html +1 -0
  40. package/conformance/cases/write-all-matches-group.after.json +3 -0
  41. package/conformance/cases/write-all-matches-group.data.json +1 -0
  42. package/conformance/cases/write-all-matches-group.html +1 -0
  43. package/conformance/cases/write-all-matches-group.meta +6 -0
  44. package/conformance/cases/write-all-matches-group.write.json +4 -0
  45. package/conformance/cases/write-all-matches-text.after.html +1 -0
  46. package/conformance/cases/write-all-matches-text.after.json +3 -0
  47. package/conformance/cases/write-all-matches-text.data.json +1 -0
  48. package/conformance/cases/write-all-matches-text.html +1 -0
  49. package/conformance/cases/write-all-matches-text.meta +6 -0
  50. package/conformance/cases/write-all-matches-text.write.json +4 -0
  51. package/conformance/cases/write-all-matches-unmatched.data.json +1 -0
  52. package/conformance/cases/write-all-matches-unmatched.error.json +14 -0
  53. package/conformance/cases/write-all-matches-unmatched.html +1 -0
  54. package/conformance/cases/write-all-matches-unmatched.meta +6 -0
  55. package/conformance/sheet/body-data-write.body.json +3 -0
  56. package/conformance/sheet/body-data-write.data.json +3 -0
  57. package/conformance/sheet/body-data-write.html +25 -0
  58. package/conformance/sheet/body-data-write.meta +5 -0
  59. package/conformance/sheet/body-patch-key-in-rules.body.json +3 -0
  60. package/conformance/sheet/body-patch-key-in-rules.data.json +13 -0
  61. package/conformance/sheet/body-patch-key-in-rules.html +18 -0
  62. package/conformance/sheet/body-patch-key-in-rules.meta +5 -0
  63. package/conformance/sheet/body-patch-not-array.body.json +3 -0
  64. package/conformance/sheet/body-patch-not-array.data.json +3 -0
  65. package/conformance/sheet/body-patch-not-array.html +25 -0
  66. package/conformance/sheet/body-patch-not-array.meta +5 -0
  67. package/conformance/sheet/body-patch-on-sheet.body.json +3 -0
  68. package/conformance/sheet/body-patch-on-sheet.data.json +14 -0
  69. package/conformance/sheet/body-patch-on-sheet.html +25 -0
  70. package/conformance/sheet/body-patch-on-sheet.meta +5 -0
  71. package/conformance/sheet/body-patch-version-1.body.json +3 -0
  72. package/conformance/sheet/body-patch-version-1.data.json +13 -0
  73. package/conformance/sheet/body-patch-version-1.html +4 -0
  74. package/conformance/sheet/body-patch-version-1.meta +5 -0
  75. package/conformance/sheet/body-patch-with-other-key.body.json +3 -0
  76. package/conformance/sheet/body-patch-with-other-key.data.json +14 -0
  77. package/conformance/sheet/body-patch-with-other-key.html +25 -0
  78. package/conformance/sheet/body-patch-with-other-key.meta +5 -0
  79. package/conformance/sheet/body-patch-without-operation-id.body.json +3 -0
  80. package/conformance/sheet/body-patch-without-operation-id.data.json +13 -0
  81. package/conformance/sheet/body-patch-without-operation-id.html +25 -0
  82. package/conformance/sheet/body-patch-without-operation-id.meta +5 -0
  83. package/conformance/sheet/params-bad-range.error.json +6 -0
  84. package/conformance/sheet/params-bad-range.html +25 -0
  85. package/conformance/sheet/params-bad-range.meta +6 -0
  86. package/conformance/sheet/params-bad-render.error.json +6 -0
  87. package/conformance/sheet/params-bad-render.html +25 -0
  88. package/conformance/sheet/params-bad-render.meta +6 -0
  89. package/conformance/sheet/params-bad-shape.error.json +6 -0
  90. package/conformance/sheet/params-bad-shape.html +25 -0
  91. package/conformance/sheet/params-bad-shape.meta +6 -0
  92. package/conformance/sheet/params-cache-buster.expected.json +128 -0
  93. package/conformance/sheet/params-cache-buster.html +25 -0
  94. package/conformance/sheet/params-cache-buster.meta +6 -0
  95. package/conformance/sheet/params-data-alone.expected.json +128 -0
  96. package/conformance/sheet/params-data-alone.html +25 -0
  97. package/conformance/sheet/params-data-alone.meta +6 -0
  98. package/conformance/sheet/params-empty.error.json +6 -0
  99. package/conformance/sheet/params-empty.html +25 -0
  100. package/conformance/sheet/params-empty.meta +6 -0
  101. package/conformance/sheet/params-index.expected.json +19 -0
  102. package/conformance/sheet/params-index.html +25 -0
  103. package/conformance/sheet/params-index.meta +6 -0
  104. package/conformance/sheet/params-none.expected.json +128 -0
  105. package/conformance/sheet/params-none.html +25 -0
  106. package/conformance/sheet/params-none.meta +6 -0
  107. package/conformance/sheet/params-range.expected.json +17 -0
  108. package/conformance/sheet/params-range.html +25 -0
  109. package/conformance/sheet/params-range.meta +6 -0
  110. package/conformance/sheet/params-render-without-range.error.json +6 -0
  111. package/conformance/sheet/params-render-without-range.html +25 -0
  112. package/conformance/sheet/params-render-without-range.meta +6 -0
  113. package/conformance/sheet/params-repeated.error.json +6 -0
  114. package/conformance/sheet/params-repeated.html +25 -0
  115. package/conformance/sheet/params-repeated.meta +6 -0
  116. package/conformance/sheet/params-unknown-sheet.error.json +6 -0
  117. package/conformance/sheet/params-unknown-sheet.html +25 -0
  118. package/conformance/sheet/params-unknown-sheet.meta +6 -0
  119. package/conformance/sheet/params-with-data.error.json +6 -0
  120. package/conformance/sheet/params-with-data.html +25 -0
  121. package/conformance/sheet/params-with-data.meta +6 -0
  122. package/conformance/sheet/sheet-contract-cells.expected.json +57 -0
  123. package/conformance/sheet/sheet-contract-cells.html +10 -0
  124. package/conformance/sheet/sheet-contract-cells.meta +6 -0
  125. package/conformance/sheet/sheet-contract-formulas.expected.json +29 -0
  126. package/conformance/sheet/sheet-contract-formulas.html +10 -0
  127. package/conformance/sheet/sheet-contract-formulas.meta +6 -0
  128. package/conformance/sheet/sheet-contract-values.expected.json +29 -0
  129. package/conformance/sheet/sheet-contract-values.html +10 -0
  130. package/conformance/sheet/sheet-contract-values.meta +6 -0
  131. package/conformance/sheet/sheet-contract-whole.expected.json +59 -0
  132. package/conformance/sheet/sheet-contract-whole.html +10 -0
  133. package/conformance/sheet/sheet-contract-whole.meta +5 -0
  134. package/conformance/sheet/sheet-hostile-01.expected.json +18 -0
  135. package/conformance/sheet/sheet-hostile-01.html +6 -0
  136. package/conformance/sheet/sheet-hostile-01.meta +5 -0
  137. package/conformance/sheet/sheet-hostile-02.expected.json +18 -0
  138. package/conformance/sheet/sheet-hostile-02.html +6 -0
  139. package/conformance/sheet/sheet-hostile-02.meta +5 -0
  140. package/conformance/sheet/sheet-hostile-03.expected.json +18 -0
  141. package/conformance/sheet/sheet-hostile-03.html +6 -0
  142. package/conformance/sheet/sheet-hostile-03.meta +5 -0
  143. package/conformance/sheet/sheet-hostile-04.expected.json +18 -0
  144. package/conformance/sheet/sheet-hostile-04.html +6 -0
  145. package/conformance/sheet/sheet-hostile-04.meta +5 -0
  146. package/conformance/sheet/sheet-hostile-05.expected.json +18 -0
  147. package/conformance/sheet/sheet-hostile-05.html +6 -0
  148. package/conformance/sheet/sheet-hostile-05.meta +5 -0
  149. package/conformance/sheet/sheet-hostile-06.expected.json +21 -0
  150. package/conformance/sheet/sheet-hostile-06.html +6 -0
  151. package/conformance/sheet/sheet-hostile-06.meta +5 -0
  152. package/conformance/sheet/sheet-hostile-07.expected.json +21 -0
  153. package/conformance/sheet/sheet-hostile-07.html +6 -0
  154. package/conformance/sheet/sheet-hostile-07.meta +5 -0
  155. package/conformance/sheet/sheet-hostile-08.expected.json +21 -0
  156. package/conformance/sheet/sheet-hostile-08.html +6 -0
  157. package/conformance/sheet/sheet-hostile-08.meta +5 -0
  158. package/conformance/sheet/sheet-hostile-09.expected.json +21 -0
  159. package/conformance/sheet/sheet-hostile-09.html +6 -0
  160. package/conformance/sheet/sheet-hostile-09.meta +5 -0
  161. package/conformance/sheet/sheet-hostile-10.expected.json +21 -0
  162. package/conformance/sheet/sheet-hostile-10.html +6 -0
  163. package/conformance/sheet/sheet-hostile-10.meta +5 -0
  164. package/conformance/sheet/sheet-hostile-11.expected.json +22 -0
  165. package/conformance/sheet/sheet-hostile-11.html +6 -0
  166. package/conformance/sheet/sheet-hostile-11.meta +5 -0
  167. package/conformance/sheet/sheet-hostile-12.expected.json +18 -0
  168. package/conformance/sheet/sheet-hostile-12.html +6 -0
  169. package/conformance/sheet/sheet-hostile-12.meta +5 -0
  170. package/conformance/sheet/sheet-hostile-13.expected.json +18 -0
  171. package/conformance/sheet/sheet-hostile-13.html +6 -0
  172. package/conformance/sheet/sheet-hostile-13.meta +5 -0
  173. package/conformance/sheet/sheet-hostile-14.expected.json +21 -0
  174. package/conformance/sheet/sheet-hostile-14.html +6 -0
  175. package/conformance/sheet/sheet-hostile-14.meta +5 -0
  176. package/conformance/sheet/sheet-hostile-15.expected.json +18 -0
  177. package/conformance/sheet/sheet-hostile-15.html +0 -0
  178. package/conformance/sheet/sheet-hostile-15.meta +5 -0
  179. package/conformance/sheet/sheet-hostile-16.expected.json +18 -0
  180. package/conformance/sheet/sheet-hostile-16.html +6 -0
  181. package/conformance/sheet/sheet-hostile-16.meta +5 -0
  182. package/conformance/sheet/sheet-hostile-17.expected.json +18 -0
  183. package/conformance/sheet/sheet-hostile-17.html +6 -0
  184. package/conformance/sheet/sheet-hostile-17.meta +5 -0
  185. package/conformance/sheet/sheet-hostile-18.expected.json +18 -0
  186. package/conformance/sheet/sheet-hostile-18.html +6 -0
  187. package/conformance/sheet/sheet-hostile-18.meta +5 -0
  188. package/conformance/sheet/sheet-hostile-19.expected.json +18 -0
  189. package/conformance/sheet/sheet-hostile-19.html +6 -0
  190. package/conformance/sheet/sheet-hostile-19.meta +5 -0
  191. package/conformance/sheet/sheet-hostile-20.expected.json +16 -0
  192. package/conformance/sheet/sheet-hostile-20.html +6 -0
  193. package/conformance/sheet/sheet-hostile-20.meta +5 -0
  194. package/conformance/sheet/sheet-hostile-21.expected.json +18 -0
  195. package/conformance/sheet/sheet-hostile-21.html +6 -0
  196. package/conformance/sheet/sheet-hostile-21.meta +5 -0
  197. package/conformance/sheet/sheet-hostile-22.expected.json +21 -0
  198. package/conformance/sheet/sheet-hostile-22.html +6 -0
  199. package/conformance/sheet/sheet-hostile-22.meta +5 -0
  200. package/conformance/sheet/sheet-hostile-23.expected.json +18 -0
  201. package/conformance/sheet/sheet-hostile-23.html +6 -0
  202. package/conformance/sheet/sheet-hostile-23.meta +5 -0
  203. package/conformance/sheet/sheet-hostile-24.expected.json +18 -0
  204. package/conformance/sheet/sheet-hostile-24.html +6 -0
  205. package/conformance/sheet/sheet-hostile-24.meta +5 -0
  206. package/conformance/sheet/sheet-hostile-25.expected.json +18 -0
  207. package/conformance/sheet/sheet-hostile-25.html +6 -0
  208. package/conformance/sheet/sheet-hostile-25.meta +5 -0
  209. package/conformance/sheet/sheet-hostile-26.expected.json +18 -0
  210. package/conformance/sheet/sheet-hostile-26.html +6 -0
  211. package/conformance/sheet/sheet-hostile-26.meta +5 -0
  212. package/conformance/sheet/sheet-hostile-27.expected.json +24 -0
  213. package/conformance/sheet/sheet-hostile-27.html +7 -0
  214. package/conformance/sheet/sheet-hostile-27.meta +5 -0
  215. package/conformance/sheet/sheet-hostile-28.expected.json +18 -0
  216. package/conformance/sheet/sheet-hostile-28.html +6 -0
  217. package/conformance/sheet/sheet-hostile-28.meta +5 -0
  218. package/conformance/sheet/sheet-hostile-29.expected.json +18 -0
  219. package/conformance/sheet/sheet-hostile-29.html +6 -0
  220. package/conformance/sheet/sheet-hostile-29.meta +5 -0
  221. package/conformance/sheet/sheet-hostile-30.expected.json +18 -0
  222. package/conformance/sheet/sheet-hostile-30.html +6 -0
  223. package/conformance/sheet/sheet-hostile-30.meta +5 -0
  224. package/conformance/sheet/sheet-hostile-31.expected.json +19 -0
  225. package/conformance/sheet/sheet-hostile-31.html +6 -0
  226. package/conformance/sheet/sheet-hostile-31.meta +5 -0
  227. package/conformance/sheet/sheet-hostile-32.expected.json +24 -0
  228. package/conformance/sheet/sheet-hostile-32.html +6 -0
  229. package/conformance/sheet/sheet-hostile-32.meta +5 -0
  230. package/conformance/sheet/sheet-hostile-33.expected.json +18 -0
  231. package/conformance/sheet/sheet-hostile-33.html +6 -0
  232. package/conformance/sheet/sheet-hostile-33.meta +5 -0
  233. package/conformance/sheet/sheet-hostile-34.expected.json +24 -0
  234. package/conformance/sheet/sheet-hostile-34.html +8 -0
  235. package/conformance/sheet/sheet-hostile-34.meta +5 -0
  236. package/conformance/sheet/sheet-hostile-35.expected.json +24 -0
  237. package/conformance/sheet/sheet-hostile-35.html +8 -0
  238. package/conformance/sheet/sheet-hostile-35.meta +5 -0
  239. package/conformance/sheet/sheet-patch-append.after.html +27 -0
  240. package/conformance/sheet/sheet-patch-append.after.json +143 -0
  241. package/conformance/sheet/sheet-patch-append.data.json +15 -0
  242. package/conformance/sheet/sheet-patch-append.html +25 -0
  243. package/conformance/sheet/sheet-patch-append.meta +5 -0
  244. package/conformance/sheet/sheet-patch-append.write.json +7 -0
  245. package/conformance/sheet/sheet-patch-bad-range.data.json +11 -0
  246. package/conformance/sheet/sheet-patch-bad-range.error.json +6 -0
  247. package/conformance/sheet/sheet-patch-bad-range.html +25 -0
  248. package/conformance/sheet/sheet-patch-bad-range.meta +5 -0
  249. package/conformance/sheet/sheet-patch-bad-value.data.json +13 -0
  250. package/conformance/sheet/sheet-patch-bad-value.error.json +6 -0
  251. package/conformance/sheet/sheet-patch-bad-value.html +25 -0
  252. package/conformance/sheet/sheet-patch-bad-value.meta +5 -0
  253. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.after.html +25 -0
  254. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.after.json +129 -0
  255. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.data.json +11 -0
  256. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.html +25 -0
  257. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.meta +5 -0
  258. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.write.json +7 -0
  259. package/conformance/sheet/sheet-patch-covered-cell.data.json +10 -0
  260. package/conformance/sheet/sheet-patch-covered-cell.error.json +6 -0
  261. package/conformance/sheet/sheet-patch-covered-cell.html +10 -0
  262. package/conformance/sheet/sheet-patch-covered-cell.meta +5 -0
  263. package/conformance/sheet/sheet-patch-empty.data.json +1 -0
  264. package/conformance/sheet/sheet-patch-empty.error.json +6 -0
  265. package/conformance/sheet/sheet-patch-empty.html +25 -0
  266. package/conformance/sheet/sheet-patch-empty.meta +5 -0
  267. package/conformance/sheet/sheet-patch-fallback-route.after.html +24 -0
  268. package/conformance/sheet/sheet-patch-fallback-route.after.json +86 -0
  269. package/conformance/sheet/sheet-patch-fallback-route.data.json +10 -0
  270. package/conformance/sheet/sheet-patch-fallback-route.html +24 -0
  271. package/conformance/sheet/sheet-patch-fallback-route.meta +5 -0
  272. package/conformance/sheet/sheet-patch-fallback-route.write.json +7 -0
  273. package/conformance/sheet/sheet-patch-first-tab.after.html +25 -0
  274. package/conformance/sheet/sheet-patch-first-tab.after.json +129 -0
  275. package/conformance/sheet/sheet-patch-first-tab.data.json +10 -0
  276. package/conformance/sheet/sheet-patch-first-tab.html +25 -0
  277. package/conformance/sheet/sheet-patch-first-tab.meta +5 -0
  278. package/conformance/sheet/sheet-patch-first-tab.write.json +7 -0
  279. package/conformance/sheet/sheet-patch-formula.after.html +25 -0
  280. package/conformance/sheet/sheet-patch-formula.after.json +128 -0
  281. package/conformance/sheet/sheet-patch-formula.data.json +11 -0
  282. package/conformance/sheet/sheet-patch-formula.html +25 -0
  283. package/conformance/sheet/sheet-patch-formula.meta +5 -0
  284. package/conformance/sheet/sheet-patch-formula.write.json +7 -0
  285. package/conformance/sheet/sheet-patch-no-sheet-rule.data.json +10 -0
  286. package/conformance/sheet/sheet-patch-no-sheet-rule.error.json +6 -0
  287. package/conformance/sheet/sheet-patch-no-sheet-rule.html +18 -0
  288. package/conformance/sheet/sheet-patch-no-sheet-rule.meta +5 -0
  289. package/conformance/sheet/sheet-patch-no-values.data.json +6 -0
  290. package/conformance/sheet/sheet-patch-no-values.error.json +6 -0
  291. package/conformance/sheet/sheet-patch-no-values.html +25 -0
  292. package/conformance/sheet/sheet-patch-no-values.meta +5 -0
  293. package/conformance/sheet/sheet-patch-not-canonical.data.json +11 -0
  294. package/conformance/sheet/sheet-patch-not-canonical.error.json +6 -0
  295. package/conformance/sheet/sheet-patch-not-canonical.html +25 -0
  296. package/conformance/sheet/sheet-patch-not-canonical.meta +5 -0
  297. package/conformance/sheet/sheet-patch-outside-sheet.data.json +11 -0
  298. package/conformance/sheet/sheet-patch-outside-sheet.error.json +6 -0
  299. package/conformance/sheet/sheet-patch-outside-sheet.html +25 -0
  300. package/conformance/sheet/sheet-patch-outside-sheet.meta +5 -0
  301. package/conformance/sheet/sheet-patch-range.after.html +25 -0
  302. package/conformance/sheet/sheet-patch-range.after.json +126 -0
  303. package/conformance/sheet/sheet-patch-range.data.json +11 -0
  304. package/conformance/sheet/sheet-patch-range.html +25 -0
  305. package/conformance/sheet/sheet-patch-range.meta +5 -0
  306. package/conformance/sheet/sheet-patch-range.write.json +7 -0
  307. package/conformance/sheet/sheet-patch-two-tabs.after.html +25 -0
  308. package/conformance/sheet/sheet-patch-two-tabs.after.json +130 -0
  309. package/conformance/sheet/sheet-patch-two-tabs.data.json +20 -0
  310. package/conformance/sheet/sheet-patch-two-tabs.html +25 -0
  311. package/conformance/sheet/sheet-patch-two-tabs.meta +5 -0
  312. package/conformance/sheet/sheet-patch-two-tabs.write.json +7 -0
  313. package/conformance/sheet/sheet-patch-typed.after.html +25 -0
  314. package/conformance/sheet/sheet-patch-typed.after.json +121 -0
  315. package/conformance/sheet/sheet-patch-typed.data.json +13 -0
  316. package/conformance/sheet/sheet-patch-typed.html +25 -0
  317. package/conformance/sheet/sheet-patch-typed.meta +5 -0
  318. package/conformance/sheet/sheet-patch-typed.write.json +7 -0
  319. package/conformance/sheet/sheet-patch-unknown-sheet.data.json +11 -0
  320. package/conformance/sheet/sheet-patch-unknown-sheet.error.json +6 -0
  321. package/conformance/sheet/sheet-patch-unknown-sheet.html +25 -0
  322. package/conformance/sheet/sheet-patch-unknown-sheet.meta +5 -0
  323. package/conformance/sheet/sheet-read-bad-range.error.json +6 -0
  324. package/conformance/sheet/sheet-read-bad-range.html +25 -0
  325. package/conformance/sheet/sheet-read-bad-range.meta +6 -0
  326. package/conformance/sheet/sheet-read-bad-render.error.json +6 -0
  327. package/conformance/sheet/sheet-read-bad-render.html +25 -0
  328. package/conformance/sheet/sheet-read-bad-render.meta +6 -0
  329. package/conformance/sheet/sheet-read-digest.expected.json +91 -0
  330. package/conformance/sheet/sheet-read-digest.html +25 -0
  331. package/conformance/sheet/sheet-read-digest.meta +6 -0
  332. package/conformance/sheet/sheet-read-empty.expected.json +11 -0
  333. package/conformance/sheet/sheet-read-empty.html +6 -0
  334. package/conformance/sheet/sheet-read-empty.meta +5 -0
  335. package/conformance/sheet/sheet-read-escapes.expected.json +108 -0
  336. package/conformance/sheet/sheet-read-escapes.html +22 -0
  337. package/conformance/sheet/sheet-read-escapes.meta +5 -0
  338. package/conformance/sheet/sheet-read-formulas-formatted.expected.json +298 -0
  339. package/conformance/sheet/sheet-read-formulas-formatted.html +16 -0
  340. package/conformance/sheet/sheet-read-formulas-formatted.meta +5 -0
  341. package/conformance/sheet/sheet-read-grid-in-comment.expected.json +85 -0
  342. package/conformance/sheet/sheet-read-grid-in-comment.html +24 -0
  343. package/conformance/sheet/sheet-read-grid-in-comment.meta +5 -0
  344. package/conformance/sheet/sheet-read-grid-in-script.expected.json +85 -0
  345. package/conformance/sheet/sheet-read-grid-in-script.html +24 -0
  346. package/conformance/sheet/sheet-read-grid-in-script.meta +5 -0
  347. package/conformance/sheet/sheet-read-grid-in-template.expected.json +85 -0
  348. package/conformance/sheet/sheet-read-grid-in-template.html +24 -0
  349. package/conformance/sheet/sheet-read-grid-in-template.meta +5 -0
  350. package/conformance/sheet/sheet-read-grid-in-textarea.expected.json +85 -0
  351. package/conformance/sheet/sheet-read-grid-in-textarea.html +24 -0
  352. package/conformance/sheet/sheet-read-grid-in-textarea.meta +5 -0
  353. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.expected.json +94 -0
  354. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.html +24 -0
  355. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.meta +5 -0
  356. package/conformance/sheet/sheet-read-grid-outside-sheet.expected.json +85 -0
  357. package/conformance/sheet/sheet-read-grid-outside-sheet.html +24 -0
  358. package/conformance/sheet/sheet-read-grid-outside-sheet.meta +5 -0
  359. package/conformance/sheet/sheet-read-hand-edited.expected.json +85 -0
  360. package/conformance/sheet/sheet-read-hand-edited.html +18 -0
  361. package/conformance/sheet/sheet-read-hand-edited.meta +5 -0
  362. package/conformance/sheet/sheet-read-index.expected.json +19 -0
  363. package/conformance/sheet/sheet-read-index.html +25 -0
  364. package/conformance/sheet/sheet-read-index.meta +6 -0
  365. package/conformance/sheet/sheet-read-merged.expected.json +59 -0
  366. package/conformance/sheet/sheet-read-merged.html +10 -0
  367. package/conformance/sheet/sheet-read-merged.meta +5 -0
  368. package/conformance/sheet/sheet-read-no-tag.error.json +5 -0
  369. package/conformance/sheet/sheet-read-no-tag.html +18 -0
  370. package/conformance/sheet/sheet-read-no-tag.meta +5 -0
  371. package/conformance/sheet/sheet-read-padded-name-index.expected.json +19 -0
  372. package/conformance/sheet/sheet-read-padded-name-index.html +25 -0
  373. package/conformance/sheet/sheet-read-padded-name-index.meta +6 -0
  374. package/conformance/sheet/sheet-read-padded-name-lookup.expected.json +83 -0
  375. package/conformance/sheet/sheet-read-padded-name-lookup.html +25 -0
  376. package/conformance/sheet/sheet-read-padded-name-lookup.meta +6 -0
  377. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.error.json +6 -0
  378. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.html +25 -0
  379. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.meta +6 -0
  380. package/conformance/sheet/sheet-read-padded-name.expected.json +128 -0
  381. package/conformance/sheet/sheet-read-padded-name.html +25 -0
  382. package/conformance/sheet/sheet-read-padded-name.meta +5 -0
  383. package/conformance/sheet/sheet-read-range-cells.expected.json +55 -0
  384. package/conformance/sheet/sheet-read-range-cells.html +25 -0
  385. package/conformance/sheet/sheet-read-range-cells.meta +6 -0
  386. package/conformance/sheet/sheet-read-range-first-tab.expected.json +12 -0
  387. package/conformance/sheet/sheet-read-range-first-tab.html +25 -0
  388. package/conformance/sheet/sheet-read-range-first-tab.meta +6 -0
  389. package/conformance/sheet/sheet-read-range-formulas.expected.json +24 -0
  390. package/conformance/sheet/sheet-read-range-formulas.html +25 -0
  391. package/conformance/sheet/sheet-read-range-formulas.meta +6 -0
  392. package/conformance/sheet/sheet-read-range-values.expected.json +24 -0
  393. package/conformance/sheet/sheet-read-range-values.html +25 -0
  394. package/conformance/sheet/sheet-read-range-values.meta +6 -0
  395. package/conformance/sheet/sheet-read-sheet-by-id.expected.json +46 -0
  396. package/conformance/sheet/sheet-read-sheet-by-id.html +25 -0
  397. package/conformance/sheet/sheet-read-sheet-by-id.meta +6 -0
  398. package/conformance/sheet/sheet-read-sheet-by-name.expected.json +46 -0
  399. package/conformance/sheet/sheet-read-sheet-by-name.html +25 -0
  400. package/conformance/sheet/sheet-read-sheet-by-name.meta +6 -0
  401. package/conformance/sheet/sheet-read-styled.expected.json +48 -0
  402. package/conformance/sheet/sheet-read-styled.html +10 -0
  403. package/conformance/sheet/sheet-read-styled.meta +5 -0
  404. package/conformance/sheet/sheet-read-tiny.expected.json +85 -0
  405. package/conformance/sheet/sheet-read-tiny.html +18 -0
  406. package/conformance/sheet/sheet-read-tiny.meta +5 -0
  407. package/conformance/sheet/sheet-read-two-tabs.expected.json +128 -0
  408. package/conformance/sheet/sheet-read-two-tabs.html +25 -0
  409. package/conformance/sheet/sheet-read-two-tabs.meta +5 -0
  410. package/conformance/sheet/sheet-read-unknown-sheet.error.json +6 -0
  411. package/conformance/sheet/sheet-read-unknown-sheet.html +25 -0
  412. package/conformance/sheet/sheet-read-unknown-sheet.meta +6 -0
  413. package/conformance/sheet/sheet-read-version-1-codec.error.json +5 -0
  414. package/conformance/sheet/sheet-read-version-1-codec.html +25 -0
  415. package/conformance/sheet/sheet-read-version-1-codec.meta +5 -0
  416. package/conformance/sheet/sheet-write-whole-refused.data.json +3 -0
  417. package/conformance/sheet/sheet-write-whole-refused.error.json +7 -0
  418. package/conformance/sheet/sheet-write-whole-refused.html +25 -0
  419. package/conformance/sheet/sheet-write-whole-refused.meta +5 -0
  420. package/conformance/sheet-guard-extraction.json +616 -0
  421. package/conformance/sheet-guard.json +32362 -0
  422. package/conformance/sheet-read-shapes.json +1 -0
  423. package/dist/hyper-html-api.engine.esm.js +1 -1
  424. package/dist/hyper-html-api.engine.min.js +1 -1
  425. package/dist/hyper-html-api.min.js +8 -8
  426. package/dist/hyper-html-api.upgrade.min.js +2 -2
  427. package/package.json +6 -3
  428. package/src/codecs/sheet.js +127 -0
  429. package/src/codecs/sheet.vendor.js +887 -0
  430. package/src/dom-api.js +20 -11
  431. package/src/engine/apply.js +17 -7
  432. package/src/engine/codecs.js +80 -0
  433. package/src/engine/errors.js +20 -1
  434. package/src/engine/extract.js +17 -3
  435. package/src/engine/index.js +9 -5
  436. package/src/engine/round-trip.js +24 -10
  437. package/src/engine/rule-syntax.js +29 -3
  438. package/src/engine/rules-tag.js +3 -2
  439. package/src/engine/write-plan.js +4 -3
  440. package/src/page.js +15 -5
  441. package/src/patch-document.js +122 -0
  442. package/src/read.js +163 -0
  443. package/src/site.js +164 -0
  444. package/src/upgrade/codec-carry.js +23 -0
  445. package/src/upgrade/extract-all.js +12 -3
  446. package/src/upgrade/migrate.js +31 -3
  447. package/src/upgrade/rules-tags.js +4 -4
  448. package/src/upgrade/shape-match.js +32 -13
  449. package/src/write.js +44 -5
package/src/dom-api.js CHANGED
@@ -4,9 +4,12 @@ import domAdapter, { createDomAdapter } from './adapters/dom.js'
4
4
  // The DOM-bound engine: every engine call pre-wired with the DOM adapter. Lives
5
5
  // here (instead of inline in hyper-html-api.js) so both the full CDN bundle and
6
6
  // the lean `data.js` entry share one definition.
7
+ // Calls default `codecs` to the ones registered with engine.codecs.register.
8
+ const withCodecs = (opts) => ({ codecs: engineMod.registeredCodecs(), ...opts })
9
+
7
10
  export const engine = {
8
- extract: (root, rules, opts = {}) => engineMod.extract(createDomAdapter(root, opts), root, rules, opts),
9
- apply: (root, rules, data, opts = {}) => engineMod.apply(createDomAdapter(root, { ...opts, _write: true }), root, rules, data, opts),
11
+ extract: (root, rules, opts = {}) => engineMod.extract(createDomAdapter(root, opts), root, rules, withCodecs(opts)),
12
+ apply: (root, rules, data, opts = {}) => engineMod.apply(createDomAdapter(root, { ...opts, _write: true }), root, rules, data, withCodecs(opts)),
10
13
  findRulesIn: (root, token) => engineMod.findRulesIn(domAdapter, root, token),
11
14
  findRules: (root, source) => engineMod.resolveRules(domAdapter, root, source),
12
15
  bind: (root, source, opts = {}) => {
@@ -15,12 +18,14 @@ export const engine = {
15
18
  const what = typeof source === 'string' ? `data-rules-name~="${source}"` : 'the provided rules object'
16
19
  throw new Error(`hyper-html-api: could not resolve rules for ${what}`)
17
20
  }
21
+ const callOpts = withCodecs({ version: found.version, ...opts })
18
22
  return {
19
23
  ...found,
20
- get: () => engineMod.extract(createDomAdapter(root, opts), root, found.rules, opts),
21
- set: data => engineMod.apply(createDomAdapter(root, { ...opts, _write: true }), root, found.rules, data, opts),
24
+ get: () => engineMod.extract(createDomAdapter(root, opts), root, found.rules, callOpts),
25
+ set: data => engineMod.apply(createDomAdapter(root, { ...opts, _write: true }), root, found.rules, data, callOpts),
22
26
  }
23
27
  },
28
+ codecs: { register: engineMod.registerCodec, get: engineMod.getCodec },
24
29
  parseStrict: engineMod.parseStrict,
25
30
  parseRelaxed: engineMod.parseRelaxed,
26
31
  // Adapter-free, like the parsers above: a consumer that has to split a rule
@@ -38,7 +43,7 @@ function defaultRules(root) {
38
43
  const searchRoot = root && root.ownerDocument ? root.ownerDocument : root
39
44
  const found = engineMod.findRulesIn(domAdapter, searchRoot, 'api')
40
45
  if (!found) throw new engineMod.errors.NoRulesTag('api')
41
- return found.rules
46
+ return { rules: found.rules, version: found.version }
42
47
  }
43
48
 
44
49
  function containingTree(root) {
@@ -119,8 +124,8 @@ function assertNoReadOnlyTargets(rule, value) {
119
124
  }
120
125
  }
121
126
 
122
- function guardedApply(root, rules, data) {
123
- const opts = { templateAttr: 'cms-template' }
127
+ function guardedApply(root, rules, data, version) {
128
+ const opts = withCodecs({ templateAttr: 'cms-template', version })
124
129
  const liveTree = containingTree(root)
125
130
  const rootPath = childIndexPath(liveTree, root)
126
131
  const detachedTree = cloneContainingTree(liveTree)
@@ -145,7 +150,8 @@ function guardedApply(root, rules, data) {
145
150
 
146
151
  function exposeWriteError(error) {
147
152
  if (!(error instanceof Error)) return error
148
- const code = error.name || 'Error'
153
+ const keepsCode = error instanceof engineMod.errors.UnknownCodec || error instanceof engineMod.errors.CodecRefused
154
+ const code = keepsCode ? error.code : error.name || 'Error'
149
155
  try {
150
156
  error.code = code
151
157
  } catch {
@@ -182,7 +188,10 @@ function exposeWriteError(error) {
182
188
  export function extractData(a, b) {
183
189
  const root = isNode(a) ? a : document
184
190
  const source = isNode(a) ? b : a
185
- if (source === undefined) return engineMod.extract(createDomAdapter(root), root, defaultRules(root))
191
+ if (source === undefined) {
192
+ const { rules, version } = defaultRules(root)
193
+ return engineMod.extract(createDomAdapter(root), root, rules, withCodecs({ version }))
194
+ }
186
195
  return engine.bind(root, source).get()
187
196
  }
188
197
 
@@ -193,8 +202,8 @@ export function applyData(root, data, source) {
193
202
  if (!isNode(root)) {
194
203
  throw new Error('hyper-html-api: applyData(root, data, source?) needs a DOM root as the first argument.')
195
204
  }
196
- const rules = source === undefined ? defaultRules(root) : engine.bind(root, source).rules
197
- guardedApply(root, rules, data)
205
+ const { rules, version } = source === undefined ? defaultRules(root) : engine.bind(root, source)
206
+ guardedApply(root, rules, data, version)
198
207
  } catch (error) {
199
208
  throw exposeWriteError(error)
200
209
  }
@@ -10,13 +10,15 @@ import {
10
10
  ShapeMismatch,
11
11
  } from './errors.js'
12
12
  import { listDiff } from './diff.js'
13
- import { ruleAttrIndex } from './rule-syntax.js'
13
+ import { codecOf, ruleAttrIndex, splitRule } from './rule-syntax.js'
14
14
  import { isCustomPropertyName, setStyleVar } from './style-vars.js'
15
+ import { checkCodecRules, codecForRule } from './codecs.js'
15
16
 
16
17
  export function apply(adapter, root, rules, data, opts = {}) {
17
18
  if (Object.prototype.hasOwnProperty.call(opts, 'exclude') && adapter.semanticExclude === false) {
18
19
  throw new Error('This adapter does not support semantic exclude queries; omit exclude or use the DOM adapter.')
19
20
  }
21
+ checkCodecRules(rules, opts)
20
22
  const mismatches = []
21
23
  validateShape(rules, data, [], mismatches)
22
24
  if (mismatches.length) throw new ShapeMismatch(mismatches)
@@ -64,17 +66,23 @@ function applyScalar(adapter, ctx, rule, value, trace, opts) {
64
66
  return ctx
65
67
  }
66
68
 
69
+ const codec = codecForRule(rule, opts)
70
+ if (codec) {
71
+ const matches = codec.selector ? adapter.find(ctx, codec.selector, opts) : [ctx]
72
+ if (matches.length) codec.codec.write(adapter, matches[0], value, { query: opts.query })
73
+ return ctx
74
+ }
75
+
67
76
  if (rule.startsWith('@')) {
68
77
  return writePropOrAttr(adapter, ctx, rule.slice(1), value)
69
78
  }
70
79
 
71
80
  const at = ruleAttrIndex(rule)
72
81
  if (at !== -1) {
73
- const selector = rule.slice(0, at)
82
+ const { selector, all } = splitRule(rule)
74
83
  const name = rule.slice(at + 1)
75
84
  const matches = selector ? adapter.find(ctx, selector, opts) : [ctx]
76
- if (matches.length === 0) return ctx
77
- writePropOrAttr(adapter, matches[0], name, value)
85
+ for (const node of all ? matches : matches.slice(0, 1)) writePropOrAttr(adapter, node, name, value)
78
86
  return ctx
79
87
  }
80
88
 
@@ -83,9 +91,9 @@ function applyScalar(adapter, ctx, rule, value, trace, opts) {
83
91
  return ctx
84
92
  }
85
93
 
86
- const matches = adapter.find(ctx, rule, opts)
87
- if (matches.length === 0) return ctx
88
- writeText(adapter, matches[0], value)
94
+ const { selector, all } = splitRule(rule)
95
+ const matches = adapter.find(ctx, selector, opts)
96
+ for (const node of all ? matches : matches.slice(0, 1)) writeText(adapter, node, value)
89
97
  return ctx
90
98
  }
91
99
 
@@ -157,6 +165,8 @@ function validateShape(rule, value, path, mismatches) {
157
165
  if (value === undefined) return
158
166
 
159
167
  if (typeof rule === 'string') {
168
+ // A codec judges its own value when it writes.
169
+ if (codecOf(rule)) return
160
170
  if (rule.endsWith('[]')) {
161
171
  if (!Array.isArray(value)) {
162
172
  mismatches.push({ path: pathStr(path), expected: 'array', got: typeofX(value) })
@@ -0,0 +1,80 @@
1
+ import { MAX_RULE_DEPTH, RulesParseError, UnknownCodec } from './errors.js'
2
+ import { codecOf } from './rule-syntax.js'
3
+
4
+ // A codec reads and writes one element's whole value as JSON, for content the
5
+ // rule grammar cannot describe (a sheet's grid). It is named by the rule form
6
+ // "selector@codec(name)", valid only under data-rules-version="2" and only as the
7
+ // value of a top level key.
8
+ //
9
+ // codec = {
10
+ // name,
11
+ // read(adapter, node, ctx) -> JSON value ctx = { query?, view?, region? }
12
+ // write(adapter, node, value, ctx) -> void | { regions }
13
+ // patch?(adapter, node, ops, ctx) -> { changed, stale, regions }
14
+ // same(written, reread) -> boolean
15
+ // styles?: string[]
16
+ // cut?(src, marker) -> { rest, regions }
17
+ // }
18
+
19
+ const NAME_RE = /^[a-z][a-z0-9-]*$/
20
+ const registry = new Map()
21
+
22
+ // The browser registry. Hosts pass `codecs` per call instead, so no state is
23
+ // shared between requests.
24
+ export function registerCodec(name, codec) {
25
+ if (typeof name !== 'string' || !NAME_RE.test(name)) {
26
+ throw new Error(`hyper-html-api: invalid codec name ${JSON.stringify(name)} (must match ${NAME_RE})`)
27
+ }
28
+ if (!codec || typeof codec.read !== 'function' || typeof codec.write !== 'function' || typeof codec.same !== 'function') {
29
+ throw new Error(`hyper-html-api: codec "${name}" needs read, write and same functions`)
30
+ }
31
+ registry.set(name, codec)
32
+ }
33
+
34
+ export function getCodec(name) {
35
+ return registry.get(name) ?? null
36
+ }
37
+
38
+ export function registeredCodecs() {
39
+ return Object.fromEntries(registry)
40
+ }
41
+
42
+ export const NEEDS_VERSION_2 = 'The @codec(name) rule form needs a rules tag with data-rules-version="2".'
43
+ export const NEEDS_TOP_LEVEL = 'A @codec(name) rule must be the value of a top level key.'
44
+
45
+ // The codec a rule names, or null for any other rule. Assumes checkCodecRules
46
+ // already accepted the tree.
47
+ export function codecForRule(rule, opts) {
48
+ const form = codecOf(rule)
49
+ return form ? { ...form, codec: opts.codecs[form.name] } : null
50
+ }
51
+
52
+ // Walks the whole rule tree once, before anything is read or written, so a
53
+ // rules tree the call cannot honor refuses without a partial write. A codec
54
+ // rule read as a plain attribute would return null for the whole value, and a
55
+ // null that reaches a draft store or a guard baseline looks like the owner
56
+ // emptied it, which is why every refusal here is loud.
57
+ export function checkCodecRules(rules, opts = {}) {
58
+ const version = opts.version ?? '1'
59
+ const codecs = opts.codecs ?? {}
60
+ walk(rules, 0)
61
+
62
+ function walk(rule, depth) {
63
+ if (depth > MAX_RULE_DEPTH) return
64
+ if (typeof rule === 'string') {
65
+ const form = codecOf(rule)
66
+ if (!form) return
67
+ if (version !== '2') throw new RulesParseError(NEEDS_VERSION_2)
68
+ if (depth !== 1) throw new RulesParseError(NEEDS_TOP_LEVEL)
69
+ if (!Object.prototype.hasOwnProperty.call(codecs, form.name)) throw new UnknownCodec(form.name)
70
+ return
71
+ }
72
+ if (Array.isArray(rule)) {
73
+ walk(rule[1], depth + 1)
74
+ return
75
+ }
76
+ if (rule && typeof rule === 'object') {
77
+ for (const sub of Object.values(rule)) walk(sub, depth + 1)
78
+ }
79
+ }
80
+ }
@@ -8,7 +8,7 @@ export class RulesParseError extends Error {
8
8
 
9
9
  export class UnknownRulesVersion extends Error {
10
10
  constructor(version) {
11
- super(`unknown rules version: ${version}. Library supports "1".`)
11
+ super(`unknown rules version: ${version}. Library supports "1" and "2".`)
12
12
  this.name = 'UnknownRulesVersion'
13
13
  this.version = version
14
14
  }
@@ -92,3 +92,22 @@ export class WriteRejected extends Error {
92
92
  this.unmatched = unmatched
93
93
  }
94
94
  }
95
+
96
+ export class UnknownCodec extends Error {
97
+ constructor(name) {
98
+ super(`unknown codec "${name}": no codec by that name is given to this call`)
99
+ this.name = 'UnknownCodec'
100
+ this.code = 'unknown-codec'
101
+ this.codec = name
102
+ }
103
+ }
104
+
105
+ // Thrown by a codec that will not take a write, with its own code (for
106
+ // example "sheet-not-canonical"). A client error, like UnknownCodec.
107
+ export class CodecRefused extends Error {
108
+ constructor(code, message) {
109
+ super(message)
110
+ this.name = 'CodecRefused'
111
+ this.code = code
112
+ }
113
+ }
@@ -1,12 +1,14 @@
1
1
  import { DOM_PROPERTIES_SET } from './dom-properties.js'
2
2
  import { MaxRuleDepthExceeded, MAX_RULE_DEPTH } from './errors.js'
3
- import { ruleAttrIndex } from './rule-syntax.js'
3
+ import { ruleAttrIndex, splitRule } from './rule-syntax.js'
4
4
  import { isCustomPropertyName, readStyleVar } from './style-vars.js'
5
+ import { checkCodecRules, codecForRule } from './codecs.js'
5
6
 
6
7
  export function extract(adapter, root, rules, opts = {}) {
7
8
  if (Object.prototype.hasOwnProperty.call(opts, 'exclude') && adapter.semanticExclude === false) {
8
9
  throw new Error('This adapter does not support semantic exclude queries; omit exclude or use the DOM adapter.')
9
10
  }
11
+ checkCodecRules(rules, opts)
10
12
  return extractAt(adapter, root, rules, { depth: 0, path: [] }, opts)
11
13
  }
12
14
 
@@ -57,25 +59,37 @@ function extractScalar(adapter, ctx, rule, trace, opts) {
57
59
  return matches.map((n) => adapter.text(n))
58
60
  }
59
61
 
62
+ const codec = codecForRule(rule, opts)
63
+ if (codec) return readCodec(adapter, ctx, codec, opts)
64
+
60
65
  if (rule.startsWith('@')) {
61
66
  return readPropOrAttr(adapter, ctx, rule.slice(1))
62
67
  }
63
68
 
64
69
  const at = ruleAttrIndex(rule)
65
70
  if (at !== -1) {
66
- const selector = rule.slice(0, at)
71
+ const { selector, all } = splitRule(rule)
67
72
  const name = rule.slice(at + 1)
68
73
  const matches = selector ? adapter.find(ctx, selector, opts) : [ctx]
74
+ if (all && opts.allMatches) return matches.map((n) => readPropOrAttr(adapter, n, name))
69
75
  if (matches.length === 0) return null
70
76
  return readPropOrAttr(adapter, matches[0], name)
71
77
  }
72
78
 
73
79
  if (rule === '.') return adapter.text(ctx)
74
80
 
75
- const matches = adapter.find(ctx, rule, opts)
81
+ const { selector, all } = splitRule(rule)
82
+ const matches = adapter.find(ctx, selector, opts)
83
+ if (all && opts.allMatches) return matches.map((n) => adapter.text(n))
76
84
  return matches.length === 0 ? null : adapter.text(matches[0])
77
85
  }
78
86
 
87
+ function readCodec(adapter, ctx, { selector, codec }, opts) {
88
+ const matches = selector ? adapter.find(ctx, selector, opts) : [ctx]
89
+ if (matches.length === 0) return null
90
+ return codec.read(adapter, matches[0], { query: opts.query, view: opts.view, region: opts.region })
91
+ }
92
+
79
93
  // The path is copied because the caller is handed it and every nested path is
80
94
  // built from the same array. A throwing hook must not abandon the extract.
81
95
  function reportRows(opts, trace, nodes) {
@@ -6,7 +6,9 @@ export { extract } from './extract.js'
6
6
  export { apply } from './apply.js'
7
7
  export { findRulesIn } from './rules-tag.js'
8
8
  export { parseStrict, parseRelaxed } from './rules.js'
9
- export { ruleAttrIndex, splitRule } from './rule-syntax.js'
9
+ export { ruleAttrIndex, splitRule, codecOf } from './rule-syntax.js'
10
+ export { registerCodec, getCodec, registeredCodecs, checkCodecRules } from './codecs.js'
11
+ export { SUPPORTED_VERSION, SUPPORTED_VERSIONS } from './rules-tag.js'
10
12
  export * as errors from './errors.js'
11
13
  export { DOM_PROPERTIES, DOM_PROPERTIES_SET, DOM_PROPERTIES_READ_ONLY_SET } from './dom-properties.js'
12
14
  export { WRITE_POLICY, guardAdapter, checkAttribute, checkCustomPropertyValue, urlScheme } from './write-policy.js'
@@ -21,7 +23,7 @@ export { trackAdapter, spliceDocument } from './splice.js'
21
23
  // For a Document root or the cheerio $.root(), ownerDocument is absent → no-op.
22
24
  export function resolveRules(adapter, root, source) {
23
25
  if (source && typeof source === 'object') {
24
- return { rules: source, tagNode: null }
26
+ return { rules: source, tagNode: null, version: '1' }
25
27
  }
26
28
  if (typeof source === 'string') {
27
29
  const searchRoot = (root && root.ownerDocument) ? root.ownerDocument : root
@@ -36,11 +38,13 @@ export function bind(adapter, root, source, opts) {
36
38
  const what = typeof source === 'string' ? `data-rules-name~="${source}"` : 'the provided rules object'
37
39
  throw new Error(`hyper-html-api: could not resolve rules for ${what}`)
38
40
  }
39
- const { rules, tagNode } = found
41
+ const { rules, tagNode, version } = found
42
+ const callOpts = { version, ...opts }
40
43
  return {
41
44
  rules,
42
45
  tagNode, // informational only; null for a literal object
43
- get: () => extract(adapter, root, rules, opts), // opts (skip/templateAttr) forwarded
44
- set: (data) => apply(adapter, root, rules, data, opts),
46
+ version,
47
+ get: () => extract(adapter, root, rules, callOpts), // opts (skip/templateAttr/codecs) forwarded
48
+ set: (data) => apply(adapter, root, rules, data, callOpts),
45
49
  }
46
50
  }
@@ -4,7 +4,7 @@ import {
4
4
  coercePropValue,
5
5
  } from './dom-properties.js'
6
6
  import { WriteNotRoundTripped } from './errors.js'
7
- import { ruleAttrIndex } from './rule-syntax.js'
7
+ import { codecOf, ruleAttrIndex, splitRule } from './rule-syntax.js'
8
8
  import { isCustomPropertyName } from './style-vars.js'
9
9
 
10
10
  // Props whose read-side value keeps its whitespace. Every other text rule reads
@@ -18,22 +18,28 @@ const UNTRIMMED_PROPS = new Set(['textContent', 'innerText'])
18
18
  // WriteNotRoundTripped instead of returning a false success. Only the supplied
19
19
  // projection is checked: omitted keys and undefined are skipped exactly as the
20
20
  // writer skips them, which is what keeps partial updates working.
21
- export function assertRoundTrip(rules, requested, actual) {
22
- check(rules, requested, actual, [])
21
+ // A codec value is compared by its codec's own same(), from `codecs`.
22
+ export function assertRoundTrip(rules, requested, actual, codecs = {}) {
23
+ check(rules, requested, actual, [], codecs)
23
24
  }
24
25
 
25
- function check(rule, want, got, path) {
26
+ function check(rule, want, got, path, codecs) {
26
27
  if (want === undefined) return
27
28
  if (typeof rule === 'string') {
29
+ const form = codecOf(rule)
30
+ if (form) {
31
+ if (!codecs[form.name].same(want, got)) fail(path)
32
+ return
33
+ }
28
34
  checkScalarRule(rule, want, got, path)
29
35
  return
30
36
  }
31
37
  if (Array.isArray(rule)) {
32
- checkList(rule[1], want, got, path)
38
+ checkList(rule[1], want, got, path, codecs)
33
39
  return
34
40
  }
35
41
  if (typeof rule === 'object' && rule !== null) {
36
- checkObject(rule, want, got, path)
42
+ checkObject(rule, want, got, path, codecs)
37
43
  }
38
44
  }
39
45
 
@@ -47,6 +53,14 @@ function checkScalarRule(rule, want, got, path) {
47
53
  return
48
54
  }
49
55
  const at = ruleAttrIndex(rule)
56
+ if (splitRule(rule).all) {
57
+ if (!Array.isArray(got) || got.length === 0) fail(path)
58
+ for (const one of got) {
59
+ if (at !== -1) checkPropOrAttr(rule.slice(at + 1), want, one, path)
60
+ else checkText(want, one, path)
61
+ }
62
+ return
63
+ }
50
64
  if (at !== -1) {
51
65
  checkPropOrAttr(rule.slice(at + 1), want, got, path)
52
66
  return
@@ -63,16 +77,16 @@ function checkTextList(want, got, path) {
63
77
  for (let i = 0; i < want.length; i++) checkText(want[i], got[i], [...path, i])
64
78
  }
65
79
 
66
- function checkObject(rule, want, got, path) {
80
+ function checkObject(rule, want, got, path, codecs) {
67
81
  if (!isPlainObject(want) || !isPlainObject(got)) fail(path)
68
82
  for (const [key, sub] of Object.entries(rule)) {
69
- check(sub, want[key], got[key], [...path, key])
83
+ check(sub, want[key], got[key], [...path, key], codecs)
70
84
  }
71
85
  }
72
86
 
73
- function checkList(shape, want, got, path) {
87
+ function checkList(shape, want, got, path, codecs) {
74
88
  if (!Array.isArray(want) || !Array.isArray(got) || want.length !== got.length) fail(path)
75
- for (let i = 0; i < want.length; i++) check(shape, want[i], got[i], [...path, i])
89
+ for (let i = 0; i < want.length; i++) check(shape, want[i], got[i], [...path, i], codecs)
76
90
  }
77
91
 
78
92
  function checkPropOrAttr(name, want, got, path) {
@@ -10,6 +10,8 @@
10
10
  // rule into `a[href="mailto:hi` and `example.com"]`, which reads downstream as
11
11
  // an invalid selector when the rule the author wrote was correct.
12
12
 
13
+ import { RulesParseError } from './errors.js'
14
+
13
15
  // Index of the separator "@", or -1 when the rule has no prop part.
14
16
  export function ruleAttrIndex(rule) {
15
17
  let depth = 0
@@ -34,12 +36,22 @@ export function ruleAttrIndex(rule) {
34
36
  return found
35
37
  }
36
38
 
39
+ const ALL = '[*]'
40
+
37
41
  // "<selector>@<prop>" split at that separator. selector is "" for a leading "@"
38
- // (the context node itself); prop is null when the rule names no prop.
42
+ // (the context node itself); prop is null when the rule names no prop. A selector
43
+ // that ends in "[*]" names every match instead of the first: `all` is true and
44
+ // the marker is removed, because "[*]" is not CSS and no adapter can run it.
39
45
  export function splitRule(rule) {
40
46
  const at = ruleAttrIndex(rule)
41
- if (at === -1) return { selector: rule, prop: null }
42
- return { selector: rule.slice(0, at), prop: rule.slice(at + 1) || null }
47
+ const head = at === -1 ? rule : rule.slice(0, at)
48
+ const all = head.endsWith(ALL)
49
+ const split = {
50
+ selector: all ? head.slice(0, -ALL.length) : head,
51
+ prop: at === -1 ? null : rule.slice(at + 1) || null,
52
+ }
53
+ if (all) split.all = true
54
+ return split
43
55
  }
44
56
 
45
57
  // The list grammar needs a second split: "container > row" says where a row
@@ -112,3 +124,17 @@ function isSingleCompound(selector, start) {
112
124
  }
113
125
  return true
114
126
  }
127
+
128
+ const CODEC_PROP = /^codec\(([a-z][a-z0-9-]*)\)$/
129
+
130
+ // { selector, name } when a scalar rule calls a codec, else null.
131
+ export function codecOf(rule) {
132
+ if (typeof rule !== 'string') return null
133
+ const at = ruleAttrIndex(rule)
134
+ if (at === -1) return null
135
+ const match = CODEC_PROP.exec(rule.slice(at + 1))
136
+ if (!match) return null
137
+ const { selector, all } = splitRule(rule)
138
+ if (all) throw new RulesParseError('A @codec(name) rule cannot use [*].')
139
+ return { selector, name: match[1] }
140
+ }
@@ -2,6 +2,7 @@ import { parseRelaxed } from './rules.js'
2
2
  import { UnknownRulesVersion } from './errors.js'
3
3
 
4
4
  export const SUPPORTED_VERSION = '1'
5
+ export const SUPPORTED_VERSIONS = ['1', '2']
5
6
  const TOKEN_RE = /^[a-zA-Z0-9_-]+$/
6
7
 
7
8
  // findRulesIn(adapter, root) → first script[data-rules-name] (tooling/no-token)
@@ -29,12 +30,12 @@ export function findRulesIn(adapter, root, token) {
29
30
 
30
31
  const tagNode = candidates[0]
31
32
  const version = adapter.attr(tagNode, 'data-rules-version')
32
- if (version !== SUPPORTED_VERSION) throw new UnknownRulesVersion(version)
33
+ if (!SUPPORTED_VERSIONS.includes(version)) throw new UnknownRulesVersion(version)
33
34
 
34
35
  // Script tag bodies accept the same relaxed JSON syntax as the ?data=
35
36
  // URL parameter: unquoted keys, single-quoted strings, trailing commas.
36
37
  // parseRelaxed tries strict JSON.parse first, so valid JSON keeps
37
38
  // parsing identically.
38
39
  const rules = parseRelaxed(adapter.text(tagNode))
39
- return { rules, tagNode }
40
+ return { rules, tagNode, version }
40
41
  }
@@ -1,4 +1,5 @@
1
- import { ruleAttrIndex } from './rule-syntax.js'
1
+ import { splitRule } from './rule-syntax.js'
2
+ import { checkCodecRules } from './codecs.js'
2
3
 
3
4
  function isPlainObject(v) {
4
5
  return v !== null && typeof v === 'object' && !Array.isArray(v)
@@ -13,6 +14,7 @@ function label(path) {
13
14
  // Shape problems are left to apply(), which already reports them. Inside list
14
15
  // items only unknown keys are checked, because new rows do not exist yet.
15
16
  export function planWrite(adapter, root, rules, data, opts = {}) {
17
+ checkCodecRules(rules, opts)
16
18
  const unknownKeys = []
17
19
  const unmatched = []
18
20
 
@@ -34,8 +36,7 @@ export function planWrite(adapter, root, rules, data, opts = {}) {
34
36
  if (value === undefined) return
35
37
  if (typeof rule === 'string') {
36
38
  if (rule.endsWith('[]') || rule === '.' || rule.startsWith('@')) return
37
- const at = ruleAttrIndex(rule)
38
- const selector = at === -1 ? rule : rule.slice(0, at)
39
+ const { selector } = splitRule(rule)
39
40
  if (!selector) return
40
41
  if (adapter.find(ctx, selector, opts).length === 0) unmatched.push({ path: label(path), selector })
41
42
  return
package/src/page.js CHANGED
@@ -5,7 +5,7 @@ import { NoRulesTag, RulesParseError } from './engine/errors.js'
5
5
  import { parseRelaxed } from './engine/rules.js'
6
6
  import { writeDocument } from './write.js'
7
7
 
8
- const PAGE = /^page(?:\.[0-9]+)?$/
8
+ const PAGE = /^blockeditor(?:\.[0-9]+)?$/
9
9
  const TEXT = new Set(['paragraph', 'heading', 'list_item', 'callout', 'code_block', 'toggle'])
10
10
  const MARKS = ['b', 'i', 's', 'c', 'a']
11
11
 
@@ -21,10 +21,19 @@ function refuse(message) {
21
21
  throw new PageWriteRefused('invalid-content', message)
22
22
  }
23
23
 
24
+ // Inert content (a starter article kept in a <template>) is not part of the page, as in a browser.
25
+ // Cheerio parents a template's content under a root node, which closest() does not climb past.
26
+ function live(_, node) {
27
+ for (let up = node.parent; up; up = up.parent) {
28
+ if (up.type === 'tag' && (up.name === 'template' || up.name === 'noscript')) return false
29
+ }
30
+ return true
31
+ }
32
+
24
33
  function pageNodes($) {
25
- const tags = $('script[merge][clay~="live"]').filter((_, node) => PAGE.test($(node).attr('merge')))
34
+ const tags = $('script[merge][clay~="live"]').filter((_, node) => PAGE.test($(node).attr('merge'))).filter(live)
26
35
  if (!tags.length) return null
27
- const articles = $('article[data-page]')
36
+ const articles = $('article[data-blockeditor]').filter(live)
28
37
  if (tags.length !== 1 || articles.length !== 1) refuse('Expected one page store and one page article')
29
38
  return { tag: tags[0], article: articles[0] }
30
39
  }
@@ -316,8 +325,9 @@ export async function writePageDocument(load, html, data, options = {}) {
316
325
  const next$ = load(candidate.html, { sourceCodeLocationInfo: true })
317
326
  const nextNodes = pageNodes(next$)
318
327
  const aligned$ = load(aligned)
319
- const changes = pageChanges(next$, nextNodes.article, aligned$, aligned$('article[data-page]')[0], page.blocks, before.model, projection)
320
- const unchangedAttrs = canon(next$(nextNodes.article).attr()) === canon(aligned$('article[data-page]').attr())
328
+ const alignedArticle = pageNodes(aligned$).article
329
+ const changes = pageChanges(next$, nextNodes.article, aligned$, alignedArticle, page.blocks, before.model, projection)
330
+ const unchangedAttrs = canon(next$(nextNodes.article).attr()) === canon(aligned$(alignedArticle).attr())
321
331
  const proposed = projectionOf(load, proposedBlocks(page.blocks, changes), projection)
322
332
  if (!unchangedAttrs || !sameArticle(next$, nextNodes.article, proposed)) {
323
333
  refuse('The article edit cannot be represented in the page store')