X Tutup
Skip to content

Commit 9c6b929

Browse files
vicbjelbourn
authored andcommitted
fix(HtmlParser): close void elements on all node types
fixes #5528
1 parent 070d818 commit 9c6b929

File tree

3 files changed

+45
-20
lines changed

3 files changed

+45
-20
lines changed

modules/angular2/src/compiler/html_parser.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,15 @@ class TreeBuilder {
6161
} else if (this.peek.type === HtmlTokenType.TAG_CLOSE) {
6262
this._consumeEndTag(this._advance());
6363
} else if (this.peek.type === HtmlTokenType.CDATA_START) {
64+
this._closeVoidElement();
6465
this._consumeCdata(this._advance());
6566
} else if (this.peek.type === HtmlTokenType.COMMENT_START) {
67+
this._closeVoidElement();
6668
this._consumeComment(this._advance());
6769
} else if (this.peek.type === HtmlTokenType.TEXT ||
6870
this.peek.type === HtmlTokenType.RAW_TEXT ||
6971
this.peek.type === HtmlTokenType.ESCAPABLE_RAW_TEXT) {
72+
this._closeVoidElement();
7073
this._consumeText(this._advance());
7174
} else {
7275
// Skip all other tokens...
@@ -107,6 +110,16 @@ class TreeBuilder {
107110
this._addToParent(new HtmlTextAst(token.parts[0], token.sourceSpan));
108111
}
109112

113+
private _closeVoidElement(): void {
114+
if (this.elementStack.length > 0) {
115+
let el = ListWrapper.last(this.elementStack);
116+
117+
if (getHtmlTagDefinition(el.name).isVoid) {
118+
this.elementStack.pop();
119+
}
120+
}
121+
}
122+
110123
private _consumeStartTag(startTagToken: HtmlToken) {
111124
var prefix = startTagToken.parts[0];
112125
var name = startTagToken.parts[1];

modules/angular2/src/compiler/html_tags.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,22 @@ export class HtmlTagDefinition {
7070
public parentToAdd: string;
7171
public implicitNamespacePrefix: string;
7272
public contentType: HtmlTagContentType;
73+
public isVoid: boolean;
7374

7475
constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType,
75-
closedByParent}: {
76+
closedByParent, isVoid}: {
7677
closedByChildren?: string[],
7778
closedByParent?: boolean,
7879
requiredParents?: string[],
7980
implicitNamespacePrefix?: string,
80-
contentType?: HtmlTagContentType
81+
contentType?: HtmlTagContentType,
82+
isVoid?: boolean
8183
} = {}) {
8284
if (isPresent(closedByChildren) && closedByChildren.length > 0) {
8385
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
8486
}
85-
this.closedByParent = normalizeBool(closedByParent);
87+
this.isVoid = normalizeBool(isVoid);
88+
this.closedByParent = normalizeBool(closedByParent) || this.isVoid;
8689
if (isPresent(requiredParents) && requiredParents.length > 0) {
8790
this.requiredParents = {};
8891
this.parentToAdd = requiredParents[0];
@@ -98,21 +101,20 @@ export class HtmlTagDefinition {
98101
}
99102

100103
isClosedByChild(name: string): boolean {
101-
return normalizeBool(this.closedByChildren['*']) ||
102-
normalizeBool(this.closedByChildren[name.toLowerCase()]);
104+
return this.isVoid || normalizeBool(this.closedByChildren[name.toLowerCase()]);
103105
}
104106
}
105107

106108
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
107109
// This implementation does not fully conform to the HTML5 spec.
108110
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
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}),
111+
'link': new HtmlTagDefinition({isVoid: true}),
112+
'ng-content': new HtmlTagDefinition({isVoid: true}),
113+
'img': new HtmlTagDefinition({isVoid: true}),
114+
'input': new HtmlTagDefinition({isVoid: true}),
115+
'hr': new HtmlTagDefinition({isVoid: true}),
116+
'br': new HtmlTagDefinition({isVoid: true}),
117+
'wbr': new HtmlTagDefinition({isVoid: true}),
116118
'p': new HtmlTagDefinition({
117119
closedByChildren: [
118120
'address',

modules/angular2/test/compiler/html_parser_spec.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ export function main() {
3131
describe('parse', () => {
3232
describe('text nodes', () => {
3333
it('should parse root level text nodes', () => {
34-
expect(humanizeDom(parser.parse('a', 'TestComp'))).toEqual([[HtmlTextAst, 'a']]);
34+
expect(humanizeDom(parser.parse('a', 'TestComp'))).toEqual([[HtmlTextAst, 'a', 0]]);
3535
});
3636

3737
it('should parse text nodes inside regular elements', () => {
3838
expect(humanizeDom(parser.parse('<div>a</div>', 'TestComp')))
39-
.toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'a']]);
39+
.toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'a', 1]]);
4040
});
4141

4242
it('should parse text nodes inside template elements', () => {
4343
expect(humanizeDom(parser.parse('<template>a</template>', 'TestComp')))
44-
.toEqual([[HtmlElementAst, 'template', 0], [HtmlTextAst, 'a']]);
44+
.toEqual([[HtmlElementAst, 'template', 0], [HtmlTextAst, 'a', 1]]);
4545
});
4646

4747
it('should parse CDATA', () => {
4848
expect(humanizeDom(parser.parse('<![CDATA[text]]>', 'TestComp')))
49-
.toEqual([[HtmlTextAst, 'text']]);
49+
.toEqual([[HtmlTextAst, 'text', 0]]);
5050
});
5151
});
5252

@@ -75,14 +75,24 @@ export function main() {
7575
]);
7676
});
7777

78+
it('should close void elements on text nodes', () => {
79+
expect(humanizeDom(parser.parse('<p>before<br>after</p>', 'TestComp')))
80+
.toEqual([
81+
[HtmlElementAst, 'p', 0],
82+
[HtmlTextAst, 'before', 1],
83+
[HtmlElementAst, 'br', 1],
84+
[HtmlTextAst, 'after', 1],
85+
]);
86+
});
87+
7888
it('should support optional end tags', () => {
7989
expect(humanizeDom(parser.parse('<div><p>1<p>2</div>', 'TestComp')))
8090
.toEqual([
8191
[HtmlElementAst, 'div', 0],
8292
[HtmlElementAst, 'p', 1],
83-
[HtmlTextAst, '1'],
93+
[HtmlTextAst, '1', 2],
8494
[HtmlElementAst, 'p', 1],
85-
[HtmlTextAst, '2'],
95+
[HtmlTextAst, '2', 2],
8696
]);
8797
});
8898

@@ -187,7 +197,7 @@ export function main() {
187197
[HtmlAttrAst, '(e)', 'do()', '(e)="do()"'],
188198
[HtmlAttrAst, 'attr', 'v2', 'attr="v2"'],
189199
[HtmlAttrAst, 'noValue', '', 'noValue'],
190-
[HtmlTextAst, '\na\n', '\na\n'],
200+
[HtmlTextAst, '\na\n', 1, '\na\n'],
191201
]);
192202
});
193203
});
@@ -272,7 +282,7 @@ class Humanizer implements HtmlAstVisitor {
272282
}
273283

274284
visitText(ast: HtmlTextAst, context: any): any {
275-
var res = this._appendContext(ast, [HtmlTextAst, ast.value]);
285+
var res = this._appendContext(ast, [HtmlTextAst, ast.value, this.elDepth]);
276286
this.result.push(res);
277287
return null;
278288
}

0 commit comments

Comments
 (0)
X Tutup