|
| 1 | +import {MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; |
| 2 | +import { |
| 3 | + isPresent, |
| 4 | + StringWrapper, |
| 5 | + stringify, |
| 6 | + assertionsEnabled, |
| 7 | + StringJoiner |
| 8 | +} from 'angular2/src/core/facade/lang'; |
| 9 | +import {DOM} from 'angular2/src/core/dom/dom_adapter'; |
| 10 | + |
| 11 | +import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast'; |
| 12 | + |
| 13 | +const NG_NON_BINDABLE = 'ng-non-bindable'; |
| 14 | + |
| 15 | +export class HtmlParser { |
| 16 | + parse(template: string, sourceInfo: string): HtmlAst[] { |
| 17 | + var root = DOM.createTemplate(template); |
| 18 | + return parseChildNodes(root, sourceInfo); |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +function parseText(text: Text, indexInParent: number, parentSourceInfo: string): HtmlTextAst { |
| 23 | + // TODO(tbosch): add source row/column source info from parse5 / package:html |
| 24 | + var value = DOM.getText(text); |
| 25 | + return new HtmlTextAst(value, |
| 26 | + `${parentSourceInfo} > #text(${value}):nth-child(${indexInParent})`); |
| 27 | +} |
| 28 | + |
| 29 | +function parseAttr(element: Element, parentSourceInfo: string, attrName: string, attrValue: string): |
| 30 | + HtmlAttrAst { |
| 31 | + // TODO(tbosch): add source row/column source info from parse5 / package:html |
| 32 | + return new HtmlAttrAst(attrName, attrValue, `${parentSourceInfo}[${attrName}=${attrValue}]`); |
| 33 | +} |
| 34 | + |
| 35 | +function parseElement(element: Element, indexInParent: number, parentSourceInfo: string): |
| 36 | + HtmlElementAst { |
| 37 | + // normalize nodename always as lower case so that following build steps |
| 38 | + // can rely on this |
| 39 | + var nodeName = DOM.nodeName(element).toLowerCase(); |
| 40 | + // TODO(tbosch): add source row/column source info from parse5 / package:html |
| 41 | + var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`; |
| 42 | + var attrs = parseAttrs(element, sourceInfo); |
| 43 | + |
| 44 | + var childNodes; |
| 45 | + if (ignoreChildren(attrs)) { |
| 46 | + childNodes = []; |
| 47 | + } else { |
| 48 | + childNodes = parseChildNodes(element, sourceInfo); |
| 49 | + } |
| 50 | + return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo); |
| 51 | +} |
| 52 | + |
| 53 | +function parseAttrs(element: Element, elementSourceInfo: string): HtmlAttrAst[] { |
| 54 | + // Note: sort the attributes early in the pipeline to get |
| 55 | + // consistent results throughout the pipeline, as attribute order is not defined |
| 56 | + // in DOM parsers! |
| 57 | + var attrMap = DOM.attributeMap(element); |
| 58 | + var attrList: string[][] = []; |
| 59 | + MapWrapper.forEach(attrMap, (value, name) => { attrList.push([name, value]); }); |
| 60 | + ListWrapper.sort(attrList, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); |
| 61 | + return attrList.map(entry => parseAttr(element, elementSourceInfo, entry[0], entry[1])); |
| 62 | +} |
| 63 | + |
| 64 | +function parseChildNodes(element: Element, parentSourceInfo: string): HtmlAst[] { |
| 65 | + var root = DOM.templateAwareRoot(element); |
| 66 | + var childNodes = DOM.childNodesAsList(root); |
| 67 | + var result = []; |
| 68 | + var index = 0; |
| 69 | + childNodes.forEach(childNode => { |
| 70 | + var childResult = null; |
| 71 | + if (DOM.isTextNode(childNode)) { |
| 72 | + var text = <Text>childNode; |
| 73 | + childResult = parseText(text, index, parentSourceInfo); |
| 74 | + } else if (DOM.isElementNode(childNode)) { |
| 75 | + var el = <Element>childNode; |
| 76 | + childResult = parseElement(el, index, parentSourceInfo); |
| 77 | + } |
| 78 | + if (isPresent(childResult)) { |
| 79 | + // Won't have a childResult for e.g. comment nodes |
| 80 | + result.push(childResult); |
| 81 | + } |
| 82 | + index++; |
| 83 | + }); |
| 84 | + return result; |
| 85 | +} |
| 86 | + |
| 87 | +function ignoreChildren(attrs: HtmlAttrAst[]): boolean { |
| 88 | + for (var i = 0; i < attrs.length; i++) { |
| 89 | + var a = attrs[i]; |
| 90 | + if (a.name == NG_NON_BINDABLE) { |
| 91 | + return true; |
| 92 | + } |
| 93 | + } |
| 94 | + return false; |
| 95 | +} |
0 commit comments