diff --git a/index.js b/index.js
index de6fc07..95fc8ee 100644
--- a/index.js
+++ b/index.js
@@ -1,5 +1,6 @@
var attrToProp = require('hyperscript-attribute-to-property')
+
var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4
var ATTR_KEY = 5, ATTR_KEY_W = 6
var ATTR_VALUE_W = 7, ATTR_VALUE = 8
@@ -7,21 +8,26 @@ var ATTR_VALUE_SQ = 9, ATTR_VALUE_DQ = 10
var ATTR_EQ = 11, ATTR_BREAK = 12
var COMMENT = 13
+
module.exports = function (h, opts) {
- if (!opts) opts = {}
+ if (!opts) opts = { }
+
var concat = opts.concat || function (a, b) {
return String(a) + String(b)
}
- if (opts.attrToProp !== false) {
+
+ if (opts.attrToProp !== false)
h = attrToProp(h)
- }
+
return function (strings) {
- var state = TEXT, reg = ''
+
+ var state = TEXT, reg = '', isSelfClosing = false
var arglen = arguments.length
var parts = []
for (var i = 0; i < strings.length; i++) {
+
if (i < arglen - 1) {
var arg = arguments[i+1]
var p = parse(strings[i])
@@ -43,138 +49,191 @@ module.exports = function (h, opts) {
p.push([ VAR, xstate, arg ])
}
parts.push.apply(parts, p)
- } else parts.push.apply(parts, parse(strings[i]))
+ } else {
+ parts.push.apply(parts, parse(strings[i]))
+ }
}
- var tree = [null,{},[]]
- var stack = [[tree,-1]]
+ var tree = [ null, {}, [] ]
+ var stack = [ [ tree, -1 ] ]
+
for (var i = 0; i < parts.length; i++) {
var cur = stack[stack.length-1][0]
- var p = parts[i], s = p[0]
- if (s === OPEN && /^\//.test(p[1])) {
+ var p = parts[i], state = p[0]
+
+ if (state === OPEN && /^\//.test(p[1])) {
var ix = stack[stack.length-1][1]
if (stack.length > 1) {
stack.pop()
- stack[stack.length-1][0][2][ix] = h(
- cur[0], cur[1], cur[2].length ? cur[2] : undefined
- )
+ stack[stack.length-1][0][2][ix] = h(cur[0], cur[1], cur[2].length ? cur[2] : undefined)
}
- } else if (s === OPEN) {
+
+ } else if (state === OPEN) {
var c = [p[1],{},[]]
cur[2].push(c)
stack.push([c,cur[2].length-1])
- } else if (s === ATTR_KEY || (s === VAR && p[1] === ATTR_KEY)) {
+
+ } else if (state === ATTR_KEY || (state === VAR && p[1] === ATTR_KEY)) {
var key = ''
var copyKey
for (; i < parts.length; i++) {
if (parts[i][0] === ATTR_KEY) {
key = concat(key, parts[i][1])
+
} else if (parts[i][0] === VAR && parts[i][1] === ATTR_KEY) {
if (typeof parts[i][2] === 'object' && !key) {
- for (copyKey in parts[i][2]) {
- if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey]) {
+ for (copyKey in parts[i][2])
+ if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey])
cur[1][copyKey] = parts[i][2][copyKey]
- }
- }
+
} else {
key = concat(key, parts[i][2])
}
- } else break
+
+ } else {
+ break
+ }
}
- if (parts[i][0] === ATTR_EQ) i++
+
+ if (parts[i][0] === ATTR_EQ)
+ i++
+
var j = i
+
for (; i < parts.length; i++) {
if (parts[i][0] === ATTR_VALUE || parts[i][0] === ATTR_KEY) {
- if (!cur[1][key]) cur[1][key] = strfn(parts[i][1])
- else parts[i][1]==="" || (cur[1][key] = concat(cur[1][key], parts[i][1]));
- } else if (parts[i][0] === VAR
- && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) {
- if (!cur[1][key]) cur[1][key] = strfn(parts[i][2])
- else parts[i][2]==="" || (cur[1][key] = concat(cur[1][key], parts[i][2]));
+ if (!cur[1][key])
+ cur[1][key] = strfn(parts[i][1])
+ else
+ parts[i][1]==="" || (cur[1][key] = concat(cur[1][key], parts[i][1]));
+
+ } else if (parts[i][0] === VAR && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) {
+ if (!cur[1][key])
+ cur[1][key] = strfn(parts[i][2])
+ else
+ parts[i][2]==="" || (cur[1][key] = concat(cur[1][key], parts[i][2]));
+
} else {
- if (key.length && !cur[1][key] && i === j
- && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) {
+ if (key.length && !cur[1][key] && i === j && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) {
// https://html.spec.whatwg.org/multipage/infrastructure.html#boolean-attributes
// empty string is falsy, not well behaved value in browser
cur[1][key] = key.toLowerCase()
}
- if (parts[i][0] === CLOSE) {
+
+ if (parts[i][0] === CLOSE)
i--
- }
+
break
}
}
- } else if (s === ATTR_KEY) {
+
+ } else if (state === ATTR_KEY) {
cur[1][p[1]] = true
- } else if (s === VAR && p[1] === ATTR_KEY) {
+
+ } else if (state === VAR && p[1] === ATTR_KEY) {
cur[1][p[2]] = true
- } else if (s === CLOSE) {
- if (selfClosing(cur[0]) && stack.length) {
+
+ } else if (state === CLOSE) {
+
+ const isSelfClosing = p[1] || selfClosingVoid(cur[0])
+ //if (selfClosing(cur[0]) && stack.length) {
+ if (isSelfClosing && stack.length) {
var ix = stack[stack.length-1][1]
stack.pop()
- stack[stack.length-1][0][2][ix] = h(
- cur[0], cur[1], cur[2].length ? cur[2] : undefined
- )
+ stack[stack.length-1][0][2][ix] = h(cur[0], cur[1], cur[2].length ? cur[2] : undefined)
}
- } else if (s === VAR && p[1] === TEXT) {
- if (p[2] === undefined || p[2] === null) p[2] = ''
- else if (!p[2]) p[2] = concat('', p[2])
- if (Array.isArray(p[2][0])) {
+
+ } else if (state === VAR && p[1] === TEXT) {
+ if (p[2] === undefined || p[2] === null)
+ p[2] = ''
+ else if (!p[2])
+ p[2] = concat('', p[2])
+
+ if (Array.isArray(p[2][0]))
cur[2].push.apply(cur[2], p[2])
- } else {
+ else
cur[2].push(p[2])
- }
- } else if (s === TEXT) {
+
+ } else if (state === TEXT) {
cur[2].push(p[1])
- } else if (s === ATTR_EQ || s === ATTR_BREAK) {
+
+ } else if (state === ATTR_EQ || state === ATTR_BREAK) {
// no-op
+
} else {
- throw new Error('unhandled: ' + s)
+ throw new Error('unhandled: ' + state)
+
}
}
- if (tree[2].length > 1 && /^\s*$/.test(tree[2][0])) {
+ if (tree[2].length > 1 && /^\s*$/.test(tree[2][0]))
tree[2].shift()
- }
- if (tree[2].length > 2
- || (tree[2].length === 2 && /\S/.test(tree[2][1]))) {
- if (opts.createFragment) return opts.createFragment(tree[2])
+ if (tree[2].length > 2 || (tree[2].length === 2 && /\S/.test(tree[2][1]))) {
+ if (opts.createFragment)
+ return opts.createFragment(tree[2])
+
throw new Error(
'multiple root elements must be wrapped in an enclosing tag'
)
}
- if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string'
- && Array.isArray(tree[2][0][2])) {
+
+ if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string' && Array.isArray(tree[2][0][2]))
tree[2][0] = h(tree[2][0][0], tree[2][0][1], tree[2][0][2])
- }
+
return tree[2][0]
function parse (str) {
- var res = []
- if (state === ATTR_VALUE_W) state = ATTR
+ var res = [ ]
+
+ var isInStyleTag = false
+
+ if (state === ATTR_VALUE_W)
+ state = ATTR
+
for (var i = 0; i < str.length; i++) {
var c = str.charAt(i)
if (state === TEXT && c === '<') {
- if (reg.length) res.push([TEXT, reg])
+ if (reg.length)
+ res.push([TEXT, reg])
reg = ''
state = OPEN
+ isInStyleTag = false
+
} else if (c === '>' && !quot(state) && state !== COMMENT) {
+
if (state === OPEN && reg.length) {
res.push([OPEN,reg])
+
+ if (reg === 'style')
+ isInStyleTag = true
+ else if (reg === '/style')
+ isInStyleTag = false
+
} else if (state === ATTR_KEY) {
res.push([ATTR_KEY,reg])
} else if (state === ATTR_VALUE && reg.length) {
res.push([ATTR_VALUE,reg])
}
- res.push([CLOSE])
- reg = ''
+
+ if (state === TEXT && isInStyleTag) {
+ // the css descendant selector within
+ reg += c
+ } else {
+ res.push([CLOSE, isSelfClosing])
+ isSelfClosing = false
+ reg = ''
+ }
+
state = TEXT
+
} else if (state === COMMENT && /-$/.test(reg) && c === '-') {
if (opts.comments) {
res.push([ATTR_VALUE,reg.substr(0, reg.length - 1)])
}
reg = ''
+ isSelfClosing = true
state = TEXT
} else if (state === OPEN && /^!--$/.test(reg)) {
if (opts.comments) {
@@ -185,11 +244,18 @@ module.exports = function (h, opts) {
} else if (state === TEXT || state === COMMENT) {
reg += c
} else if (state === OPEN && c === '/' && reg.length) {
- // no-op, self closing tag without a space
+ // self closing tag without a space
+ isSelfClosing = true
+
} else if (state === OPEN && /\s/.test(c)) {
- if (reg.length) {
+ if (reg.length)
res.push([OPEN, reg])
- }
+
+ if (reg === 'style')
+ isInStyleTag = true
+ else if (reg === '/style')
+ isInStyleTag = false
+
reg = ''
state = ATTR
} else if (state === OPEN) {
@@ -198,7 +264,8 @@ module.exports = function (h, opts) {
state = ATTR_KEY
reg = c
} else if (state === ATTR && /\s/.test(c)) {
- if (reg.length) res.push([ATTR_KEY,reg])
+ if (reg.length)
+ res.push([ATTR_KEY,reg])
res.push([ATTR_BREAK])
} else if (state === ATTR_KEY && /\s/.test(c)) {
res.push([ATTR_KEY,reg])
@@ -208,6 +275,10 @@ module.exports = function (h, opts) {
res.push([ATTR_KEY,reg],[ATTR_EQ])
reg = ''
state = ATTR_VALUE_W
+ } else if (state === ATTR_KEY && c === '/') {
+ isSelfClosing = true
+ reg=''
+ state = ATTR
} else if (state === ATTR_KEY) {
reg += c
} else if ((state === ATTR_KEY_W || state === ATTR) && c === '=') {
@@ -218,7 +289,11 @@ module.exports = function (h, opts) {
if (/[\w-]/.test(c)) {
reg += c
state = ATTR_KEY
- } else state = ATTR
+ } else if (c === '/') {
+ isSelfClosing = true
+ } else {
+ state = ATTR
+ }
} else if (state === ATTR_VALUE_W && c === '"') {
state = ATTR_VALUE_DQ
} else if (state === ATTR_VALUE_W && c === "'") {
@@ -276,6 +351,16 @@ function quot (state) {
return state === ATTR_VALUE_SQ || state === ATTR_VALUE_DQ
}
+//area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
+var voidCloseRE = RegExp('^(' + [
+ 'area', 'base', 'br', 'col', 'command', 'embed',
+ 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param',
+ 'source', 'track', 'wbr'
+].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$')
+
+function selfClosingVoid (tag) { return voidCloseRE.test(tag) }
+
+/*
var closeRE = RegExp('^(' + [
'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'command', 'embed',
'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
@@ -292,4 +377,6 @@ var closeRE = RegExp('^(' + [
'path', 'polygon', 'polyline', 'rect', 'set', 'stop', 'tref', 'use', 'view',
'vkern'
].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$')
+
function selfClosing (tag) { return closeRE.test(tag) }
+*/
diff --git a/package.json b/package.json
index de50b87..509caeb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "hyperx",
- "version": "2.5.4",
+ "name": "hyperx-tmp",
+ "version": "2.5.6",
"description": "tagged template string virtual dom builder",
"main": "index.js",
"scripts": {
@@ -21,8 +21,8 @@
"license": "BSD",
"devDependencies": {
"covert": "^1.1.0",
- "hyperscript": "^1.4.7",
- "tape": "^4.4.0",
+ "hyperscript": "^2.0.2",
+ "tape": "^5.4.0",
"virtual-dom": "^2.1.1"
},
"dependencies": {
@@ -34,10 +34,10 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/substack/hyperx.git"
+ "url": "git+https://github.com/mreinstein/hyperx.git"
},
"bugs": {
- "url": "https://github.com/substack/hyperx/issues"
+ "url": "https://github.com/mreinstein/hyperx/issues"
},
- "homepage": "https://github.com/substack/hyperx#readme"
+ "homepage": "https://github.com/mreinstein/hyperx#readme"
}
diff --git a/test/style.js b/test/style.js
index 7eef5b5..14cd390 100644
--- a/test/style.js
+++ b/test/style.js
@@ -15,3 +15,42 @@ test('style', function (t) {
)
t.end()
})
+
+
+test('embedded style', function (t) {
+ var key = 'type'
+ var value = 'text'
+ var tree = hx``
+ t.equal(
+ vdom.create(tree).toString(),
+ ``
+ )
+ t.end()
+})
+
+test('embedded style with attributes', function (t) {
+ var key = 'type'
+ var value = 'text'
+ var tree = hx``
+ t.equal(
+ vdom.create(tree).toString(),
+ ``
+ )
+ t.end()
+})
diff --git a/test/svg.js b/test/svg.js
index 1ee0fa1..e7f515b 100644
--- a/test/svg.js
+++ b/test/svg.js
@@ -19,3 +19,28 @@ test('svg mixed with html', function (t) {
t.equal(vdom.create(tree).toString(), expected)
t.end()
})
+
+test('svg mixed with html and close / self-closing tags', function (t) {
+ var expected = `