diff --git a/src/.jshintrc b/src/.jshintrc index b3211355..1202b644 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -138,6 +138,7 @@ "JQLitePrototype": false, "addEventListenerFn": false, "removeEventListenerFn": false, + "jqLiteIsTextNode": false, /* apis.js */ "hashKey": false, diff --git a/src/jqLite.js b/src/jqLite.js index 738f47a9..d03ea67b 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -179,6 +179,81 @@ function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArgu } } +var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; +var HTML_REGEXP = /<|&#?\w+;/; +var TAG_NAME_REGEXP = /<([\w:]+)/; +var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + +var wrapMap = { + 'option': [1, ''], + + 'thead': [1, '', '
'], + 'col': [2, '', '
'], + 'tr': [2, '', '
'], + 'td': [3, '', '
'], + '_default': [0, "", ""] +}; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function jqLiteIsTextNode(html) { + return !HTML_REGEXP.test(html); +} + +function jqLiteBuildFragment(html, context) { + var elem, tmp, tag, wrap, + fragment = context.createDocumentFragment(), + nodes = [], i; + + if (jqLiteIsTextNode(html)) { + // Convert non-html into a text node + nodes.push(context.createTextNode(html)); + } else { + // Convert html into DOM nodes + tmp = tmp || fragment.appendChild(context.createElement("div")); + tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; + + // Descend through wrappers to the right content + i = wrap[0]; + while (i--) { + tmp = tmp.lastChild; + } + + nodes = concat(nodes, tmp.childNodes); + + tmp = fragment.firstChild; + tmp.textContent = ""; + } + + // Remove wrapper from fragment + fragment.textContent = ""; + fragment.innerHTML = ""; // Clear inner HTML + forEach(nodes, function(node) { + fragment.appendChild(node); + }); + + return fragment; +} + +function jqLiteParseHTML(html, context) { + context = context || document; + var parsed; + + if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { + return [context.createElement(parsed[1])]; + } + + if ((parsed = jqLiteBuildFragment(html, context))) { + return parsed.childNodes; + } + + return []; +} + ///////////////////////////////////////////// function JQLite(element) { if (element instanceof JQLite) { @@ -195,14 +270,7 @@ function JQLite(element) { } if (isString(element)) { - var div = document.createElement('div'); - // Read about the NoScope elements here: - // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx - div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! - div.removeChild(div.firstChild); // remove the superfluous div - jqLiteAddNodes(this, div.childNodes); - var fragment = jqLite(document.createDocumentFragment()); - fragment.append(this); // detach the elements from the temporary DOM div. + jqLiteAddNodes(this, jqLiteParseHTML(element)); } else { jqLiteAddNodes(this, element); } diff --git a/src/ng/compile.js b/src/ng/compile.js index 58c3b5fc..98159551 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -513,8 +513,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|thead|tbody|tfoot)(\s+[^>]*)?>/i; + CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with @@ -1259,7 +1258,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (directive.replace) { replaceDirective = directive; - $template = directiveTemplateContents(directiveValue); + if (jqLiteIsTextNode(directiveValue)) { + $template = []; + } else { + $template = jqLite(directiveValue); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { @@ -1658,27 +1661,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function directiveTemplateContents(template) { - var type; - template = trim(template); - if ((type = TABLE_CONTENT_REGEXP.exec(template))) { - type = type[1].toLowerCase(); - var table = jqLite('' + template + '
'); - if (/(thead|tbody|tfoot)/.test(type)) { - return table.children(type); - } - table = table.children('tbody'); - if (type === 'tr') { - return table.children('tr'); - } - return table.children('tr').contents(); - } - return jqLite('
' + - template + - '
').contents(); - } - - function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], @@ -1703,7 +1685,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { content = denormalizeTemplate(content); if (origAsyncDirective.replace) { - $template = directiveTemplateContents(content); + if (jqLiteIsTextNode(content)) { + $template = []; + } else { + $template = jqLite(content); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index faf1c98c..f9c6f3a2 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -95,6 +95,36 @@ describe('jqLite', function() { expect(fragment.length).toBe(1); expect(fragment[0].nodeType).toBe(11); }); + + + it('should allow construction of