var _ = require('../util')
var Cache = require('../cache')
var templateCache = new Cache(1000)
var idSelectorCache = new Cache(1000)
var map = {
_default: [0, '', ''],
legend: [1, '
']
map.g =
map.defs =
map.symbol =
map.use =
map.image =
map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [
1,
''
]
/**
* Check if a node is a supported template node with a
* DocumentFragment content.
*
* @param {Node} node
* @return {Boolean}
*/
function isRealTemplate (node) {
return _.isTemplate(node) &&
node.content instanceof DocumentFragment
}
var tagRE = /<([\w:]+)/
var entityRE = /&\w+;|\d+;|[\dA-F]+;/
/**
* Convert a string template to a DocumentFragment.
* Determines correct wrapping by tag types. Wrapping
* strategy found in jQuery & component/domify.
*
* @param {String} templateString
* @return {DocumentFragment}
*/
function stringToFragment (templateString) {
// try a cache hit first
var hit = templateCache.get(templateString)
if (hit) {
return hit
}
var frag = document.createDocumentFragment()
var tagMatch = templateString.match(tagRE)
var entityMatch = entityRE.test(templateString)
if (!tagMatch && !entityMatch) {
// text only, return a single text node.
frag.appendChild(
document.createTextNode(templateString)
)
} else {
var tag = tagMatch && tagMatch[1]
var wrap = map[tag] || map._default
var depth = wrap[0]
var prefix = wrap[1]
var suffix = wrap[2]
var node = document.createElement('div')
node.innerHTML = prefix + templateString.trim() + suffix
while (depth--) {
node = node.lastChild
}
var child
/* eslint-disable no-cond-assign */
while (child = node.firstChild) {
/* eslint-enable no-cond-assign */
frag.appendChild(child)
}
}
templateCache.put(templateString, frag)
return frag
}
/**
* Convert a template node to a DocumentFragment.
*
* @param {Node} node
* @return {DocumentFragment}
*/
function nodeToFragment (node) {
// if its a template tag and the browser supports it,
// its content is already a document fragment.
if (isRealTemplate(node)) {
_.trimNode(node.content)
return node.content
}
// script template
if (node.tagName === 'SCRIPT') {
return stringToFragment(node.textContent)
}
// normal node, clone it to avoid mutating the original
var clone = exports.clone(node)
var frag = document.createDocumentFragment()
var child
/* eslint-disable no-cond-assign */
while (child = clone.firstChild) {
/* eslint-enable no-cond-assign */
frag.appendChild(child)
}
_.trimNode(frag)
return frag
}
// Test for the presence of the Safari template cloning bug
// https://bugs.webkit.org/show_bug.cgi?id=137755
var hasBrokenTemplate = (function () {
/* istanbul ignore else */
if (_.inBrowser) {
var a = document.createElement('div')
a.innerHTML = '1'
return !a.cloneNode(true).firstChild.innerHTML
} else {
return false
}
})()
// Test for IE10/11 textarea placeholder clone bug
var hasTextareaCloneBug = (function () {
/* istanbul ignore else */
if (_.inBrowser) {
var t = document.createElement('textarea')
t.placeholder = 't'
return t.cloneNode(true).value === 't'
} else {
return false
}
})()
/**
* 1. Deal with Safari cloning nested bug by
* manually cloning all template instances.
* 2. Deal with IE10/11 textarea placeholder bug by setting
* the correct value after cloning.
*
* @param {Element|DocumentFragment} node
* @return {Element|DocumentFragment}
*/
exports.clone = function (node) {
if (!node.querySelectorAll) {
return node.cloneNode()
}
var res = node.cloneNode(true)
var i, original, cloned
/* istanbul ignore if */
if (hasBrokenTemplate) {
var clone = res
if (isRealTemplate(node)) {
node = node.content
clone = res.content
}
original = node.querySelectorAll('template')
if (original.length) {
cloned = clone.querySelectorAll('template')
i = cloned.length
while (i--) {
cloned[i].parentNode.replaceChild(
exports.clone(original[i]),
cloned[i]
)
}
}
}
/* istanbul ignore if */
if (hasTextareaCloneBug) {
if (node.tagName === 'TEXTAREA') {
res.value = node.value
} else {
original = node.querySelectorAll('textarea')
if (original.length) {
cloned = res.querySelectorAll('textarea')
i = cloned.length
while (i--) {
cloned[i].value = original[i].value
}
}
}
}
return res
}
/**
* Process the template option and normalizes it into a
* a DocumentFragment that can be used as a partial or a
* instance template.
*
* @param {*} template
* Possible values include:
* - DocumentFragment object
* - Node object of type Template
* - id selector: '#some-template-id'
* - template string: '
{{msg}}
'
* @param {Boolean} clone
* @param {Boolean} noSelector
* @return {DocumentFragment|undefined}
*/
exports.parse = function (template, clone, noSelector) {
var node, frag
// if the template is already a document fragment,
// do nothing
if (template instanceof DocumentFragment) {
_.trimNode(template)
return clone
? exports.clone(template)
: template
}
if (typeof template === 'string') {
// id selector
if (!noSelector && template.charAt(0) === '#') {
// id selector can be cached too
frag = idSelectorCache.get(template)
if (!frag) {
node = document.getElementById(template.slice(1))
if (node) {
frag = nodeToFragment(node)
// save selector to cache
idSelectorCache.put(template, frag)
}
}
} else {
// normal string template
frag = stringToFragment(template)
}
} else if (template.nodeType) {
// a direct node
frag = nodeToFragment(template)
}
return frag && clone
? exports.clone(frag)
: frag
}