hyper-html-api 0.8.1 → 0.9.1
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.
- package/README.md +28 -0
- package/conformance/MANIFEST.json +2 -2
- package/conformance/cases/no-data-list-skipped.expected.json +6 -0
- package/conformance/cases/no-data-list-skipped.html +1 -0
- package/conformance/cases/no-data-list-skipped.meta +4 -0
- package/conformance/cases/no-data-list-skipped.parsed.json +3 -0
- package/conformance/cases/no-data-list-skipped.rules +1 -0
- package/conformance/cases/no-data-text-projected.expected.json +3 -0
- package/conformance/cases/no-data-text-projected.html +1 -0
- package/conformance/cases/no-data-text-projected.meta +4 -0
- package/conformance/cases/no-data-text-projected.parsed.json +3 -0
- package/conformance/cases/no-data-text-projected.rules +1 -0
- package/conformance/cases/props-type-is-attribute.expected.json +5 -0
- package/conformance/cases/props-type-is-attribute.meta +4 -0
- package/conformance/cases/write-attr-app-scheme.after.html +1 -0
- package/conformance/cases/write-attr-app-scheme.after.json +3 -0
- package/conformance/cases/write-attr-app-scheme.data.json +1 -0
- package/conformance/cases/write-attr-app-scheme.html +1 -0
- package/conformance/cases/write-attr-app-scheme.meta +4 -0
- package/conformance/cases/write-attr-app-scheme.write.json +4 -0
- package/conformance/cases/write-attr-href.after.html +1 -0
- package/conformance/cases/write-attr-href.after.json +3 -0
- package/conformance/cases/write-attr-href.data.json +1 -0
- package/conformance/cases/write-attr-href.html +1 -0
- package/conformance/cases/write-attr-href.meta +4 -0
- package/conformance/cases/write-attr-href.write.json +4 -0
- package/conformance/cases/write-checked-false.after.html +1 -0
- package/conformance/cases/write-checked-false.after.json +3 -0
- package/conformance/cases/write-checked-false.data.json +1 -0
- package/conformance/cases/write-checked-false.html +1 -0
- package/conformance/cases/write-checked-false.meta +4 -0
- package/conformance/cases/write-checked-false.write.json +4 -0
- package/conformance/cases/write-checked-true.after.html +1 -0
- package/conformance/cases/write-checked-true.after.json +3 -0
- package/conformance/cases/write-checked-true.data.json +1 -0
- package/conformance/cases/write-checked-true.html +1 -0
- package/conformance/cases/write-checked-true.meta +4 -0
- package/conformance/cases/write-checked-true.write.json +4 -0
- package/conformance/cases/write-crlf-multi.after.html +12 -0
- package/conformance/cases/write-crlf-multi.after.json +8 -0
- package/conformance/cases/write-crlf-multi.data.json +1 -0
- package/conformance/cases/write-crlf-multi.html +12 -0
- package/conformance/cases/write-crlf-multi.meta +4 -0
- package/conformance/cases/write-crlf-multi.write.json +4 -0
- package/conformance/cases/write-fallback-implied.after.html +1 -0
- package/conformance/cases/write-fallback-implied.after.json +3 -0
- package/conformance/cases/write-fallback-implied.data.json +1 -0
- package/conformance/cases/write-fallback-implied.html +1 -0
- package/conformance/cases/write-fallback-implied.meta +4 -0
- package/conformance/cases/write-fallback-implied.write.json +4 -0
- package/conformance/cases/write-flatten.after.html +1 -0
- package/conformance/cases/write-flatten.after.json +3 -0
- package/conformance/cases/write-flatten.data.json +1 -0
- package/conformance/cases/write-flatten.html +1 -0
- package/conformance/cases/write-flatten.meta +4 -0
- package/conformance/cases/write-flatten.write.json +4 -0
- package/conformance/cases/write-list-empty-no-seed.data.json +1 -0
- package/conformance/cases/write-list-empty-no-seed.error.json +8 -0
- package/conformance/cases/write-list-empty-no-seed.html +1 -0
- package/conformance/cases/write-list-empty-no-seed.meta +4 -0
- package/conformance/cases/write-list-grow-from-template.after.html +1 -0
- package/conformance/cases/write-list-grow-from-template.after.json +5 -0
- package/conformance/cases/write-list-grow-from-template.data.json +1 -0
- package/conformance/cases/write-list-grow-from-template.html +1 -0
- package/conformance/cases/write-list-grow-from-template.meta +4 -0
- package/conformance/cases/write-list-grow-from-template.write.json +4 -0
- package/conformance/cases/write-list-object-clone.after.html +1 -0
- package/conformance/cases/write-list-object-clone.after.json +12 -0
- package/conformance/cases/write-list-object-clone.data.json +1 -0
- package/conformance/cases/write-list-object-clone.html +1 -0
- package/conformance/cases/write-list-object-clone.meta +4 -0
- package/conformance/cases/write-list-object-clone.write.json +4 -0
- package/conformance/cases/write-list-scalar-grow.after.html +4 -0
- package/conformance/cases/write-list-scalar-grow.after.json +7 -0
- package/conformance/cases/write-list-scalar-grow.data.json +1 -0
- package/conformance/cases/write-list-scalar-grow.html +4 -0
- package/conformance/cases/write-list-scalar-grow.meta +4 -0
- package/conformance/cases/write-list-scalar-grow.write.json +4 -0
- package/conformance/cases/write-list-scalar-shrink.after.html +4 -0
- package/conformance/cases/write-list-scalar-shrink.after.json +5 -0
- package/conformance/cases/write-list-scalar-shrink.data.json +1 -0
- package/conformance/cases/write-list-scalar-shrink.html +4 -0
- package/conformance/cases/write-list-scalar-shrink.meta +4 -0
- package/conformance/cases/write-list-scalar-shrink.write.json +4 -0
- package/conformance/cases/write-list-shrink-zero.after.html +1 -0
- package/conformance/cases/write-list-shrink-zero.after.json +3 -0
- package/conformance/cases/write-list-shrink-zero.data.json +1 -0
- package/conformance/cases/write-list-shrink-zero.html +1 -0
- package/conformance/cases/write-list-shrink-zero.meta +4 -0
- package/conformance/cases/write-list-shrink-zero.write.json +4 -0
- package/conformance/cases/write-no-data-kept.after.html +1 -0
- package/conformance/cases/write-no-data-kept.after.json +3 -0
- package/conformance/cases/write-no-data-kept.data.json +1 -0
- package/conformance/cases/write-no-data-kept.html +1 -0
- package/conformance/cases/write-no-data-kept.meta +4 -0
- package/conformance/cases/write-no-data-kept.write.json +4 -0
- package/conformance/cases/write-no-rules-tag.data.json +1 -0
- package/conformance/cases/write-no-rules-tag.error.json +6 -0
- package/conformance/cases/write-no-rules-tag.html +1 -0
- package/conformance/cases/write-no-rules-tag.meta +4 -0
- package/conformance/cases/write-noop.after.html +3 -0
- package/conformance/cases/write-noop.after.json +3 -0
- package/conformance/cases/write-noop.data.json +1 -0
- package/conformance/cases/write-noop.html +3 -0
- package/conformance/cases/write-noop.meta +4 -0
- package/conformance/cases/write-noop.write.json +4 -0
- package/conformance/cases/write-outerhtml-unchanged.after.html +1 -0
- package/conformance/cases/write-outerhtml-unchanged.after.json +3 -0
- package/conformance/cases/write-outerhtml-unchanged.data.json +1 -0
- package/conformance/cases/write-outerhtml-unchanged.html +1 -0
- package/conformance/cases/write-outerhtml-unchanged.meta +4 -0
- package/conformance/cases/write-outerhtml-unchanged.write.json +4 -0
- package/conformance/cases/write-readonly-changed.data.json +1 -0
- package/conformance/cases/write-readonly-changed.error.json +6 -0
- package/conformance/cases/write-readonly-changed.html +1 -0
- package/conformance/cases/write-readonly-changed.meta +4 -0
- package/conformance/cases/write-readonly-unchanged.after.html +1 -0
- package/conformance/cases/write-readonly-unchanged.after.json +4 -0
- package/conformance/cases/write-readonly-unchanged.data.json +1 -0
- package/conformance/cases/write-readonly-unchanged.html +1 -0
- package/conformance/cases/write-readonly-unchanged.meta +4 -0
- package/conformance/cases/write-readonly-unchanged.write.json +4 -0
- package/conformance/cases/write-refused-animate.data.json +1 -0
- package/conformance/cases/write-refused-animate.error.json +11 -0
- package/conformance/cases/write-refused-animate.html +1 -0
- package/conformance/cases/write-refused-animate.meta +4 -0
- package/conformance/cases/write-refused-attr-name.data.json +1 -0
- package/conformance/cases/write-refused-attr-name.error.json +11 -0
- package/conformance/cases/write-refused-attr-name.html +1 -0
- package/conformance/cases/write-refused-attr-name.meta +4 -0
- package/conformance/cases/write-refused-data-url.data.json +1 -0
- package/conformance/cases/write-refused-data-url.error.json +11 -0
- package/conformance/cases/write-refused-data-url.html +1 -0
- package/conformance/cases/write-refused-data-url.meta +4 -0
- package/conformance/cases/write-refused-innerhtml.data.json +1 -0
- package/conformance/cases/write-refused-innerhtml.error.json +11 -0
- package/conformance/cases/write-refused-innerhtml.html +1 -0
- package/conformance/cases/write-refused-innerhtml.meta +4 -0
- package/conformance/cases/write-refused-javascript-url.data.json +1 -0
- package/conformance/cases/write-refused-javascript-url.error.json +11 -0
- package/conformance/cases/write-refused-javascript-url.html +1 -0
- package/conformance/cases/write-refused-javascript-url.meta +4 -0
- package/conformance/cases/write-refused-list-removes-rules-tag.data.json +1 -0
- package/conformance/cases/write-refused-list-removes-rules-tag.error.json +11 -0
- package/conformance/cases/write-refused-list-removes-rules-tag.html +1 -0
- package/conformance/cases/write-refused-list-removes-rules-tag.meta +4 -0
- package/conformance/cases/write-refused-multiple.data.json +1 -0
- package/conformance/cases/write-refused-multiple.error.json +15 -0
- package/conformance/cases/write-refused-multiple.html +1 -0
- package/conformance/cases/write-refused-multiple.meta +4 -0
- package/conformance/cases/write-refused-onclick.data.json +1 -0
- package/conformance/cases/write-refused-onclick.error.json +11 -0
- package/conformance/cases/write-refused-onclick.html +1 -0
- package/conformance/cases/write-refused-onclick.meta +4 -0
- package/conformance/cases/write-refused-rules-tag-ancestor.data.json +1 -0
- package/conformance/cases/write-refused-rules-tag-ancestor.error.json +11 -0
- package/conformance/cases/write-refused-rules-tag-ancestor.html +1 -0
- package/conformance/cases/write-refused-rules-tag-ancestor.meta +4 -0
- package/conformance/cases/write-refused-script.data.json +1 -0
- package/conformance/cases/write-refused-script.error.json +11 -0
- package/conformance/cases/write-refused-script.html +1 -0
- package/conformance/cases/write-refused-script.meta +4 -0
- package/conformance/cases/write-shape-mismatch.data.json +1 -0
- package/conformance/cases/write-shape-mismatch.error.json +12 -0
- package/conformance/cases/write-shape-mismatch.html +1 -0
- package/conformance/cases/write-shape-mismatch.meta +4 -0
- package/conformance/cases/write-text-basic.after.html +3 -0
- package/conformance/cases/write-text-basic.after.json +3 -0
- package/conformance/cases/write-text-basic.data.json +1 -0
- package/conformance/cases/write-text-basic.html +3 -0
- package/conformance/cases/write-text-basic.meta +4 -0
- package/conformance/cases/write-text-basic.write.json +4 -0
- package/conformance/cases/write-text-escape.after.html +1 -0
- package/conformance/cases/write-text-escape.after.json +3 -0
- package/conformance/cases/write-text-escape.data.json +1 -0
- package/conformance/cases/write-text-escape.html +1 -0
- package/conformance/cases/write-text-escape.meta +4 -0
- package/conformance/cases/write-text-escape.write.json +4 -0
- package/conformance/cases/write-text-null-clears.after.html +1 -0
- package/conformance/cases/write-text-null-clears.after.json +3 -0
- package/conformance/cases/write-text-null-clears.data.json +1 -0
- package/conformance/cases/write-text-null-clears.html +1 -0
- package/conformance/cases/write-text-null-clears.meta +4 -0
- package/conformance/cases/write-text-null-clears.write.json +4 -0
- package/conformance/cases/write-type-attribute.after.html +1 -0
- package/conformance/cases/write-type-attribute.after.json +3 -0
- package/conformance/cases/write-type-attribute.data.json +1 -0
- package/conformance/cases/write-type-attribute.html +1 -0
- package/conformance/cases/write-type-attribute.meta +4 -0
- package/conformance/cases/write-type-attribute.write.json +4 -0
- package/conformance/cases/write-unknown-key.data.json +1 -0
- package/conformance/cases/write-unknown-key.error.json +11 -0
- package/conformance/cases/write-unknown-key.html +3 -0
- package/conformance/cases/write-unknown-key.meta +4 -0
- package/conformance/cases/write-unmatched.data.json +1 -0
- package/conformance/cases/write-unmatched.error.json +14 -0
- package/conformance/cases/write-unmatched.html +1 -0
- package/conformance/cases/write-unmatched.meta +4 -0
- package/conformance/write-policy.json +78 -0
- package/dist/hyper-html-api.engine.min.js +1 -1
- package/dist/hyper-html-api.min.js +6 -4
- package/dist/hyper-html-api.upgrade.min.js +2 -2
- package/package.json +4 -3
- package/packed-contract.json +1 -1
- package/src/adapters/cheerio.js +105 -10
- package/src/engine/apply.js +2 -0
- package/src/engine/errors.js +25 -0
- package/src/engine/index.js +3 -0
- package/src/engine/splice.js +118 -0
- package/src/engine/write-plan.js +52 -0
- package/src/engine/write-policy.js +136 -0
- package/src/lib/content-dom.js +4 -2
- package/src/write.js +29 -0
- package/conformance/cases/props-type-is-node-type.expected.json +0 -5
- package/conformance/cases/props-type-is-node-type.meta +0 -11
- /package/conformance/cases/{props-type-is-node-type.html → props-type-is-attribute.html} +0 -0
- /package/conformance/cases/{props-type-is-node-type.parsed.json → props-type-is-attribute.parsed.json} +0 -0
- /package/conformance/cases/{props-type-is-node-type.rules → props-type-is-attribute.rules} +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { POLICY_TOKENS } from '../lib/region-capabilities.js'
|
|
2
|
+
import { WriteRefused } from './errors.js'
|
|
3
|
+
|
|
4
|
+
export const WRITE_POLICY = Object.freeze({
|
|
5
|
+
refusedRuleForms: ['innerHTML', 'outerHTML'],
|
|
6
|
+
refusedTargets: ['script', 'style', 'template', 'noscript', 'iframe', 'object', 'embed', 'xmp', 'plaintext', 'noembed', 'noframes', 'meta', 'link', 'base', 'animate', 'set', 'animatemotion', 'animatetransform'],
|
|
7
|
+
refusedAttributes: [
|
|
8
|
+
'style', 'srcdoc', 'is', 'http-equiv', 'sandbox', 'allow', 'nonce', 'integrity', 'charset',
|
|
9
|
+
'htmlclaytoken', 'htmlclayid', 'data-rules-name', 'data-rules-version', 'clay', 'contenteditable',
|
|
10
|
+
...POLICY_TOKENS,
|
|
11
|
+
],
|
|
12
|
+
refusedAttributePrefixes: ['on'],
|
|
13
|
+
urlAttributes: ['href', 'src', 'srcset', 'action', 'formaction', 'poster', 'cite', 'data', 'ping', 'background', 'longdesc', 'usemap', 'xlink:href', 'manifest', 'codebase', 'icon'],
|
|
14
|
+
refusedSchemes: ['javascript', 'vbscript', 'data'],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const TEXT_PROPS = new Set(['textContent', 'innerText'])
|
|
18
|
+
|
|
19
|
+
export function urlScheme(value) {
|
|
20
|
+
const cleaned = String(value).replace(/^[\u0000- ]+|[\u0000- ]+$/g, '').replace(/[\t\n\r]/g, '')
|
|
21
|
+
const m = cleaned.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/)
|
|
22
|
+
return m ? m[1].toLowerCase() : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const VALID_ATTR_NAME = /^[^\t\n\f\r "'>/=\u0000-\u001f\u007f]+$/
|
|
26
|
+
|
|
27
|
+
export function checkAttribute(name, value, policy = WRITE_POLICY) {
|
|
28
|
+
if (!VALID_ATTR_NAME.test(String(name))) return `attribute name ${JSON.stringify(String(name))} is not valid`
|
|
29
|
+
const lower = String(name).toLowerCase()
|
|
30
|
+
if (policy.refusedAttributePrefixes.some((p) => lower.startsWith(p))) return `attribute "${lower}" can run script`
|
|
31
|
+
if (policy.refusedAttributes.includes(lower)) return `attribute "${lower}" is not content`
|
|
32
|
+
if (policy.urlAttributes.includes(lower) && value != null) {
|
|
33
|
+
const urls = lower === 'srcset'
|
|
34
|
+
? String(value).split(',').map((c) => c.trim().split(/\s+/)[0]).filter(Boolean)
|
|
35
|
+
: [String(value)]
|
|
36
|
+
for (const url of urls) {
|
|
37
|
+
const scheme = urlScheme(url)
|
|
38
|
+
if (scheme && policy.refusedSchemes.includes(scheme)) return `"${scheme}:" URLs are not allowed in "${lower}"`
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function tagOf(adapter, node) {
|
|
45
|
+
const t = adapter.prop(node, 'tagName')
|
|
46
|
+
return t ? String(t).toLowerCase() : ''
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function describe(adapter, node) {
|
|
50
|
+
const tag = tagOf(adapter, node) || 'node'
|
|
51
|
+
const id = adapter.attr(node, 'id')
|
|
52
|
+
return id ? `<${tag}#${id}>` : `<${tag}>`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function refusedAncestor(adapter, node, policy) {
|
|
56
|
+
let cur = node
|
|
57
|
+
while (cur) {
|
|
58
|
+
const tag = tagOf(adapter, cur)
|
|
59
|
+
if (tag && policy.refusedTargets.includes(tag)) return tag
|
|
60
|
+
cur = adapter.parent(cur)
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function holdsRulesTag(adapter, node) {
|
|
66
|
+
return adapter.find(node, 'script[data-rules-name]', { includeRulesTag: true, templateAttr: null }).length > 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isOrHoldsRulesTag(adapter, node) {
|
|
70
|
+
if (tagOf(adapter, node) === 'script' && adapter.attr(node, 'data-rules-name') != null) return true
|
|
71
|
+
return holdsRulesTag(adapter, node)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Wraps an adapter so every write is checked against the policy first. A write
|
|
75
|
+
// that breaks it is skipped and recorded; call assertClean() after apply() to
|
|
76
|
+
// throw one WriteRefused listing every violation.
|
|
77
|
+
export function guardAdapter(adapter, policy = WRITE_POLICY) {
|
|
78
|
+
const refusals = []
|
|
79
|
+
const refuse = (node, reason) => {
|
|
80
|
+
refusals.push({ target: describe(adapter, node), reason })
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
const allowTarget = (node) => {
|
|
84
|
+
const bad = refusedAncestor(adapter, node, policy)
|
|
85
|
+
return bad ? refuse(node, `inside <${bad}>, which is not content`) : true
|
|
86
|
+
}
|
|
87
|
+
const allowText = (node) => {
|
|
88
|
+
if (!allowTarget(node)) return false
|
|
89
|
+
if (holdsRulesTag(adapter, node)) return refuse(node, 'a text write here would delete the data rules tag')
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
const allowAttr = (node, name, value) => {
|
|
93
|
+
if (!allowTarget(node)) return false
|
|
94
|
+
const reason = checkAttribute(name, value, policy)
|
|
95
|
+
return reason ? refuse(node, reason) : true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const guarded = {
|
|
99
|
+
...adapter,
|
|
100
|
+
text(node, value) {
|
|
101
|
+
if (value === undefined) return adapter.text(node)
|
|
102
|
+
if (allowText(node)) adapter.text(node, value)
|
|
103
|
+
},
|
|
104
|
+
attr(node, name, value) {
|
|
105
|
+
if (value === undefined) return adapter.attr(node, name)
|
|
106
|
+
if (allowAttr(node, name, value)) adapter.attr(node, name, value)
|
|
107
|
+
},
|
|
108
|
+
prop(node, name, value) {
|
|
109
|
+
if (value === undefined) return adapter.prop(node, name)
|
|
110
|
+
if (policy.refusedRuleForms.includes(name)) return refuse(node, `"@${name}" writes HTML, only text is allowed`)
|
|
111
|
+
if (TEXT_PROPS.has(name)) {
|
|
112
|
+
if (allowText(node)) adapter.prop(node, name, value)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
const attrName = name === 'className' ? 'class' : name
|
|
116
|
+
if (allowAttr(node, attrName, value)) adapter.prop(node, name, value)
|
|
117
|
+
},
|
|
118
|
+
remove(node) {
|
|
119
|
+
if (isOrHoldsRulesTag(adapter, node)) {
|
|
120
|
+
refuse(node, 'removing this would delete the data rules tag')
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
adapter.remove(node)
|
|
124
|
+
},
|
|
125
|
+
replaceWith(node, html) {
|
|
126
|
+
if (html === adapter.prop(node, 'outerHTML')) return node
|
|
127
|
+
refuse(node, '"@outerHTML" writes HTML, only text is allowed')
|
|
128
|
+
return node
|
|
129
|
+
},
|
|
130
|
+
refusals,
|
|
131
|
+
assertClean() {
|
|
132
|
+
if (refusals.length) throw new WriteRefused(refusals.slice())
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
return guarded
|
|
136
|
+
}
|
package/src/lib/content-dom.js
CHANGED
|
@@ -29,8 +29,10 @@ function matchesWithin(node, selector, boundary, inherit) {
|
|
|
29
29
|
|
|
30
30
|
function importTree(source, targetDocument, capability, capabilityMatch, exclude, boundary, inherit, maps) {
|
|
31
31
|
if (source.nodeType === 1 && (
|
|
32
|
-
(inherit
|
|
33
|
-
|
|
32
|
+
(inherit
|
|
33
|
+
? hasCapability(source, capability)
|
|
34
|
+
: matchesWithin(source, capabilityMatch, boundary, false)) ||
|
|
35
|
+
(exclude && (inherit && source !== boundary ? source.matches(exclude) : matchesWithin(source, exclude, boundary, inherit)))
|
|
34
36
|
)) return null
|
|
35
37
|
const copy = targetDocument.importNode(source, false)
|
|
36
38
|
maps.cloneToLive.set(copy, source)
|
package/src/write.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import cheerioAdapter from './adapters/cheerio.js'
|
|
2
|
+
import { apply } from './engine/apply.js'
|
|
3
|
+
import { findRulesIn } from './engine/rules-tag.js'
|
|
4
|
+
import { guardAdapter } from './engine/write-policy.js'
|
|
5
|
+
import { planWrite } from './engine/write-plan.js'
|
|
6
|
+
import { trackAdapter, spliceDocument } from './engine/splice.js'
|
|
7
|
+
import { NoRulesTag, WriteRejected } from './engine/errors.js'
|
|
8
|
+
|
|
9
|
+
// Applies a JSON body to a document's source through the document's own rules
|
|
10
|
+
// tag, under the content-only policy. `load` is cheerio.load, passed in so this
|
|
11
|
+
// package keeps cheerio out of its browser bundles. Returns the new source and
|
|
12
|
+
// whether anything changed. Throws NoRulesTag, WriteRejected, WriteRefused,
|
|
13
|
+
// ShapeMismatch or EmptyListInsert, always before producing any output.
|
|
14
|
+
export function writeDocument(load, src, data, { token = 'api' } = {}) {
|
|
15
|
+
const $ = load(src, { sourceCodeLocationInfo: true })
|
|
16
|
+
const root = $.root()
|
|
17
|
+
const found = findRulesIn(cheerioAdapter, root, token)
|
|
18
|
+
if (!found) throw new NoRulesTag(token)
|
|
19
|
+
const { rules } = found
|
|
20
|
+
const plan = planWrite(cheerioAdapter, root, rules, data)
|
|
21
|
+
if (plan.unknownKeys.length || plan.unmatched.length) throw new WriteRejected(plan.unknownKeys, plan.unmatched)
|
|
22
|
+
const tracker = trackAdapter(cheerioAdapter)
|
|
23
|
+
const guarded = guardAdapter(tracker)
|
|
24
|
+
apply(guarded, root, rules, data, { templateAttr: 'cms-template' })
|
|
25
|
+
guarded.assertClean()
|
|
26
|
+
if (tracker.dirty.size === 0) return { html: src, changed: false, spliced: true }
|
|
27
|
+
const { html, spliced } = spliceDocument(load, src, $, tracker)
|
|
28
|
+
return { html, changed: html !== src, spliced }
|
|
29
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
tier: 3
|
|
2
|
-
face: query
|
|
3
|
-
expect: ok
|
|
4
|
-
skip: htmlclay=ships @type fixed; fixed behavior asserted in htmlclay dataapi/props_test.go
|
|
5
|
-
note: @type is NOT the type attribute. `type` is on the DOM property whitelist, and domhandler
|
|
6
|
-
note: nodes already carry a `type` field naming the node kind, so cheerio's prop() returns that
|
|
7
|
-
note: instead: "tag" for a normal element, "script" for a script, "style" for a style. Three
|
|
8
|
-
note: values, none of them the author's type="text".
|
|
9
|
-
note: DIVERGENCE, direction JS-only. This case pins the WART so an upstream fix gets noticed.
|
|
10
|
-
note: htmlclay deliberately does not reproduce it and returns the type attribute, because that API
|
|
11
|
-
note: has no existing htmlclay users and preserving the bug would buy nothing.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|