fix(HtmlParser): consider <ng-container> when adding required parents
This commit is contained in:
parent
9cbd8f7afc
commit
9ba400d7d5
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user