X Tutup
Skip to content

Commit c58e7e0

Browse files
vicbjelbourn
authored andcommitted
fix(HtmlParser): do not add a tbody parent for tr inside thead & tfoot
fixes #5403
1 parent bdfed9d commit c58e7e0

File tree

3 files changed

+83
-36
lines changed

3 files changed

+83
-36
lines changed

modules/angular2/src/compiler/html_parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class TreeBuilder {
145145
var tagDef = getHtmlTagDefinition(el.name);
146146
var parentEl = this._getParentElement();
147147
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
148-
var newParent = new HtmlElementAst(tagDef.requiredParent, [], [el], el.sourceSpan);
148+
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan);
149149
this._addToParent(newParent);
150150
this.elementStack.push(newParent);
151151
this.elementStack.push(el);

modules/angular2/src/compiler/html_tags.ts

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,30 +66,35 @@ export enum HtmlTagContentType {
6666
export class HtmlTagDefinition {
6767
private closedByChildren: {[key: string]: boolean} = {};
6868
public closedByParent: boolean = false;
69-
public requiredParent: string;
69+
public requiredParents: {[key: string]: boolean};
70+
public parentToAdd: string;
7071
public implicitNamespacePrefix: string;
7172
public contentType: HtmlTagContentType;
7273

73-
constructor({closedByChildren, requiredParent, implicitNamespacePrefix, contentType,
74+
constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType,
7475
closedByParent}: {
75-
closedByChildren?: string,
76+
closedByChildren?: string[],
7677
closedByParent?: boolean,
77-
requiredParent?: string,
78+
requiredParents?: string[],
7879
implicitNamespacePrefix?: string,
7980
contentType?: HtmlTagContentType
8081
} = {}) {
8182
if (isPresent(closedByChildren) && closedByChildren.length > 0) {
82-
closedByChildren.split(',').forEach(tagName => this.closedByChildren[tagName.trim()] = true);
83+
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
8384
}
8485
this.closedByParent = normalizeBool(closedByParent);
85-
this.requiredParent = requiredParent;
86+
if (isPresent(requiredParents) && requiredParents.length > 0) {
87+
this.requiredParents = {};
88+
this.parentToAdd = requiredParents[0];
89+
requiredParents.forEach(tagName => this.requiredParents[tagName] = true);
90+
}
8691
this.implicitNamespacePrefix = implicitNamespacePrefix;
8792
this.contentType = isPresent(contentType) ? contentType : HtmlTagContentType.PARSABLE_DATA;
8893
}
8994

9095
requireExtraParent(currentParent: string): boolean {
91-
return isPresent(this.requiredParent) &&
92-
(isBlank(currentParent) || this.requiredParent != currentParent.toLowerCase());
96+
return isPresent(this.requiredParents) &&
97+
(isBlank(currentParent) || this.requiredParents[currentParent.toLowerCase()] != true);
9398
}
9499

95100
isClosedByChild(name: string): boolean {
@@ -101,37 +106,66 @@ export class HtmlTagDefinition {
101106
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
102107
// This implementation does not fully conform to the HTML5 spec.
103108
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
104-
'link': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
105-
'ng-content': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
106-
'img': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
107-
'input': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
108-
'hr': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
109-
'br': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
110-
'wbr': new HtmlTagDefinition({closedByChildren: '*', closedByParent: true}),
109+
'link': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
110+
'ng-content': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
111+
'img': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
112+
'input': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
113+
'hr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
114+
'br': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
115+
'wbr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
111116
'p': new HtmlTagDefinition({
112-
closedByChildren:
113-
'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',
117+
closedByChildren: [
118+
'address',
119+
'article',
120+
'aside',
121+
'blockquote',
122+
'div',
123+
'dl',
124+
'fieldset',
125+
'footer',
126+
'form',
127+
'h1',
128+
'h2',
129+
'h3',
130+
'h4',
131+
'h5',
132+
'h6',
133+
'header',
134+
'hgroup',
135+
'hr',
136+
'main',
137+
'nav',
138+
'ol',
139+
'p',
140+
'pre',
141+
'section',
142+
'table',
143+
'ul'
144+
],
145+
closedByParent: true
146+
}),
147+
'thead': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot']}),
148+
'tbody': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot'], closedByParent: true}),
149+
'tfoot': new HtmlTagDefinition({closedByChildren: ['tbody'], closedByParent: true}),
150+
'tr': new HtmlTagDefinition({
151+
closedByChildren: ['tr'],
152+
requiredParents: ['tbody', 'tfoot', 'thead'],
114153
closedByParent: true
115154
}),
116-
'thead': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot'}),
117-
'tbody': new HtmlTagDefinition({closedByChildren: 'tbody,tfoot', closedByParent: true}),
118-
'tfoot': new HtmlTagDefinition({closedByChildren: 'tbody', closedByParent: true}),
119-
'tr': new HtmlTagDefinition(
120-
{closedByChildren: 'tr', requiredParent: 'tbody', closedByParent: true}),
121-
'td': new HtmlTagDefinition({closedByChildren: 'td,th', closedByParent: true}),
122-
'th': new HtmlTagDefinition({closedByChildren: 'td,th', closedByParent: true}),
123-
'col': new HtmlTagDefinition({closedByChildren: 'col', requiredParent: 'colgroup'}),
155+
'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
156+
'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
157+
'col': new HtmlTagDefinition({closedByChildren: ['col'], requiredParents: ['colgroup']}),
124158
'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
125159
'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}),
126-
'li': new HtmlTagDefinition({closedByChildren: 'li', closedByParent: true}),
127-
'dt': new HtmlTagDefinition({closedByChildren: 'dt,dd'}),
128-
'dd': new HtmlTagDefinition({closedByChildren: 'dt,dd', closedByParent: true}),
129-
'rb': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}),
130-
'rt': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}),
131-
'rtc': new HtmlTagDefinition({closedByChildren: 'rb,rtc,rp', closedByParent: true}),
132-
'rp': new HtmlTagDefinition({closedByChildren: 'rb,rt,rtc,rp', closedByParent: true}),
133-
'optgroup': new HtmlTagDefinition({closedByChildren: 'optgroup', closedByParent: true}),
134-
'option': new HtmlTagDefinition({closedByChildren: 'option,optgroup', closedByParent: true}),
160+
'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}),
161+
'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}),
162+
'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}),
163+
'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
164+
'rt': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
165+
'rtc': new HtmlTagDefinition({closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true}),
166+
'rp': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
167+
'optgroup': new HtmlTagDefinition({closedByChildren: ['optgroup'], closedByParent: true}),
168+
'option': new HtmlTagDefinition({closedByChildren: ['option', 'optgroup'], closedByParent: true}),
135169
'style': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}),
136170
'script': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}),
137171
'title': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}),

modules/angular2/test/compiler/html_parser_spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,24 @@ export function main() {
9797
});
9898

9999
it('should add the requiredParent', () => {
100-
expect(humanizeDom(parser.parse('<table><tr></tr></table>', 'TestComp')))
100+
expect(
101+
humanizeDom(parser.parse(
102+
'<table><thead><tr head></tr></thead><tr noparent></tr><tbody><tr body></tr></tbody><tfoot><tr foot></tr></tfoot></table>',
103+
'TestComp')))
101104
.toEqual([
102105
[HtmlElementAst, 'table', 0],
106+
[HtmlElementAst, 'thead', 1],
107+
[HtmlElementAst, 'tr', 2],
108+
[HtmlAttrAst, 'head', ''],
109+
[HtmlElementAst, 'tbody', 1],
110+
[HtmlElementAst, 'tr', 2],
111+
[HtmlAttrAst, 'noparent', ''],
103112
[HtmlElementAst, 'tbody', 1],
104113
[HtmlElementAst, 'tr', 2],
114+
[HtmlAttrAst, 'body', ''],
115+
[HtmlElementAst, 'tfoot', 1],
116+
[HtmlElementAst, 'tr', 2],
117+
[HtmlAttrAst, 'foot', '']
105118
]);
106119
});
107120

0 commit comments

Comments
 (0)
X Tutup