fix(HtmlParser): consider <ng-container> when adding required parents

This commit is contained in:
Victor Berchet 2016-06-15 09:37:33 -07:00
parent 9cbd8f7afc
commit 9ba400d7d5
5 changed files with 94 additions and 30 deletions

View File

@ -1,11 +1,7 @@
import {Injectable} from '@angular/core';
import {isPresent, isBlank,} from '../src/facade/lang';
import {ListWrapper} from '../src/facade/collection';
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst} from './html_ast';
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
import {ParseError, ParseSourceSpan} from './parse_util';
import {getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
@ -267,18 +263,17 @@ class TreeBuilder {
}
}
var tagDef = getHtmlTagDefinition(el.name);
var parentEl = this._getParentElement();
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
const tagDef = getHtmlTagDefinition(el.name);
const {parent, container} = this._getParentElementSkippingContainers();
if (tagDef.requireExtraParent(isPresent(parent) ? parent.name : null)) {
var newParent = new HtmlElementAst(
tagDef.parentToAdd, [], [el], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
this._addToParent(newParent);
this.elementStack.push(newParent);
this.elementStack.push(el);
} else {
this._addToParent(el);
this.elementStack.push(el);
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
this._insertBeforeContainer(parent, container, newParent);
}
this._addToParent(el);
this.elementStack.push(el);
}
private _consumeEndTag(endTagToken: HtmlToken) {
@ -330,6 +325,25 @@ class TreeBuilder {
return this.elementStack.length > 0 ? ListWrapper.last(this.elementStack) : null;
}
/**
* Returns the parent in the DOM and the container.
*
* `<ng-container>` elements are skipped as they are not rendered as DOM element.
*/
private _getParentElementSkippingContainers():
{parent: HtmlElementAst, container: HtmlElementAst} {
let container: HtmlElementAst = null;
for (let i = this.elementStack.length - 1; i >= 0; i--) {
if (this.elementStack[i].name !== 'ng-container') {
return { parent: this.elementStack[i], container }
}
container = this.elementStack[i];
}
return {parent: ListWrapper.last(this.elementStack), container};
}
private _addToParent(node: HtmlAst) {
var parent = this._getParentElement();
if (isPresent(parent)) {
@ -338,6 +352,31 @@ class TreeBuilder {
this.rootNodes.push(node);
}
}
/**
* Insert a node between the parent and the container.
* When no container is given, the node is appended as a child of the parent.
* Also updates the element stack accordingly.
*
* @internal
*/
private _insertBeforeContainer(
parent: HtmlElementAst, container: HtmlElementAst, node: HtmlElementAst) {
if (!container) {
this._addToParent(node);
this.elementStack.push(node);
} else {
if (parent) {
// replace the container with the new node in the children
let index = parent.children.indexOf(container);
parent.children[index] = node;
} else {
this.rootNodes.push(node);
}
node.children.push(container);
this.elementStack.splice(this.elementStack.indexOf(container), 0, node);
}
}
}
function getElementFullName(

View File

@ -1,21 +1,15 @@
import {StringMapWrapper} from './facade/collection';
import {IS_DART, Math, StringWrapper, isArray, isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
import {IS_DART, StringWrapper, isArray, isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
export var MODULE_SUFFIX = IS_DART ? '.dart' : '';
var CAMEL_CASE_REGEXP = /([A-Z])/g;
var DASH_CASE_REGEXP = /-([a-z])/g;
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(
input, CAMEL_CASE_REGEXP, (m: string[]) => { return '-' + m[1].toLowerCase(); });
}
export function dashCaseToCamelCase(input: string): string {
return StringWrapper.replaceAllMapped(
input, DASH_CASE_REGEXP, (m: string[]) => { return m[1].toUpperCase(); });
}
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {

View File

@ -1,6 +1,6 @@
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '@angular/compiler/src/html_ast';
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
import {ParseError, ParseLocation} from '@angular/compiler/src/parse_util';
import {HtmlParseTreeResult} from '@angular/compiler/src/html_parser';
import {ParseLocation} from '@angular/compiler/src/parse_util';
import {BaseException} from '../src/facade/exceptions';

View File

@ -2,6 +2,7 @@ import {HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpan
import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
import {ParseError} from '@angular/compiler/src/parse_util';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
@ -111,12 +112,42 @@ export function main() {
'<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', '']
[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', ''],
]);
});
it('should append the required parent considering ng-container', () => {
expect(humanizeDom(parser.parse(
'<table><ng-container><tr></tr></ng-container></table>', 'TestComp')))
.toEqual([
[HtmlElementAst, 'table', 0],
[HtmlElementAst, 'tbody', 1],
[HtmlElementAst, 'ng-container', 2],
[HtmlElementAst, 'tr', 3],
]);
});
it('should special case ng-container when adding a required parent', () => {
expect(humanizeDom(parser.parse(
'<table><thead><ng-container><tr></tr></ng-container></thead></table>',
'TestComp')))
.toEqual([
[HtmlElementAst, 'table', 0],
[HtmlElementAst, 'thead', 1],
[HtmlElementAst, 'ng-container', 2],
[HtmlElementAst, 'tr', 3],
]);
});

View File

@ -8,7 +8,7 @@ import {TestInjector, async, getTestInjector, inject, injectAsync} from './test_
export {async, inject, injectAsync} from './test_injector';
declare var global: any /** TODO #9100 */;
declare var global: any;
var _global = <any>(typeof window === 'undefined' ? global : window);