parent
070d818e68
commit
9c6b929c7b
@ -61,12 +61,15 @@ class TreeBuilder {
|
|||||||
} else if (this.peek.type === HtmlTokenType.TAG_CLOSE) {
|
} else if (this.peek.type === HtmlTokenType.TAG_CLOSE) {
|
||||||
this._consumeEndTag(this._advance());
|
this._consumeEndTag(this._advance());
|
||||||
} else if (this.peek.type === HtmlTokenType.CDATA_START) {
|
} else if (this.peek.type === HtmlTokenType.CDATA_START) {
|
||||||
|
this._closeVoidElement();
|
||||||
this._consumeCdata(this._advance());
|
this._consumeCdata(this._advance());
|
||||||
} else if (this.peek.type === HtmlTokenType.COMMENT_START) {
|
} else if (this.peek.type === HtmlTokenType.COMMENT_START) {
|
||||||
|
this._closeVoidElement();
|
||||||
this._consumeComment(this._advance());
|
this._consumeComment(this._advance());
|
||||||
} else if (this.peek.type === HtmlTokenType.TEXT ||
|
} else if (this.peek.type === HtmlTokenType.TEXT ||
|
||||||
this.peek.type === HtmlTokenType.RAW_TEXT ||
|
this.peek.type === HtmlTokenType.RAW_TEXT ||
|
||||||
this.peek.type === HtmlTokenType.ESCAPABLE_RAW_TEXT) {
|
this.peek.type === HtmlTokenType.ESCAPABLE_RAW_TEXT) {
|
||||||
|
this._closeVoidElement();
|
||||||
this._consumeText(this._advance());
|
this._consumeText(this._advance());
|
||||||
} else {
|
} else {
|
||||||
// Skip all other tokens...
|
// Skip all other tokens...
|
||||||
@ -107,6 +110,16 @@ class TreeBuilder {
|
|||||||
this._addToParent(new HtmlTextAst(token.parts[0], token.sourceSpan));
|
this._addToParent(new HtmlTextAst(token.parts[0], token.sourceSpan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _closeVoidElement(): void {
|
||||||
|
if (this.elementStack.length > 0) {
|
||||||
|
let el = ListWrapper.last(this.elementStack);
|
||||||
|
|
||||||
|
if (getHtmlTagDefinition(el.name).isVoid) {
|
||||||
|
this.elementStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _consumeStartTag(startTagToken: HtmlToken) {
|
private _consumeStartTag(startTagToken: HtmlToken) {
|
||||||
var prefix = startTagToken.parts[0];
|
var prefix = startTagToken.parts[0];
|
||||||
var name = startTagToken.parts[1];
|
var name = startTagToken.parts[1];
|
||||||
|
@ -70,19 +70,22 @@ export class HtmlTagDefinition {
|
|||||||
public parentToAdd: string;
|
public parentToAdd: string;
|
||||||
public implicitNamespacePrefix: string;
|
public implicitNamespacePrefix: string;
|
||||||
public contentType: HtmlTagContentType;
|
public contentType: HtmlTagContentType;
|
||||||
|
public isVoid: boolean;
|
||||||
|
|
||||||
constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType,
|
constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType,
|
||||||
closedByParent}: {
|
closedByParent, isVoid}: {
|
||||||
closedByChildren?: string[],
|
closedByChildren?: string[],
|
||||||
closedByParent?: boolean,
|
closedByParent?: boolean,
|
||||||
requiredParents?: string[],
|
requiredParents?: string[],
|
||||||
implicitNamespacePrefix?: string,
|
implicitNamespacePrefix?: string,
|
||||||
contentType?: HtmlTagContentType
|
contentType?: HtmlTagContentType,
|
||||||
|
isVoid?: boolean
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (isPresent(closedByChildren) && closedByChildren.length > 0) {
|
if (isPresent(closedByChildren) && closedByChildren.length > 0) {
|
||||||
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
|
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
|
||||||
}
|
}
|
||||||
this.closedByParent = normalizeBool(closedByParent);
|
this.isVoid = normalizeBool(isVoid);
|
||||||
|
this.closedByParent = normalizeBool(closedByParent) || this.isVoid;
|
||||||
if (isPresent(requiredParents) && requiredParents.length > 0) {
|
if (isPresent(requiredParents) && requiredParents.length > 0) {
|
||||||
this.requiredParents = {};
|
this.requiredParents = {};
|
||||||
this.parentToAdd = requiredParents[0];
|
this.parentToAdd = requiredParents[0];
|
||||||
@ -98,21 +101,20 @@ export class HtmlTagDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isClosedByChild(name: string): boolean {
|
isClosedByChild(name: string): boolean {
|
||||||
return normalizeBool(this.closedByChildren['*']) ||
|
return this.isVoid || normalizeBool(this.closedByChildren[name.toLowerCase()]);
|
||||||
normalizeBool(this.closedByChildren[name.toLowerCase()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
|
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
|
||||||
// This implementation does not fully conform to the HTML5 spec.
|
// This implementation does not fully conform to the HTML5 spec.
|
||||||
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
|
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
|
||||||
'link': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'link': new HtmlTagDefinition({isVoid: true}),
|
||||||
'ng-content': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'ng-content': new HtmlTagDefinition({isVoid: true}),
|
||||||
'img': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'img': new HtmlTagDefinition({isVoid: true}),
|
||||||
'input': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'input': new HtmlTagDefinition({isVoid: true}),
|
||||||
'hr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'hr': new HtmlTagDefinition({isVoid: true}),
|
||||||
'br': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'br': new HtmlTagDefinition({isVoid: true}),
|
||||||
'wbr': new HtmlTagDefinition({closedByChildren: ['*'], closedByParent: true}),
|
'wbr': new HtmlTagDefinition({isVoid: true}),
|
||||||
'p': new HtmlTagDefinition({
|
'p': new HtmlTagDefinition({
|
||||||
closedByChildren: [
|
closedByChildren: [
|
||||||
'address',
|
'address',
|
||||||
|
@ -31,22 +31,22 @@ export function main() {
|
|||||||
describe('parse', () => {
|
describe('parse', () => {
|
||||||
describe('text nodes', () => {
|
describe('text nodes', () => {
|
||||||
it('should parse root level text nodes', () => {
|
it('should parse root level text nodes', () => {
|
||||||
expect(humanizeDom(parser.parse('a', 'TestComp'))).toEqual([[HtmlTextAst, 'a']]);
|
expect(humanizeDom(parser.parse('a', 'TestComp'))).toEqual([[HtmlTextAst, 'a', 0]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse text nodes inside regular elements', () => {
|
it('should parse text nodes inside regular elements', () => {
|
||||||
expect(humanizeDom(parser.parse('<div>a</div>', 'TestComp')))
|
expect(humanizeDom(parser.parse('<div>a</div>', 'TestComp')))
|
||||||
.toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'a']]);
|
.toEqual([[HtmlElementAst, 'div', 0], [HtmlTextAst, 'a', 1]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse text nodes inside template elements', () => {
|
it('should parse text nodes inside template elements', () => {
|
||||||
expect(humanizeDom(parser.parse('<template>a</template>', 'TestComp')))
|
expect(humanizeDom(parser.parse('<template>a</template>', 'TestComp')))
|
||||||
.toEqual([[HtmlElementAst, 'template', 0], [HtmlTextAst, 'a']]);
|
.toEqual([[HtmlElementAst, 'template', 0], [HtmlTextAst, 'a', 1]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse CDATA', () => {
|
it('should parse CDATA', () => {
|
||||||
expect(humanizeDom(parser.parse('<![CDATA[text]]>', 'TestComp')))
|
expect(humanizeDom(parser.parse('<![CDATA[text]]>', 'TestComp')))
|
||||||
.toEqual([[HtmlTextAst, 'text']]);
|
.toEqual([[HtmlTextAst, 'text', 0]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,14 +75,24 @@ export function main() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should close void elements on text nodes', () => {
|
||||||
|
expect(humanizeDom(parser.parse('<p>before<br>after</p>', 'TestComp')))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlElementAst, 'p', 0],
|
||||||
|
[HtmlTextAst, 'before', 1],
|
||||||
|
[HtmlElementAst, 'br', 1],
|
||||||
|
[HtmlTextAst, 'after', 1],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support optional end tags', () => {
|
it('should support optional end tags', () => {
|
||||||
expect(humanizeDom(parser.parse('<div><p>1<p>2</div>', 'TestComp')))
|
expect(humanizeDom(parser.parse('<div><p>1<p>2</div>', 'TestComp')))
|
||||||
.toEqual([
|
.toEqual([
|
||||||
[HtmlElementAst, 'div', 0],
|
[HtmlElementAst, 'div', 0],
|
||||||
[HtmlElementAst, 'p', 1],
|
[HtmlElementAst, 'p', 1],
|
||||||
[HtmlTextAst, '1'],
|
[HtmlTextAst, '1', 2],
|
||||||
[HtmlElementAst, 'p', 1],
|
[HtmlElementAst, 'p', 1],
|
||||||
[HtmlTextAst, '2'],
|
[HtmlTextAst, '2', 2],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -187,7 +197,7 @@ export function main() {
|
|||||||
[HtmlAttrAst, '(e)', 'do()', '(e)="do()"'],
|
[HtmlAttrAst, '(e)', 'do()', '(e)="do()"'],
|
||||||
[HtmlAttrAst, 'attr', 'v2', 'attr="v2"'],
|
[HtmlAttrAst, 'attr', 'v2', 'attr="v2"'],
|
||||||
[HtmlAttrAst, 'noValue', '', 'noValue'],
|
[HtmlAttrAst, 'noValue', '', 'noValue'],
|
||||||
[HtmlTextAst, '\na\n', '\na\n'],
|
[HtmlTextAst, '\na\n', 1, '\na\n'],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -272,7 +282,7 @@ class Humanizer implements HtmlAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visitText(ast: HtmlTextAst, context: any): any {
|
visitText(ast: HtmlTextAst, context: any): any {
|
||||||
var res = this._appendContext(ast, [HtmlTextAst, ast.value]);
|
var res = this._appendContext(ast, [HtmlTextAst, ast.value, this.elDepth]);
|
||||||
this.result.push(res);
|
this.result.push(res);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user