-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(compile): add HtmlParser, TemplateParser, ComponentMetadataLoader
- Loading branch information
Showing
12 changed files
with
1,555 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import {HtmlAst} from './html_ast'; | ||
|
||
export class TypeMeta { | ||
type: any; | ||
typeName: string; | ||
typeUrl: string; | ||
constructor({type, typeName, typeUrl}: | ||
{type?: string, typeName?: string, typeUrl?: string} = {}) { | ||
this.type = type; | ||
this.typeName = typeName; | ||
this.typeUrl = typeUrl; | ||
} | ||
} | ||
|
||
export class TemplateMeta { | ||
encapsulation: ViewEncapsulation; | ||
nodes: HtmlAst[]; | ||
styles: string[]; | ||
styleAbsUrls: string[]; | ||
ngContentSelectors: string[]; | ||
constructor({encapsulation, nodes, styles, styleAbsUrls, ngContentSelectors}: { | ||
encapsulation: ViewEncapsulation, | ||
nodes: HtmlAst[], | ||
styles: string[], | ||
styleAbsUrls: string[], | ||
ngContentSelectors: string[] | ||
}) { | ||
this.encapsulation = encapsulation; | ||
this.nodes = nodes; | ||
this.styles = styles; | ||
this.styleAbsUrls = styleAbsUrls; | ||
this.ngContentSelectors = ngContentSelectors; | ||
} | ||
} | ||
|
||
/** | ||
* How the template and styles of a view should be encapsulated. | ||
*/ | ||
export enum ViewEncapsulation { | ||
/** | ||
* Emulate scoping of styles by preprocessing the style rules | ||
* and adding additional attributes to elements. This is the default. | ||
*/ | ||
Emulated, | ||
/** | ||
* Uses the native mechanism of the renderer. For the DOM this means creating a ShadowRoot. | ||
*/ | ||
Native, | ||
/** | ||
* Don't scope the template nor the styles. | ||
*/ | ||
None | ||
} | ||
|
||
export class DirectiveMetadata { | ||
type: TypeMeta; | ||
selector: string; | ||
constructor({type, selector}: {type?: TypeMeta, selector?: string} = {}) { | ||
this.type = type; | ||
this.selector = selector; | ||
} | ||
} | ||
|
||
export class ComponentMetadata extends DirectiveMetadata { | ||
template: TemplateMeta; | ||
constructor({type, selector, template}: | ||
{type?: TypeMeta, selector?: string, template?: TemplateMeta}) { | ||
super({type: type, selector: selector}); | ||
this.template = template; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import {isPresent} from 'angular2/src/core/facade/lang'; | ||
|
||
export interface HtmlAst { | ||
sourceInfo: string; | ||
visit(visitor: HtmlAstVisitor): any; | ||
} | ||
|
||
export class HtmlTextAst implements HtmlAst { | ||
constructor(public value: string, public sourceInfo: string) {} | ||
visit(visitor: HtmlAstVisitor): any { return visitor.visitText(this); } | ||
} | ||
|
||
export class HtmlAttrAst implements HtmlAst { | ||
constructor(public name: string, public value: string, public sourceInfo: string) {} | ||
visit(visitor: HtmlAstVisitor): any { return visitor.visitAttr(this); } | ||
} | ||
|
||
export class HtmlElementAst implements HtmlAst { | ||
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[], | ||
public sourceInfo: string) {} | ||
visit(visitor: HtmlAstVisitor): any { return visitor.visitElement(this); } | ||
} | ||
|
||
export interface HtmlAstVisitor { | ||
visitElement(ast: HtmlElementAst): any; | ||
visitAttr(ast: HtmlAttrAst): any; | ||
visitText(ast: HtmlTextAst): any; | ||
} | ||
|
||
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[]): any[] { | ||
var result = []; | ||
asts.forEach(ast => { | ||
var astResult = ast.visit(visitor); | ||
if (isPresent(astResult)) { | ||
result.push(astResult); | ||
} | ||
}); | ||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import {MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; | ||
import { | ||
isPresent, | ||
StringWrapper, | ||
stringify, | ||
assertionsEnabled, | ||
StringJoiner | ||
} from 'angular2/src/core/facade/lang'; | ||
import {DOM} from 'angular2/src/core/dom/dom_adapter'; | ||
|
||
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast'; | ||
|
||
const NG_NON_BINDABLE = 'ng-non-bindable'; | ||
|
||
export class HtmlParser { | ||
parse(template: string, sourceInfo: string): HtmlAst[] { | ||
var root = DOM.createTemplate(template); | ||
return parseChildNodes(root, sourceInfo); | ||
} | ||
} | ||
|
||
function parseText(text: Text, indexInParent: number, parentSourceInfo: string): HtmlTextAst { | ||
// TODO(tbosch): add source row/column source info from parse5 / package:html | ||
var value = DOM.getText(text); | ||
return new HtmlTextAst(value, | ||
`${parentSourceInfo} > #text(${value}):nth-child(${indexInParent})`); | ||
} | ||
|
||
function parseAttr(element: Element, parentSourceInfo: string, attrName: string, attrValue: string): | ||
HtmlAttrAst { | ||
// TODO(tbosch): add source row/column source info from parse5 / package:html | ||
return new HtmlAttrAst(attrName, attrValue, `${parentSourceInfo}[${attrName}=${attrValue}]`); | ||
} | ||
|
||
function parseElement(element: Element, indexInParent: number, parentSourceInfo: string): | ||
HtmlElementAst { | ||
// normalize nodename always as lower case so that following build steps | ||
// can rely on this | ||
var nodeName = DOM.nodeName(element).toLowerCase(); | ||
// TODO(tbosch): add source row/column source info from parse5 / package:html | ||
var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`; | ||
var attrs = parseAttrs(element, sourceInfo); | ||
|
||
var childNodes; | ||
if (ignoreChildren(attrs)) { | ||
childNodes = []; | ||
} else { | ||
childNodes = parseChildNodes(element, sourceInfo); | ||
} | ||
return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo); | ||
} | ||
|
||
function parseAttrs(element: Element, elementSourceInfo: string): HtmlAttrAst[] { | ||
// Note: sort the attributes early in the pipeline to get | ||
// consistent results throughout the pipeline, as attribute order is not defined | ||
// in DOM parsers! | ||
var attrMap = DOM.attributeMap(element); | ||
var attrList: string[][] = []; | ||
MapWrapper.forEach(attrMap, (value, name) => { attrList.push([name, value]); }); | ||
ListWrapper.sort(attrList, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); | ||
return attrList.map(entry => parseAttr(element, elementSourceInfo, entry[0], entry[1])); | ||
} | ||
|
||
function parseChildNodes(element: Element, parentSourceInfo: string): HtmlAst[] { | ||
var root = DOM.templateAwareRoot(element); | ||
var childNodes = DOM.childNodesAsList(root); | ||
var result = []; | ||
var index = 0; | ||
childNodes.forEach(childNode => { | ||
var childResult = null; | ||
if (DOM.isTextNode(childNode)) { | ||
var text = <Text>childNode; | ||
childResult = parseText(text, index, parentSourceInfo); | ||
} else if (DOM.isElementNode(childNode)) { | ||
var el = <Element>childNode; | ||
childResult = parseElement(el, index, parentSourceInfo); | ||
} | ||
if (isPresent(childResult)) { | ||
// Won't have a childResult for e.g. comment nodes | ||
result.push(childResult); | ||
} | ||
index++; | ||
}); | ||
return result; | ||
} | ||
|
||
function ignoreChildren(attrs: HtmlAttrAst[]): boolean { | ||
for (var i = 0; i < attrs.length; i++) { | ||
var a = attrs[i]; | ||
if (a.name == NG_NON_BINDABLE) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Some of the code comes from WebComponents.JS | ||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js | ||
|
||
import {Injectable} from 'angular2/di'; | ||
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/core/facade/lang'; | ||
import {UrlResolver} from 'angular2/src/core/services/url_resolver'; | ||
|
||
/** | ||
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL, | ||
* removes and returns the @import urls | ||
*/ | ||
@Injectable() | ||
export class StyleUrlResolver { | ||
constructor(public _resolver: UrlResolver) {} | ||
|
||
resolveUrls(cssText: string, baseUrl: string): string { | ||
cssText = this._replaceUrls(cssText, _cssUrlRe, baseUrl); | ||
return cssText; | ||
} | ||
|
||
extractImports(cssText: string): StyleWithImports { | ||
var foundUrls = []; | ||
cssText = this._extractUrls(cssText, _cssImportRe, foundUrls); | ||
return new StyleWithImports(cssText, foundUrls); | ||
} | ||
|
||
_replaceUrls(cssText: string, re: RegExp, baseUrl: string) { | ||
return StringWrapper.replaceAllMapped(cssText, re, (m) => { | ||
var pre = m[1]; | ||
var originalUrl = m[2]; | ||
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) { | ||
// Do not attempt to resolve data: URLs | ||
return m[0]; | ||
} | ||
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, ''); | ||
var post = m[3]; | ||
|
||
var resolvedUrl = this._resolver.resolve(baseUrl, url); | ||
|
||
return pre + "'" + resolvedUrl + "'" + post; | ||
}); | ||
} | ||
|
||
_extractUrls(cssText: string, re: RegExp, foundUrls: string[]) { | ||
return StringWrapper.replaceAllMapped(cssText, re, (m) => { | ||
var originalUrl = m[2]; | ||
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) { | ||
// Do not attempt to resolve data: URLs | ||
return m[0]; | ||
} | ||
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, ''); | ||
foundUrls.push(url); | ||
return ''; | ||
}); | ||
} | ||
} | ||
|
||
export class StyleWithImports { | ||
constructor(public style: string, public styleUrls: string[]) {} | ||
} | ||
|
||
var _cssUrlRe = /(url\()([^)]*)(\))/g; | ||
var _cssImportRe = /(@import[\s]+(?:url\()?)['"]?([^'"\)]*)['"]?(.*;)/g; | ||
var _quoteRe = /['"]/g; | ||
var _dataUrlRe = /^['"]?data:/g; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import {AST} from 'angular2/src/core/change_detection/change_detection'; | ||
import {isPresent} from 'angular2/src/core/facade/lang'; | ||
import {DirectiveMetadata} from './api'; | ||
|
||
export interface TemplateAst { | ||
sourceInfo: string; | ||
visit(visitor: TemplateAstVisitor): any; | ||
} | ||
|
||
export class TextAst implements TemplateAst { | ||
constructor(public value: string, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitText(this); } | ||
} | ||
|
||
export class BoundTextAst implements TemplateAst { | ||
constructor(public value: AST, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitBoundText(this); } | ||
} | ||
|
||
export class AttrAst implements TemplateAst { | ||
constructor(public name: string, public value: string, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitAttr(this); } | ||
} | ||
|
||
export class BoundPropertyAst implements TemplateAst { | ||
constructor(public name: string, public value: AST, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitProperty(this); } | ||
} | ||
|
||
export class BoundEventAst implements TemplateAst { | ||
constructor(public name: string, public handler: AST, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitEvent(this); } | ||
} | ||
|
||
export class VariableAst implements TemplateAst { | ||
constructor(public name: string, public value: string, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitVariable(this); } | ||
} | ||
|
||
export class ElementAst implements TemplateAst { | ||
constructor(public attrs: AttrAst[], public properties: BoundPropertyAst[], | ||
public events: BoundEventAst[], public vars: VariableAst[], | ||
public directives: DirectiveMetadata[], public children: TemplateAst[], | ||
public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitElement(this); } | ||
} | ||
|
||
export class EmbeddedTemplateAst implements TemplateAst { | ||
constructor(public attrs: AttrAst[], public properties: BoundPropertyAst[], | ||
public vars: VariableAst[], public directives: DirectiveMetadata[], | ||
public children: TemplateAst[], public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitEmbeddedTemplate(this); } | ||
} | ||
|
||
export class NgContentAst implements TemplateAst { | ||
constructor(public select: string, public sourceInfo: string) {} | ||
visit(visitor: TemplateAstVisitor): any { return visitor.visitNgContent(this); } | ||
} | ||
|
||
export interface TemplateAstVisitor { | ||
visitNgContent(ast: NgContentAst): any; | ||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst): any; | ||
visitElement(ast: ElementAst): any; | ||
visitVariable(ast: VariableAst): any; | ||
visitEvent(ast: BoundEventAst): any; | ||
visitProperty(ast: BoundPropertyAst): any; | ||
visitAttr(ast: AttrAst): any; | ||
visitBoundText(ast: BoundTextAst): any; | ||
visitText(ast: TextAst): any; | ||
} | ||
|
||
|
||
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[]): any[] { | ||
var result = []; | ||
asts.forEach(ast => { | ||
var astResult = ast.visit(visitor); | ||
if (isPresent(astResult)) { | ||
result.push(astResult); | ||
} | ||
}); | ||
return result; | ||
} |
Oops, something went wrong.