hyper-html-api 0.13.0 → 0.14.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 (47) 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/write-all-matches-attr.after.html +1 -0
  12. package/conformance/cases/write-all-matches-attr.after.json +3 -0
  13. package/conformance/cases/write-all-matches-attr.data.json +1 -0
  14. package/conformance/cases/write-all-matches-attr.html +1 -0
  15. package/conformance/cases/write-all-matches-attr.meta +6 -0
  16. package/conformance/cases/write-all-matches-attr.write.json +4 -0
  17. package/conformance/cases/write-all-matches-group.after.html +1 -0
  18. package/conformance/cases/write-all-matches-group.after.json +3 -0
  19. package/conformance/cases/write-all-matches-group.data.json +1 -0
  20. package/conformance/cases/write-all-matches-group.html +1 -0
  21. package/conformance/cases/write-all-matches-group.meta +6 -0
  22. package/conformance/cases/write-all-matches-group.write.json +4 -0
  23. package/conformance/cases/write-all-matches-text.after.html +1 -0
  24. package/conformance/cases/write-all-matches-text.after.json +3 -0
  25. package/conformance/cases/write-all-matches-text.data.json +1 -0
  26. package/conformance/cases/write-all-matches-text.html +1 -0
  27. package/conformance/cases/write-all-matches-text.meta +6 -0
  28. package/conformance/cases/write-all-matches-text.write.json +4 -0
  29. package/conformance/cases/write-all-matches-unmatched.data.json +1 -0
  30. package/conformance/cases/write-all-matches-unmatched.error.json +14 -0
  31. package/conformance/cases/write-all-matches-unmatched.html +1 -0
  32. package/conformance/cases/write-all-matches-unmatched.meta +6 -0
  33. package/dist/hyper-html-api.engine.esm.js +1 -1
  34. package/dist/hyper-html-api.engine.min.js +1 -1
  35. package/dist/hyper-html-api.min.js +8 -8
  36. package/dist/hyper-html-api.upgrade.min.js +2 -2
  37. package/package.json +3 -2
  38. package/src/engine/apply.js +6 -7
  39. package/src/engine/extract.js +6 -3
  40. package/src/engine/round-trip.js +9 -1
  41. package/src/engine/rule-syntax.js +13 -3
  42. package/src/engine/write-plan.js +2 -3
  43. package/src/page.js +15 -5
  44. package/src/site.js +164 -0
  45. package/src/upgrade/migrate.js +2 -1
  46. package/src/upgrade/shape-match.js +32 -13
  47. package/src/write.js +1 -1
@@ -1,14 +1,29 @@
1
+ import { DOM_PROPERTIES_READ_ONLY_SET } from '../engine/dom-properties.js'
2
+ import { ruleAttrIndex } from '../engine/rule-syntax.js'
3
+
1
4
  /**
2
5
  * Walk v2Rules pulling matching values from v1Data.
3
6
  *
7
+ * Outside a list, what v1 has no value for keeps v2's template default:
8
+ *
4
9
  * - Scalar leaves: carry the v1 value if defined, else leave undefined so
5
10
  * the engine keeps v2's template default on apply.
6
- * - Object arrays ([sel, shape]): carry v1's items, trimmed to v1's count;
7
- * each item shape-matches recursively. Fields v2 added that didn't exist
8
- * in v1 are left undefined → template default.
9
- * - Scalar arrays (`sel[]`): carry as-is.
11
+ * - Lists: carry v1's items, trimmed to v1's count; a list v1 never had
12
+ * keeps v2's rows.
10
13
  * - Objects: recurse per key.
11
14
  *
15
+ * Inside a list item nothing keeps a default. A row is the owner's own. It is
16
+ * written over a v2 sample row, or over a clone of one, so a field left
17
+ * undefined would take that sample's value (its label, its cover, its done
18
+ * mark, its id). So in a row:
19
+ *
20
+ * - A field v1 read as null, a field v1 never had (v2 added it), and a
21
+ * scalar field where v1 holds an object are all carried as null, and the
22
+ * write clears them.
23
+ * - A list v1 never had is carried as an empty list.
24
+ * - A property nothing can write (tagName, childElementCount) stays
25
+ * undefined, because writing null to it refuses the whole write.
26
+ *
12
27
  * Returns { data, summary } where summary counts scalar leaves carried,
13
28
  * dropped, and total list items preserved.
14
29
  */
@@ -19,31 +34,30 @@ export function shapeMatch(v1Data, v2Rules) {
19
34
  return { data, summary }
20
35
  }
21
36
 
22
- function walk(rule, v1, summary) {
37
+ function walk(rule, v1, summary, inRow = false) {
23
38
  if (typeof rule === 'string') {
24
39
  if (rule.endsWith('[]')) {
25
- if (!Array.isArray(v1)) return undefined
40
+ if (!Array.isArray(v1)) return inRow ? [] : undefined
26
41
  summary.listItems += v1.length
27
42
  summary.carriedOver += v1.length
28
43
  return v1
29
44
  }
30
- if (v1 === undefined || v1 === null) return undefined
31
- // A scalar slot can't hold an object/array from v1; leave the template
32
- // default (collectDiscards counts the dropped leaves).
33
- if (typeof v1 === 'object') return undefined
45
+ // A scalar slot can't hold an object/array from v1 (collectDiscards counts
46
+ // the dropped leaves), so it is treated as a slot v1 has no value for.
47
+ if (v1 == null || typeof v1 === 'object') return inRow && !readOnly(rule) ? null : undefined
34
48
  summary.carriedOver++
35
49
  return v1
36
50
  }
37
51
  if (Array.isArray(rule)) {
38
52
  const [, shape] = rule
39
- if (!Array.isArray(v1)) return undefined
53
+ if (!Array.isArray(v1)) return inRow ? [] : undefined
40
54
  summary.listItems += v1.length
41
- return v1.map((item) => walk(shape, item, summary))
55
+ return v1.map((item) => walk(shape, item, summary, true))
42
56
  }
43
57
  if (typeof rule === 'object' && rule !== null) {
44
58
  const out = {}
45
59
  for (const [k, sub] of Object.entries(rule)) {
46
- const v = walk(sub, v1 == null ? undefined : v1[k], summary)
60
+ const v = walk(sub, v1 == null ? undefined : v1[k], summary, inRow)
47
61
  if (v !== undefined) out[k] = v
48
62
  }
49
63
  return out
@@ -51,6 +65,11 @@ function walk(rule, v1, summary) {
51
65
  return undefined
52
66
  }
53
67
 
68
+ function readOnly(rule) {
69
+ const at = ruleAttrIndex(rule)
70
+ return at !== -1 && DOM_PROPERTIES_READ_ONLY_SET.has(rule.slice(at + 1))
71
+ }
72
+
54
73
  function collectDiscards(rule, v1, summary) {
55
74
  if (v1 == null) return
56
75
  if (typeof rule === 'object' && rule !== null && !Array.isArray(rule)) {
package/src/write.js CHANGED
@@ -46,7 +46,7 @@ export function writeDocument(load, src, data, options = {}) {
46
46
  // pays for the field it wrote, not for every rule in the document.
47
47
  const written = writtenRules(rules, data)
48
48
  const writtenRoot = load(html).root()
49
- assertRoundTrip(written, data, extract(cheerioAdapter, writtenRoot, written, opts))
49
+ assertRoundTrip(written, data, extract(cheerioAdapter, writtenRoot, written, { ...opts, allMatches: true }))
50
50
  return { html, changed: html !== src, spliced }
51
51
  }
52
52