|
| 1 | +'use strict' |
| 2 | + |
| 3 | +var xtend = require('xtend') |
| 4 | +var zwitch = require('zwitch') |
| 5 | +var namespaces = require('web-namespaces') |
| 6 | +var html = require('property-information/html') |
| 7 | +var svg = require('property-information/svg') |
| 8 | +var find = require('property-information/find') |
| 9 | +var spaces = require('space-separated-tokens').stringify |
| 10 | +var commas = require('comma-separated-tokens').stringify |
| 11 | +var position = require('unist-util-position') |
| 12 | + |
| 13 | +module.exports = toXast |
| 14 | + |
| 15 | +var one = zwitch('type') |
| 16 | + |
| 17 | +one.invalid = invalid |
| 18 | +one.unknown = unknown |
| 19 | +one.handlers.root = root |
| 20 | +one.handlers.element = element |
| 21 | +one.handlers.text = text |
| 22 | +one.handlers.comment = comment |
| 23 | +one.handlers.doctype = doctype |
| 24 | + |
| 25 | +function invalid(value) { |
| 26 | + throw new Error('Expected node, not `' + value + '`') |
| 27 | +} |
| 28 | + |
| 29 | +function unknown(value) { |
| 30 | + throw new Error('Cannot transform node of type `' + value.type + '`') |
| 31 | +} |
| 32 | + |
| 33 | +function toXast(tree, options) { |
| 34 | + var opts = typeof options === 'string' ? {space: options} : options || {} |
| 35 | + var space = opts.space === 'svg' ? 'svg' : 'html' |
| 36 | + |
| 37 | + return one(tree, {schema: space === 'svg' ? svg : html, ns: null}) |
| 38 | +} |
| 39 | + |
| 40 | +function root(node, config) { |
| 41 | + return patch(node, {type: 'root'}, config) |
| 42 | +} |
| 43 | + |
| 44 | +function text(node, config) { |
| 45 | + return patch(node, {type: 'text', value: node.value || ''}, config) |
| 46 | +} |
| 47 | + |
| 48 | +function comment(node, config) { |
| 49 | + return patch(node, {type: 'comment', value: node.value || ''}, config) |
| 50 | +} |
| 51 | + |
| 52 | +function doctype(node, config) { |
| 53 | + return patch( |
| 54 | + node, |
| 55 | + { |
| 56 | + type: 'doctype', |
| 57 | + name: node.name || '', |
| 58 | + public: node.public || undefined, |
| 59 | + system: node.system || undefined |
| 60 | + }, |
| 61 | + config |
| 62 | + ) |
| 63 | +} |
| 64 | + |
| 65 | +function element(node, parentConfig) { |
| 66 | + var schema = parentConfig.schema |
| 67 | + var name = node.tagName |
| 68 | + var props = node.properties || {} |
| 69 | + var xmlns = props.xmlns || null |
| 70 | + var ns = namespaces[schema.space] |
| 71 | + var attrs = {} |
| 72 | + var config |
| 73 | + |
| 74 | + if (xmlns) { |
| 75 | + if (xmlns === namespaces.svg) { |
| 76 | + schema = svg |
| 77 | + ns = xmlns |
| 78 | + } else if (xmlns === namespaces.html) { |
| 79 | + schema = html |
| 80 | + ns = xmlns |
| 81 | + } else { |
| 82 | + // We don’t support non-HTML, non-SVG namespaces, so stay in the same. |
| 83 | + } |
| 84 | + } else if (ns === namespaces.html && name === 'svg') { |
| 85 | + schema = svg |
| 86 | + ns = namespaces.svg |
| 87 | + } |
| 88 | + |
| 89 | + if (parentConfig.ns !== ns) { |
| 90 | + attrs.xmlns = ns |
| 91 | + } |
| 92 | + |
| 93 | + config = xtend(parentConfig, {schema: schema, ns: ns}) |
| 94 | + attrs = xtend(attrs, toAttributes(props, config)) |
| 95 | + |
| 96 | + return patch(node, {type: 'element', name: name, attributes: attrs}, config) |
| 97 | +} |
| 98 | + |
| 99 | +function patch(origin, node, config) { |
| 100 | + var pos = origin.position |
| 101 | + var hastChildren = origin.children |
| 102 | + var length |
| 103 | + var children |
| 104 | + var index |
| 105 | + |
| 106 | + if ( |
| 107 | + config.ns === namespaces.html && |
| 108 | + origin.type === 'element' && |
| 109 | + origin.tagName === 'template' |
| 110 | + ) { |
| 111 | + node.children = root(origin.content, config).children |
| 112 | + } else if (origin.type === 'element' || origin.type === 'root') { |
| 113 | + length = hastChildren && hastChildren.length |
| 114 | + children = [] |
| 115 | + index = -1 |
| 116 | + |
| 117 | + while (++index < length) { |
| 118 | + children[index] = one(hastChildren[index], config) |
| 119 | + } |
| 120 | + |
| 121 | + node.children = children |
| 122 | + } |
| 123 | + |
| 124 | + if (pos) { |
| 125 | + node.position = { |
| 126 | + start: position.start(origin), |
| 127 | + end: position.end(origin) |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + return node |
| 132 | +} |
| 133 | + |
| 134 | +function toAttributes(props, config) { |
| 135 | + var attributes = {} |
| 136 | + var value |
| 137 | + var key |
| 138 | + var info |
| 139 | + var name |
| 140 | + |
| 141 | + for (key in props) { |
| 142 | + info = find(config.schema, key) |
| 143 | + name = info.attribute |
| 144 | + value = props[key] |
| 145 | + |
| 146 | + // Ignore nully, false, and `NaN` values, and falsey known booleans. |
| 147 | + if ( |
| 148 | + value === null || |
| 149 | + value === undefined || |
| 150 | + value === false || |
| 151 | + value !== value || |
| 152 | + (info.boolean && !value) |
| 153 | + ) { |
| 154 | + continue |
| 155 | + } |
| 156 | + |
| 157 | + // Accept `array`. |
| 158 | + // Most props are space-separated. |
| 159 | + if (typeof value === 'object' && 'length' in value) { |
| 160 | + value = (info.commaSeparated ? commas : spaces)(value) |
| 161 | + } |
| 162 | + |
| 163 | + // Treat `true` and truthy known booleans. |
| 164 | + if (value === true || info.boolean) { |
| 165 | + value = '' |
| 166 | + } |
| 167 | + |
| 168 | + // Cast everything else to string. |
| 169 | + if (typeof value !== 'string') { |
| 170 | + value = String(value) |
| 171 | + } |
| 172 | + |
| 173 | + attributes[name] = value |
| 174 | + } |
| 175 | + |
| 176 | + return attributes |
| 177 | +} |
0 commit comments