import { CompileTypeMetadata, CompileDirectiveMetadata, CompileTemplateMetadata } from './directive_metadata'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {XHR} from 'angular2/src/compiler/xhr'; import {UrlResolver} from 'angular2/src/compiler/url_resolver'; import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; import {Injectable} from 'angular2/src/core/di'; import {ViewEncapsulation} from 'angular2/src/core/metadata/view'; import { HtmlAstVisitor, HtmlElementAst, HtmlTextAst, HtmlAttrAst, HtmlAst, htmlVisitAll } from './html_ast'; import {HtmlParser} from './html_parser'; import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser'; @Injectable() export class TemplateNormalizer { constructor(private _xhr: XHR, private _urlResolver: UrlResolver, private _domParser: HtmlParser) {} normalizeTemplate(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata): Promise { if (isPresent(template.template)) { return PromiseWrapper.resolve(this.normalizeLoadedTemplate( directiveType, template, template.template, directiveType.moduleUrl)); } else if (isPresent(template.templateUrl)) { var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl); return this._xhr.get(sourceAbsUrl) .then(templateContent => this.normalizeLoadedTemplate(directiveType, template, templateContent, sourceAbsUrl)); } else { throw new BaseException(`No template specified for component ${directiveType.name}`); } } normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string, templateAbsUrl: string): CompileTemplateMetadata { var domNodes = this._domParser.parse(template, directiveType.name); var visitor = new TemplatePreparseVisitor(); htmlVisitAll(visitor, domNodes); var allStyles = templateMeta.styles.concat(visitor.styles); var allStyleAbsUrls = visitor.styleUrls.filter(isStyleUrlResolvable) .map(url => this._urlResolver.resolve(templateAbsUrl, url)) .concat(templateMeta.styleUrls.filter(isStyleUrlResolvable) .map(url => this._urlResolver.resolve(directiveType.moduleUrl, url))); var allResolvedStyles = allStyles.map(style => { var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style); styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl)); return styleWithImports.style; }); var encapsulation = templateMeta.encapsulation; if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 && allStyleAbsUrls.length === 0) { encapsulation = ViewEncapsulation.None; } return new CompileTemplateMetadata({ encapsulation: encapsulation, template: template, templateUrl: templateAbsUrl, styles: allResolvedStyles, styleUrls: allStyleAbsUrls, ngContentSelectors: visitor.ngContentSelectors }); } } class TemplatePreparseVisitor implements HtmlAstVisitor { ngContentSelectors: string[] = []; styles: string[] = []; styleUrls: string[] = []; ngNonBindableStackCount: number = 0; visitElement(ast: HtmlElementAst, context: any): any { var preparsedElement = preparseElement(ast); switch (preparsedElement.type) { case PreparsedElementType.NG_CONTENT: if (this.ngNonBindableStackCount === 0) { this.ngContentSelectors.push(preparsedElement.selectAttr); } break; case PreparsedElementType.STYLE: var textContent = ''; ast.children.forEach(child => { if (child instanceof HtmlTextAst) { textContent += (child).value; } }); this.styles.push(textContent); break; case PreparsedElementType.STYLESHEET: this.styleUrls.push(preparsedElement.hrefAttr); break; } if (preparsedElement.nonBindable) { this.ngNonBindableStackCount++; } htmlVisitAll(this, ast.children); if (preparsedElement.nonBindable) { this.ngNonBindableStackCount--; } return null; } visitAttr(ast: HtmlAttrAst, context: any): any { return null; } visitText(ast: HtmlTextAst, context: any): any { return null; } }