From c58e7e0e91f77e493df5474a48b22012b7156695 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 20 Nov 2015 13:13:48 -0800 Subject: [PATCH] fix(HtmlParser): do not add a tbody parent for tr inside thead & tfoot fixes #5403 --- modules/angular2/src/compiler/html_parser.ts | 2 +- modules/angular2/src/compiler/html_tags.ts | 102 ++++++++++++------ .../test/compiler/html_parser_spec.ts | 15 ++- 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index 05d9fefe53..5c12047cf8 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -145,7 +145,7 @@ class TreeBuilder { var tagDef = getHtmlTagDefinition(el.name); var parentEl = this._getParentElement(); if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) { - var newParent = new HtmlElementAst(tagDef.requiredParent, [], [el], el.sourceSpan); + var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan); this._addToParent(newParent); this.elementStack.push(newParent); this.elementStack.push(el); diff --git a/modules/angular2/src/compiler/html_tags.ts b/modules/angular2/src/compiler/html_tags.ts index 07fc901a4d..2561e5e4cf 100644 --- a/modules/angular2/src/compiler/html_tags.ts +++ b/modules/angular2/src/compiler/html_tags.ts @@ -66,30 +66,35 @@ export enum HtmlTagContentType { export class HtmlTagDefinition { private closedByChildren: {[key: string]: boolean} = {}; public closedByParent: boolean = false; - public requiredParent: string; + public requiredParents: {[key: string]: boolean}; + public parentToAdd: string; public implicitNamespacePrefix: string; public contentType: HtmlTagContentType; - constructor({closedByChildren, requiredParent, implicitNamespacePrefix, contentType, + constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType, closedByParent}: { - closedByChildren?: string, + closedByChildren?: string[], closedByParent?: boolean, - requiredParent?: string, + requiredParents?: string[], implicitNamespacePrefix?: string, contentType?: HtmlTagContentType } = {}) { if (isPresent(closedByChildren) && closedByChildren.length > 0) { - closedByChildren.split(',').forEach(tagName => this.closedByChildren[tagName.trim()] = true); + closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true); } this.closedByParent = normalizeBool(closedByParent); - this.requiredParent = requiredParent; + if (isPresent(requiredParents) && requiredParents.length > 0) { + this.requiredParents = {}; + this.parentToAdd = requiredParents[0]; + requiredParents.forEach(tagName => this.requiredParents[tagName] = true); + } this.implicitNamespacePrefix = implicitNamespacePrefix; this.contentType = isPresent(contentType) ? contentType : HtmlTagContentType.PARSABLE_DATA; } requireExtraParent(currentParent: string): boolean { - return isPresent(this.requiredParent) && - (isBlank(currentParent) || this.requiredParent != currentParent.toLowerCase()); + return isPresent(this.requiredParents) && + (isBlank(currentParent) || this.requiredParents[currentParent.toLowerCase()] != true); } isClosedByChild(name: string): boolean { @@ -101,37 +106,66 @@ export class HtmlTagDefinition { // see http://www.w3.org/TR/html51/syntax.html#optional-tags // This implementation does not fully conform to the HTML5 spec. var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = { - 'link': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'ng-content': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'img': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'input': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'hr': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'br': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), - 'wbr': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}), + 'link': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'ng-content': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'img': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'input': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'hr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'br': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), + 'wbr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}), 'p': new HtmlTagDefinition({ - closedByChildren: - 'address,article,aside,blockquote,div,dl,fieldset,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,nav,ol,p,pre,section,table,ul', + closedByChildren: [ + 'address', + 'article', + 'aside', + 'blockquote', + 'div', + 'dl', + 'fieldset', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'header', + 'hgroup', + 'hr', + 'main', + 'nav', + 'ol', + 'p', + 'pre', + 'section', + 'table', + 'ul' + ], closedByParent: true }), - 'thead': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot'}), - 'tbody': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot', closedByParent: true}), - 'tfoot': new HtmlTagDefinition({closedByChildren: 'tbody', closedByParent: true}), - 'tr': new HtmlTagDefinition( - {closedByChildren: 'tr', requiredParent: 'tbody', closedByParent: true}), - 'td': new HtmlTagDefinition({closedByChildren: 'td,th', closedByParent: true}), - 'th': new HtmlTagDefinition({closedByChildren: 'td,th', closedByParent: true}), - 'col': new HtmlTagDefinition({closedByChildren: 'col', requiredParent: 'colgroup'}), + 'thead': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot']}), + 'tbody': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot'], closedByParent: true}), + 'tfoot': new HtmlTagDefinition({closedByChildren: ['tbody'], closedByParent: true}), + 'tr': new HtmlTagDefinition({ + closedByChildren: ['tr'], + requiredParents: ['tbody', 'tfoot', 'thead'], + closedByParent: true + }), + 'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), + 'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}), + 'col': new HtmlTagDefinition({closedByChildren: ['col'], requiredParents: ['colgroup']}), 'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}), 'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}), - 'li': new HtmlTagDefinition({closedByChildren: 'li', closedByParent: true}), - 'dt': new HtmlTagDefinition({closedByChildren: 'dt,dd'}), - 'dd': new HtmlTagDefinition({closedByChildren: 'dt,dd', closedByParent: true}), - 'rb': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}), - 'rt': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}), - 'rtc': new HtmlTagDefinition({closedByChildren: 'rb,rtc,rp', closedByParent: true}), - 'rp': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}), - 'optgroup': new HtmlTagDefinition({closedByChildren: 'optgroup', closedByParent: true}), - 'option': new HtmlTagDefinition({closedByChildren: 'option,optgroup', closedByParent: true}), + 'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}), + 'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}), + 'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}), + 'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'rt': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'rtc': new HtmlTagDefinition({closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true}), + 'rp': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}), + 'optgroup': new HtmlTagDefinition({closedByChildren: ['optgroup'], closedByParent: true}), + 'option': new HtmlTagDefinition({closedByChildren: ['option', 'optgroup'], closedByParent: true}), 'style': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}), 'script': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}), 'title': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}), diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts index 9120a05ecf..bb105e52a0 100644 --- a/modules/angular2/test/compiler/html_parser_spec.ts +++ b/modules/angular2/test/compiler/html_parser_spec.ts @@ -97,11 +97,24 @@ export function main() { }); it('should add the requiredParent', () => { - expect(humanizeDom(parser.parse('
', 'TestComp'))) + expect( + humanizeDom(parser.parse( + '
', + 'TestComp'))) .toEqual([ [HtmlElementAst, 'table', 0], + [HtmlElementAst, 'thead', 1], + [HtmlElementAst, 'tr', 2], + [HtmlAttrAst, 'head', ''], [HtmlElementAst, 'tbody', 1], [HtmlElementAst, 'tr', 2], + [HtmlAttrAst, 'noparent', ''], + [HtmlElementAst, 'tbody', 1], + [HtmlElementAst, 'tr', 2], + [HtmlAttrAst, 'body', ''], + [HtmlElementAst, 'tfoot', 1], + [HtmlElementAst, 'tr', 2], + [HtmlAttrAst, 'foot', ''] ]); });