2015-09-14 18:59:09 -04:00
|
|
|
import {
|
2015-09-18 13:33:23 -04:00
|
|
|
CompileTypeMetadata,
|
|
|
|
CompileDirectiveMetadata,
|
|
|
|
CompileTemplateMetadata
|
2015-09-14 18:59:09 -04:00
|
|
|
} from './directive_metadata';
|
2015-11-06 20:34:07 -05:00
|
|
|
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
|
|
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
|
|
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
2015-08-25 18:36:02 -04:00
|
|
|
|
2015-11-05 17:07:57 -05:00
|
|
|
import {XHR} from 'angular2/src/compiler/xhr';
|
|
|
|
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
|
2015-10-14 12:39:40 -04:00
|
|
|
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
|
2015-09-14 18:59:09 -04:00
|
|
|
import {Injectable} from 'angular2/src/core/di';
|
2015-10-05 13:10:07 -04:00
|
|
|
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
|
|
|
|
|
2015-08-25 18:36:02 -04:00
|
|
|
|
|
|
|
import {
|
|
|
|
HtmlAstVisitor,
|
|
|
|
HtmlElementAst,
|
|
|
|
HtmlTextAst,
|
|
|
|
HtmlAttrAst,
|
|
|
|
HtmlAst,
|
|
|
|
htmlVisitAll
|
|
|
|
} from './html_ast';
|
|
|
|
import {HtmlParser} from './html_parser';
|
|
|
|
|
2015-09-18 13:33:23 -04:00
|
|
|
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
|
2015-08-25 18:36:02 -04:00
|
|
|
|
2015-09-14 18:59:09 -04:00
|
|
|
@Injectable()
|
|
|
|
export class TemplateNormalizer {
|
2015-08-25 18:36:02 -04:00
|
|
|
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
|
2015-09-02 18:07:31 -04:00
|
|
|
private _domParser: HtmlParser) {}
|
2015-08-25 18:36:02 -04:00
|
|
|
|
2015-09-18 13:33:23 -04:00
|
|
|
normalizeTemplate(directiveType: CompileTypeMetadata,
|
|
|
|
template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> {
|
2015-09-14 18:59:09 -04:00
|
|
|
if (isPresent(template.template)) {
|
|
|
|
return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
|
2015-10-01 13:07:49 -04:00
|
|
|
directiveType, template, template.template, directiveType.moduleUrl));
|
2015-09-18 13:33:23 -04:00
|
|
|
} else if (isPresent(template.templateUrl)) {
|
2015-10-01 13:07:49 -04:00
|
|
|
var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
|
2015-08-25 18:36:02 -04:00
|
|
|
return this._xhr.get(sourceAbsUrl)
|
2015-09-14 18:59:09 -04:00
|
|
|
.then(templateContent => this.normalizeLoadedTemplate(directiveType, template,
|
|
|
|
templateContent, sourceAbsUrl));
|
2015-09-18 13:33:23 -04:00
|
|
|
} else {
|
|
|
|
throw new BaseException(`No template specified for component ${directiveType.name}`);
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-18 13:33:23 -04:00
|
|
|
normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata,
|
|
|
|
template: string, templateAbsUrl: string): CompileTemplateMetadata {
|
2015-09-14 18:59:09 -04:00
|
|
|
var domNodes = this._domParser.parse(template, directiveType.name);
|
2015-08-25 18:36:02 -04:00
|
|
|
var visitor = new TemplatePreparseVisitor();
|
2015-09-18 13:33:23 -04:00
|
|
|
htmlVisitAll(visitor, domNodes);
|
2015-09-14 18:59:09 -04:00
|
|
|
var allStyles = templateMeta.styles.concat(visitor.styles);
|
|
|
|
|
|
|
|
var allStyleAbsUrls =
|
2015-10-09 12:07:58 -04:00
|
|
|
visitor.styleUrls.filter(isStyleUrlResolvable)
|
2015-10-27 17:00:15 -04:00
|
|
|
.map(url => this._urlResolver.resolve(templateAbsUrl, url))
|
2015-10-09 12:07:58 -04:00
|
|
|
.concat(templateMeta.styleUrls.filter(isStyleUrlResolvable)
|
2015-10-27 17:00:15 -04:00
|
|
|
.map(url => this._urlResolver.resolve(directiveType.moduleUrl, url)));
|
2015-10-23 03:17:30 -04:00
|
|
|
|
2015-09-02 18:07:31 -04:00
|
|
|
var allResolvedStyles = allStyles.map(style => {
|
2015-10-14 12:39:40 -04:00
|
|
|
var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style);
|
2015-09-14 18:59:09 -04:00
|
|
|
styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl));
|
2015-08-25 18:36:02 -04:00
|
|
|
return styleWithImports.style;
|
|
|
|
});
|
2015-10-23 03:17:30 -04:00
|
|
|
|
2015-09-18 13:33:23 -04:00
|
|
|
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,
|
2015-08-25 18:36:02 -04:00
|
|
|
styles: allResolvedStyles,
|
2015-09-18 13:33:23 -04:00
|
|
|
styleUrls: allStyleAbsUrls,
|
2015-08-25 18:36:02 -04:00
|
|
|
ngContentSelectors: visitor.ngContentSelectors
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TemplatePreparseVisitor implements HtmlAstVisitor {
|
|
|
|
ngContentSelectors: string[] = [];
|
|
|
|
styles: string[] = [];
|
|
|
|
styleUrls: string[] = [];
|
2015-09-18 13:33:23 -04:00
|
|
|
ngNonBindableStackCount: number = 0;
|
2015-08-25 18:36:02 -04:00
|
|
|
|
2015-09-18 13:33:23 -04:00
|
|
|
visitElement(ast: HtmlElementAst, context: any): any {
|
|
|
|
var preparsedElement = preparseElement(ast);
|
|
|
|
switch (preparsedElement.type) {
|
|
|
|
case PreparsedElementType.NG_CONTENT:
|
2015-09-18 13:33:23 -04:00
|
|
|
if (this.ngNonBindableStackCount === 0) {
|
|
|
|
this.ngContentSelectors.push(preparsedElement.selectAttr);
|
|
|
|
}
|
2015-09-18 13:33:23 -04:00
|
|
|
break;
|
|
|
|
case PreparsedElementType.STYLE:
|
|
|
|
var textContent = '';
|
|
|
|
ast.children.forEach(child => {
|
|
|
|
if (child instanceof HtmlTextAst) {
|
|
|
|
textContent += (<HtmlTextAst>child).value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.styles.push(textContent);
|
|
|
|
break;
|
|
|
|
case PreparsedElementType.STYLESHEET:
|
|
|
|
this.styleUrls.push(preparsedElement.hrefAttr);
|
|
|
|
break;
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|
2015-09-18 13:33:23 -04:00
|
|
|
if (preparsedElement.nonBindable) {
|
|
|
|
this.ngNonBindableStackCount++;
|
|
|
|
}
|
|
|
|
htmlVisitAll(this, ast.children);
|
|
|
|
if (preparsedElement.nonBindable) {
|
|
|
|
this.ngNonBindableStackCount--;
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|
2015-09-18 13:33:23 -04:00
|
|
|
return null;
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|
2015-09-18 13:33:23 -04:00
|
|
|
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
|
|
|
|
visitText(ast: HtmlTextAst, context: any): any { return null; }
|
2015-08-25 18:36:02 -04:00
|
|
|
}
|