Skip to content

Commit

Permalink
Add support for SVG
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 17, 2018
1 parent aa79bdf commit c752e03
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 392 deletions.
225 changes: 84 additions & 141 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,138 +1,135 @@
'use strict'

var xtend = require('xtend')
var html = require('property-information/html')
var svg = require('property-information/svg')
var find = require('property-information/find')
var toH = require('hast-to-hyperscript')
var NS = require('web-namespaces')
var ns = require('web-namespaces')
var zwitch = require('zwitch')
var mapz = require('mapz')

module.exports = transform

var own = {}.hasOwnProperty
var one = zwitch('type')
var all = mapz(one, {key: 'children', indices: false})
var ignoredSpaces = ['svg', 'html']

var customProps = [
'sourceCodeLocation',
'childNodes',
'content',
'parentNode',
'namespaceURI'
]
var one = zwitch('type')

one.handlers.root = root
one.handlers.element = element
one.handlers.text = text
one.handlers.comment = comment
one.handlers.doctype = doctype

/* Map of tag-names starting new namespaces. */
var namespaces = {
math: NS.mathml,
svg: NS.svg
// Transform a tree from HAST to Parse5’s AST.
function transform(tree, space) {
return one(tree, space === 'svg' ? svg : html)
}

/* Map of attributes with namespaces. */
var attributeSpaces = {
'xlink:actuate': {prefix: 'xlink', name: 'actuate', namespace: NS.xlink},
'xlink:arcrole': {prefix: 'xlink', name: 'arcrole', namespace: NS.xlink},
'xlink:href': {prefix: 'xlink', name: 'href', namespace: NS.xlink},
'xlink:role': {prefix: 'xlink', name: 'role', namespace: NS.xlink},
'xlink:show': {prefix: 'xlink', name: 'show', namespace: NS.xlink},
'xlink:title': {prefix: 'xlink', name: 'title', namespace: NS.xlink},
'xlink:type': {prefix: 'xlink', name: 'type', namespace: NS.xlink},
'xml:base': {prefix: 'xml', name: 'base', namespace: NS.xml},
'xml:lang': {prefix: 'xml', name: 'lang', namespace: NS.xml},
'xml:space': {prefix: 'xml', name: 'space', namespace: NS.xml},
xmlns: {prefix: '', name: 'xmlns', namespace: NS.xmlns},
'xmlns:xlink': {prefix: 'xmlns', name: 'xlink', namespace: NS.xmlns}
function root(node, schema) {
var data = node.data || {}
var mode = data.quirksMode ? 'quirks' : 'no-quirks'

return patch(node, {nodeName: '#document', mode: mode}, schema)
}

/* Transform a tree from HAST to Parse5’s AST. */
function transform(tree) {
return patch(one(tree), null, NS.html)
function fragment(node, schema) {
return patch(node, {nodeName: '#document-fragment'}, schema)
}

function root(node) {
var data = node.data || {}
var qs = own.call(data, 'quirksMode') ? Boolean(data.quirksMode) : false
function doctype(node, schema) {
return patch(
node,
{
nodeName: '#documentType',
name: node.name,
publicId: node.public || '',
systemId: node.system || ''
},
schema
)
}

return {
nodeName: '#document',
mode: qs ? 'quirks' : 'no-quirks',
childNodes: all(node)
}
function text(node, schema) {
return patch(node, {nodeName: '#text', value: node.value}, schema)
}

function comment(node, schema) {
return patch(node, {nodeName: '#comment', data: node.value}, schema)
}

function element(node) {
var shallow = xtend(node)
function element(node, schema) {
var space = schema.space
var shallow = xtend(node, {children: []})

shallow.children = []
return toH(h, shallow, {space: space})

return toH(function(name, attrs) {
function h(name, attrs) {
var values = []
var content
var p5
var value
var key
var info
var pos

for (key in attrs) {
info = find(schema, key)
value = {name: key, value: attrs[key]}

if (own.call(attributeSpaces, key)) {
value = xtend(value, attributeSpaces[key])
if (info.space && ignoredSpaces.indexOf(info.space) === -1) {
pos = key.indexOf(':')

if (pos === -1) {
value.prefix = ''
} else {
value.name = key.slice(pos + 1)
value.prefix = key.slice(0, pos)
}
value.namespace = ns[info.space]
}

values.push(value)
}

p5 = patch(node, {nodeName: name, tagName: name, attrs: values}, schema)

if (name === 'template') {
content = transform(shallow.content)
delete content.mode
content.nodeName = '#document-fragment'
p5.content = fragment(shallow.content, schema)
}

return wrap(
node,
{
nodeName: node.tagName,
tagName: node.tagName,
attrs: values,
childNodes: node.children ? all(node) : []
},
content
)
}, shallow)
return p5
}
}

function doctype(node) {
return wrap(node, {
nodeName: '#documentType',
name: node.name,
publicId: node.public || '',
systemId: node.system || ''
})
}
// Patch specific properties.
function patch(node, p5, parentSchema) {
var schema = parentSchema
var position = node.position
var children = node.children
var childNodes = []
var length = children ? children.length : 0
var index = -1
var child

if (node.type === 'element') {
if (schema.space === 'html' && node.tagName === 'svg') {
schema = svg
}

function text(node) {
return wrap(node, {
nodeName: '#text',
value: node.value
})
}
p5.namespaceURI = ns[schema.space]
}

function comment(node) {
return wrap(node, {
nodeName: '#comment',
data: node.value
})
}
while (++index < length) {
child = one(children[index], schema)
child.parentNode = p5
childNodes[index] = child
}

/* Patch position. */
function wrap(node, ast, content) {
var position = node.position
if (node.type === 'element' || node.type === 'root') {
p5.childNodes = childNodes
}

if (position && position.start && position.end) {
ast.sourceCodeLocation = {
p5.sourceCodeLocation = {
startLine: position.start.line,
startCol: position.start.column,
startOffset: position.start.offset,
Expand All @@ -142,59 +139,5 @@ function wrap(node, ast, content) {
}
}

if (content) {
ast.content = content
}

return ast
}

/* Patch a tree recursively, by adding namespaces
* and parent references where needed. */
function patch(node, parent, ns) {
var location = node.sourceCodeLocation
var children = node.childNodes
var name = node.tagName
var replacement = {}
var length
var index
var key

for (key in node) {
if (customProps.indexOf(key) === -1) {
replacement[key] = node[key]
}
}

if (own.call(namespaces, name)) {
ns = namespaces[name]
}

if (own.call(replacement, 'tagName')) {
replacement.namespaceURI = ns
}

if (children) {
replacement.childNodes = children
length = children.length
index = -1

while (++index < length) {
children[index] = patch(children[index], replacement, ns)
}
}

if (name === 'template') {
replacement.content = patch(node.content, null, ns)
}

if (parent) {
replacement.parentNode = parent
}

if (location) {
replacement.sourceCodeLocation = location
}

return replacement
return p5
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
"index.js"
],
"dependencies": {
"hast-to-hyperscript": "^4.0.0",
"mapz": "^1.0.0",
"hast-to-hyperscript": "^5.0.0",
"property-information": "^4.0.0",
"web-namespaces": "^1.0.0",
"xtend": "^4.0.1",
"zwitch": "^1.0.0"
},
"devDependencies": {
"browserify": "^16.0.0",
"esmangle": "^1.0.1",
"json-stringify-safe": "^5.0.1",
"nyc": "^12.0.0",
"parse5": "^5.0.0",
"prettier": "^1.13.5",
Expand Down
15 changes: 4 additions & 11 deletions test/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ var parse5 = require('parse5')
var toParse5 = require('..')

test('comment', function(t) {
var node = parse5.parseFragment('<!--Alpha-->')
var actual = toParse5({type: 'comment', value: 'Alpha'})
var expected = parse5.parseFragment('<!--Alpha-->').childNodes[0]

node = node.childNodes[0]
delete node.parentNode
delete expected.parentNode

t.deepEqual(
toParse5({
type: 'comment',
value: 'Alpha'
}),
node,
'should transform comments'
)
t.deepEqual(actual, expected, 'should transform comments')

t.end()
})
45 changes: 21 additions & 24 deletions test/doctype.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,33 @@ var parse5 = require('parse5')
var toParse5 = require('..')

test('doctype', function(t) {
var node = parse5.parse(
'<!DOCTYPE html SYSTEM "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd">'
)

node = node.childNodes[0]
delete node.parentNode

t.deepEqual(
toParse5({
t.test('should transform a doctype (legacy)', function(st) {
var actual = toParse5({
type: 'doctype',
name: 'html',
system: 'http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd'
}),
node,
'should transform a doctype (legacy)'
)
})
var expected = parse5.parse(
'<!DOCTYPE html SYSTEM "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd">'
).childNodes[0]

node = parse5.parse('<!doctype html>')
delete expected.parentNode

node = node.childNodes[0]
delete node.parentNode
st.deepEqual(actual, expected)

t.deepEqual(
toParse5({
type: 'doctype',
name: 'html'
}),
node,
'should transform a doctype (modern)'
)
st.end()
})

t.test('should transform a doctype (modern)', function(st) {
var actual = toParse5({type: 'doctype', name: 'html'})
var expected = parse5.parse('<!doctypehtml>').childNodes[0]

delete expected.parentNode

st.deepEqual(actual, expected)

st.end()
})

t.end()
})
Loading

0 comments on commit c752e03

Please sign in to comment.