fix(HtmlParser): do not add a tbody parent for tr inside thead & tfoot

fixes #5403
This commit is contained in:
Victor Berchet 2015-11-20 13:13:48 -08:00 committed by Jeremy Elbourn
parent bdfed9d850
commit c58e7e0e91
3 changed files with 83 additions and 36 deletions

View File

@ -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);

View File

@ -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}),

View File

@ -97,11 +97,24 @@ export function main() {
});
it('should add the requiredParent', () => {
expect(humanizeDom(parser.parse('<table><tr></tr></table>', 'TestComp')))
expect(
humanizeDom(parser.parse(
'<table><thead><tr head></tr></thead><tr noparent></tr><tbody><tr body></tr></tbody><tfoot><tr foot></tr></tfoot></table>',
'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', '']
]);
});