From 5447b04e86fa50fbb8936b4f344807ab7772cbdb Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Wed, 25 Nov 2015 12:45:02 +0200 Subject: [PATCH] Refactoring ui/builder and template builder to preserve source information for templates --- .../non-existing-element-in-template.xml | 19 + .../xml-declaration/xml-declaration-tests.ts | 27 +- tsconfig.json | 2 - ui/builder/builder.ts | 615 ++++++++++++------ ui/builder/template-builder.d.ts | 28 - ui/builder/template-builder.ts | 92 --- 6 files changed, 454 insertions(+), 329 deletions(-) create mode 100644 apps/tests/xml-declaration/errors/non-existing-element-in-template.xml delete mode 100644 ui/builder/template-builder.d.ts delete mode 100644 ui/builder/template-builder.ts diff --git a/apps/tests/xml-declaration/errors/non-existing-element-in-template.xml b/apps/tests/xml-declaration/errors/non-existing-element-in-template.xml new file mode 100644 index 000000000..2b9d87ff1 --- /dev/null +++ b/apps/tests/xml-declaration/errors/non-existing-element-in-template.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/apps/tests/xml-declaration/xml-declaration-tests.ts b/apps/tests/xml-declaration/xml-declaration-tests.ts index d07a4bcb2..08cb151cd 100644 --- a/apps/tests/xml-declaration/xml-declaration-tests.ts +++ b/apps/tests/xml-declaration/xml-declaration-tests.ts @@ -837,7 +837,7 @@ export function test_parse_template_property() { TKUnit.assertEqual(button.text, "Click!", "Expected child Button to have text 'Click!'"); } -export function test_ParserError() { +export function test_NonExistingElementError() { var basePath = "xml-declaration/"; var expectedErrorStart = "Building UI from XML. @file:///app/" + basePath + "errors/non-existing-element.xml:11:5\n" + @@ -856,3 +856,28 @@ export function test_ParserError() { } TKUnit.assertEqual(message.substr(0, expectedErrorStart.length), expectedErrorStart, "Expected load to throw, and the message to start with specific string"); } + +export function test_NonExistingElementInTemplateError() { + var basePath = "xml-declaration/"; + var expectedErrorStart = + "Building UI from XML. @file:///app/" + basePath + "errors/non-existing-element-in-template.xml:14:17\n" + + " ↳Module 'ui/unicorn' not found for element 'Unicorn'.\n"; + if (global.android) { + expectedErrorStart += " ↳Module \"ui/unicorn\" not found"; + } else { + expectedErrorStart += " ↳Failed to find module 'ui/unicorn'"; + } + + var message; + var page = builder.load(__dirname + "/errors/non-existing-element-in-template.xml"); + TKUnit.assert(view, "Expected the xml to generate a page"); + var templateView = page.getViewById("template-view"); + TKUnit.assert(templateView, "Expected the page to have a TemplateView with 'temaplte-view' id."); + + try { + templateView.parseTemplate(); + } catch(e) { + message = e.message; + } + TKUnit.assertEqual(message.substr(0, expectedErrorStart.length), expectedErrorStart, "Expected load to throw, and the message to start with specific string"); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b7aef072f..7464a795f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -478,8 +478,6 @@ "ui/builder/component-builder.ts", "ui/builder/special-properties.d.ts", "ui/builder/special-properties.ts", - "ui/builder/template-builder.d.ts", - "ui/builder/template-builder.ts", "ui/button/button-common.ts", "ui/button/button.android.ts", "ui/button/button.d.ts", diff --git a/ui/builder/builder.ts b/ui/builder/builder.ts index 56e18294a..3955e1b08 100644 --- a/ui/builder/builder.ts +++ b/ui/builder/builder.ts @@ -3,24 +3,13 @@ import fs = require("file-system"); import xml = require("xml"); import types = require("utils/types"); import componentBuilder = require("ui/builder/component-builder"); -import templateBuilderDef = require("ui/builder/template-builder"); import platform = require("platform"); import definition = require("ui/builder"); import page = require("ui/page"); import fileResolverModule = require("file-system/file-name-resolver"); import trace = require("trace"); import debug = require("utils/debug"); - -var KNOWNCOLLECTIONS = "knownCollections"; - -function isPlatform(value: string): boolean { - return value && (value.toLowerCase() === platform.platformNames.android.toLowerCase() - || value.toLowerCase() === platform.platformNames.ios.toLowerCase()); -} - -function isCurentPlatform(value: string): boolean { - return value && value.toLowerCase() === platform.device.os.toLowerCase(); -} +import builder = require("ui/builder"); export function parse(value: string | view.Template, context: any): view.View { if (types.isString(value)) { @@ -43,164 +32,19 @@ export function parse(value: string | view.Template, context: any): view.View { } function parseInternal(value: string, context: any, uri?: string): componentBuilder.ComponentModule { - var currentPage: page.Page; - var rootComponentModule: componentBuilder.ComponentModule; - // Temporary collection used for parent scope. - var parents = new Array(); - var complexProperties = new Array(); + + var start: xml2ui.XmlStringParser; + var ui: xml2ui.ComponentParser; + + var errorFormat = (debug.debug && uri) ? xml2ui.SourceErrorFormat(uri) : xml2ui.PositionErrorFormat; + + (start = new xml2ui.XmlStringParser(errorFormat)) + .pipe(new xml2ui.PlatformFilter()) + .pipe(new xml2ui.XmlStateParser(ui = new xml2ui.ComponentParser(context, errorFormat))); - var templateBuilder: templateBuilderDef.TemplateBuilder; + start.parse(value); - var currentPlatformContext: string; - - var wrapSource: (e: Error, p: xml.Position) => Error; - if (debug.debug && uri) { - wrapSource = (e: Error, p: xml.Position) => { - var source = new debug.Source(uri, p.line, p.column); - e = new debug.SourceError(e, source, "Building UI from XML."); - return e; - } - } else { - wrapSource = e => e; // no-op identity - } - - // Parse the XML. - var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => { - try { - if (args.eventType === xml.ParserEventType.StartElement) { - if (isPlatform(args.elementName)) { - - if (currentPlatformContext) { - throw new Error("Already in '" + currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested."); - } - - currentPlatformContext = args.elementName; - return; - } - } - - if (args.eventType === xml.ParserEventType.EndElement) { - if (isPlatform(args.elementName)) { - currentPlatformContext = undefined; - return; - } - } - - if (currentPlatformContext && !isCurentPlatform(currentPlatformContext)) { - return; - } - - if (templateBuilder) { - var finished = templateBuilder.handleElement(args); - if (finished) { - // Clean-up and continnue - templateBuilder = undefined; - } - else { - // Skip processing untill the template builder finishes his job. - return; - } - } - - // Get the current parent. - var parent = parents[parents.length - 1]; - var complexProperty = complexProperties[complexProperties.length - 1]; - - // Create component instance from every element declaration. - if (args.eventType === xml.ParserEventType.StartElement) { - if (isComplexProperty(args.elementName)) { - - var name = getComplexProperty(args.elementName); - - complexProperties.push({ - parent: parent, - name: name, - items: [], - }); - - if (templateBuilderDef.isKnownTemplate(name, parent.exports)) { - templateBuilder = new templateBuilderDef.TemplateBuilder({ - context: parent ? getExports(parent.component) : null, // Passing 'context' won't work if you set "codeFile" on the page - parent: parent, - name: name, - elementName: args.elementName, - templateItems: [] - }); - } - - } else { - - var componentModule: componentBuilder.ComponentModule; - - if (args.prefix && args.namespace) { - // Custom components - componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, context, currentPage); - } else { - // Default components - componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, context); - } - - if (componentModule) { - if (parent) { - if (componentModule.component instanceof view.View) { - if (complexProperty) { - // Add to complex property to component. - addToComplexProperty(parent, complexProperty, componentModule) - } else if ((parent.component)._addChildFromBuilder) { - // Add component to visual tree - (parent.component)._addChildFromBuilder(args.elementName, componentModule.component); - } - } else if (complexProperty) { - // Add component to complex property of parent component. - addToComplexProperty(parent, complexProperty, componentModule); - } else if ((parent.component)._addChildFromBuilder) { - (parent.component)._addChildFromBuilder(args.elementName, componentModule.component); - } - } else if (parents.length === 0) { - // Set root component. - rootComponentModule = componentModule; - - if (rootComponentModule && rootComponentModule.component instanceof page.Page) { - currentPage = rootComponentModule.component; - } - } - - // Add the component instance to the parents scope collection. - parents.push(componentModule); - } - } - - } else if (args.eventType === xml.ParserEventType.EndElement) { - if (isComplexProperty(args.elementName)) { - if (complexProperty) { - if (parent && (parent.component)._addArrayFromBuilder) { - // If parent is AddArrayFromBuilder call the interface method to populate the array property. - (parent.component)._addArrayFromBuilder(complexProperty.name, complexProperty.items); - complexProperty.items = []; - } - } - // Remove the last complexProperty from the complexProperties collection (move to the previous complexProperty scope). - complexProperties.pop(); - - } else { - // Remove the last parent from the parents collection (move to the previous parent scope). - parents.pop(); - } - } - - } catch(e) { - throw wrapSource(e, args.position); - } - }, (e, p) => { - throw wrapSource(new Error("XML parse error: " + e.message), p); - }, true); - - if (types.isString(value)) { - value = value.replace(/xmlns=("|')http:\/\/((www)|(schemas))\.nativescript\.org\/tns\.xsd\1/, ""); - xmlParser.parse(value); - } - - return rootComponentModule; + return ui.rootComponentModule; } function loadCustomComponent(componentPath: string, componentName?: string, attributes?: Object, context?: Object, parentPage?: page.Page): componentBuilder.ComponentModule { @@ -297,44 +141,6 @@ function loadInternal(fileName: string, context?: any): componentBuilder.Compone return componentModule; } -function isComplexProperty(name: string): boolean { - return types.isString(name) && name.indexOf(".") !== -1; -} - -function getComplexProperty(fullName: string): string { - var name: string; - - if (types.isString(fullName)) { - var names = fullName.split("."); - name = names[names.length - 1]; - } - - return name; -} - -function isKnownCollection(name: string, context: any): boolean { - return KNOWNCOLLECTIONS in context && context[KNOWNCOLLECTIONS] && name in context[KNOWNCOLLECTIONS]; -} - -function addToComplexProperty(parent: componentBuilder.ComponentModule, complexProperty: ComplexProperty, elementModule: componentBuilder.ComponentModule) { - // If property name is known collection we populate array with elements. - var parentComponent = parent.component; - if (isKnownCollection(complexProperty.name, parent.exports)) { - complexProperty.items.push(elementModule.component); - } else if (parentComponent._addChildFromBuilder) { - parentComponent._addChildFromBuilder(complexProperty.name, elementModule.component); - } else { - // Or simply assign the value; - parentComponent[complexProperty.name] = elementModule.component; - } -} - -interface ComplexProperty { - parent: componentBuilder.ComponentModule; - name: string; - items?: Array; -} - function getExports(instance: view.View): any { var parent = instance.parent; @@ -344,3 +150,400 @@ function getExports(instance: view.View): any { return parent ? (parent).exports : undefined; } + +namespace xml2ui { + + /** + * Pipes and filters: + * https://en.wikipedia.org/wiki/Pipeline_(software) + */ + interface XmlProducer { + pipe(next: Next): Next; + } + + interface XmlConsumer { + parse(args: xml.ParserEvent); + } + + export class XmlProducerBase implements XmlProducer { + private _next: XmlConsumer; + public pipe(next: Next) { + this._next = next; + return next; + } + protected next(args: xml.ParserEvent) { + this._next.parse(args); + } + } + + export class XmlStringParser extends XmlProducerBase implements XmlProducer { + private error: ErrorFormatter; + + constructor(error?: ErrorFormatter) { + super(); + this.error = error || PositionErrorFormat; + } + + public parse(value: string) { + var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => { + try { + this.next(args); + } catch(e) { + throw this.error(e, args.position); + } + }, (e, p) => { + throw this.error(e, p); + }, true); + + if (types.isString(value)) { + value = value.replace(/xmlns=("|')http:\/\/((www)|(schemas))\.nativescript\.org\/tns\.xsd\1/, ""); + xmlParser.parse(value); + } + } + } + + interface ErrorFormatter { + (e: Error, p: xml.Position): Error; + } + + export function PositionErrorFormat(e: Error, p: xml.Position): Error { + return new debug.ScopeError(e, "Parsing XML at " + p.line + ":" + p.column); + } + + export function SourceErrorFormat(uri): ErrorFormatter { + return (e: Error, p: xml.Position) => { + var source = new debug.Source(uri, p.line, p.column); + e = new debug.SourceError(e, source, "Building UI from XML."); + return e; + } + } + + export class PlatformFilter extends XmlProducerBase implements XmlProducer, XmlConsumer { + private currentPlatformContext: string; + + public parse(args: xml.ParserEvent) { + if (args.eventType === xml.ParserEventType.StartElement) { + if (PlatformFilter.isPlatform(args.elementName)) { + + if (this.currentPlatformContext) { + throw new Error("Already in '" + this.currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested."); + } + + this.currentPlatformContext = args.elementName; + return; + } + } + + if (args.eventType === xml.ParserEventType.EndElement) { + if (PlatformFilter.isPlatform(args.elementName)) { + this.currentPlatformContext = undefined; + return; + } + } + + if (this.currentPlatformContext && !PlatformFilter.isCurentPlatform(this.currentPlatformContext)) { + return; + } + + this.next(args); + } + + private static isPlatform(value: string): boolean { + return value && (value.toLowerCase() === platform.platformNames.android.toLowerCase() + || value.toLowerCase() === platform.platformNames.ios.toLowerCase()); + } + + private static isCurentPlatform(value: string): boolean { + return value && value.toLowerCase() === platform.device.os.toLowerCase(); + } + } + + export class XmlArgsReplay extends XmlProducerBase implements XmlProducer { + private error: ErrorFormatter; + private args: xml.ParserEvent[]; + + constructor(args: xml.ParserEvent[], errorFormat: ErrorFormatter) { + super(); + this.args = args; + this.error = errorFormat; + } + + public replay() { + this.args.forEach((args: xml.ParserEvent) => { + try { + this.next(args); + } catch(e) { + throw this.error(e, args.position); + } + }); + } + } + + interface TemplateProperty { + context?: any; + parent: componentBuilder.ComponentModule; + name: string; + elementName: string; + templateItems: Array; + errorFormat: ErrorFormatter; + } + + /** + * It is a state pattern + * https://en.wikipedia.org/wiki/State_pattern + */ + export class XmlStateParser implements XmlConsumer { + private state: XmlStateConsumer; + + constructor(state: XmlStateConsumer) { + this.state = state; + } + + parse(args: xml.ParserEvent) { + this.state = this.state.parse(args); + } + } + + interface XmlStateConsumer extends XmlConsumer { + parse(args: xml.ParserEvent): XmlStateConsumer; + } + + export class TemplateParser implements XmlStateConsumer { + + private _context: any; + private _recordedXmlStream: Array; + private _templateProperty: TemplateProperty; + private _nestingLevel: number; + private _state: TemplateParser.State; + + private parent: XmlStateConsumer; + + constructor(parent: XmlStateConsumer, templateProperty: TemplateProperty) { + this.parent = parent; + + this._context = templateProperty.context; + this._recordedXmlStream = new Array(); + this._templateProperty = templateProperty; + this._nestingLevel = 0; + this._state = TemplateParser.State.EXPECTING_START; + } + + public parse(args: xml.ParserEvent): XmlStateConsumer { + if (args.eventType === xml.ParserEventType.StartElement) { + this.parseStartElement(args.prefix, args.namespace, args.elementName, args.attributes); + } else if (args.eventType === xml.ParserEventType.EndElement) { + this.parseEndElement(args.prefix, args.elementName); + } + + this._recordedXmlStream.push(args); + + return this._state === TemplateParser.State.FINISHED ? this.parent : this; + } + + public get elementName(): string { + return this._templateProperty.elementName; + } + + private parseStartElement(prefix: string, namespace: string, elementName: string, attributes: Object) { + if (this._state === TemplateParser.State.EXPECTING_START) { + this._state = TemplateParser.State.PARSING; + } else if (this._state === TemplateParser.State.FINISHED) { + throw new Error("Template must have exactly one root element but multiple elements were found."); + } + + this._nestingLevel++; + } + + private parseEndElement(prefix: string, elementName: string) { + if (this._state === TemplateParser.State.EXPECTING_START) { + throw new Error("Template must have exactly one root element but none was found."); + } else if (this._state === TemplateParser.State.FINISHED) { + throw new Error("No more closing elements expected for this template."); + } + + this._nestingLevel--; + + if (this._nestingLevel === 0) { + this._state = TemplateParser.State.FINISHED; + this.build(); + } + } + + private build() { + if (this._templateProperty.name in this._templateProperty.parent.component) { + var context = this._context; + var errorFormat = this._templateProperty.errorFormat; + var template: view.Template = () => { + var start: xml2ui.XmlArgsReplay; + var ui: xml2ui.ComponentParser; + + (start = new xml2ui.XmlArgsReplay(this._recordedXmlStream, errorFormat)) + // No platform filter, it has been filtered allready + .pipe(new XmlStateParser(ui = new ComponentParser(context, errorFormat))); + + start.replay(); + + return ui.rootComponentModule.component; + } + this._templateProperty.parent.component[this._templateProperty.name] = template; + } + } + } + + export namespace TemplateParser { + export const enum State { + EXPECTING_START, + PARSING, + FINISHED + } + } + + export class ComponentParser implements XmlStateConsumer { + + private static KNOWNCOLLECTIONS = "knownCollections"; + private static KNOWNTEMPLATES = "knownTemplates"; + + public rootComponentModule: componentBuilder.ComponentModule; + + private context: any; + + private currentPage: page.Page; + private parents = new Array(); + private complexProperties = new Array(); + + private error; + + constructor(context: any, errorFormat: ErrorFormatter) { + this.context = context; + this.error = errorFormat; + } + + public parse(args: xml.ParserEvent): XmlStateConsumer { + + // Get the current parent. + var parent = this.parents[this.parents.length - 1]; + var complexProperty = this.complexProperties[this.complexProperties.length - 1]; + + // Create component instance from every element declaration. + if (args.eventType === xml.ParserEventType.StartElement) { + if (ComponentParser.isComplexProperty(args.elementName)) { + + var name = ComponentParser.getComplexPropertyName(args.elementName); + + this.complexProperties.push({ + parent: parent, + name: name, + items: [], + }); + + if (ComponentParser.isKnownTemplate(name, parent.exports)) { + return new TemplateParser(this, { + context: parent ? getExports(parent.component) : null, // Passing 'context' won't work if you set "codeFile" on the page + parent: parent, + name: name, + elementName: args.elementName, + templateItems: [], + errorFormat: this.error + }); + } + + } else { + + var componentModule: componentBuilder.ComponentModule; + + if (args.prefix && args.namespace) { + // Custom components + componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, this.context, this.currentPage); + } else { + // Default components + componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, this.context); + } + + if (componentModule) { + if (parent) { + if (complexProperty) { + // Add component to complex property of parent component. + ComponentParser.addToComplexProperty(parent, complexProperty, componentModule); + } else if ((parent.component)._addChildFromBuilder) { + (parent.component)._addChildFromBuilder(args.elementName, componentModule.component); + } + } else if (this.parents.length === 0) { + // Set root component. + this.rootComponentModule = componentModule; + + if (this.rootComponentModule && this.rootComponentModule.component instanceof page.Page) { + this.currentPage = this.rootComponentModule.component; + } + } + + // Add the component instance to the parents scope collection. + this.parents.push(componentModule); + } + } + + } else if (args.eventType === xml.ParserEventType.EndElement) { + if (ComponentParser.isComplexProperty(args.elementName)) { + if (complexProperty) { + if (parent && (parent.component)._addArrayFromBuilder) { + // If parent is AddArrayFromBuilder call the interface method to populate the array property. + (parent.component)._addArrayFromBuilder(complexProperty.name, complexProperty.items); + complexProperty.items = []; + } + } + // Remove the last complexProperty from the complexProperties collection (move to the previous complexProperty scope). + this.complexProperties.pop(); + + } else { + // Remove the last parent from the parents collection (move to the previous parent scope). + this.parents.pop(); + } + } + + return this; + } + + private static isComplexProperty(name: string): boolean { + return types.isString(name) && name.indexOf(".") !== -1; + } + + private static getComplexPropertyName(fullName: string): string { + var name: string; + + if (types.isString(fullName)) { + var names = fullName.split("."); + name = names[names.length - 1]; + } + + return name; + } + + private static isKnownTemplate(name: string, exports: any): boolean { + return ComponentParser.KNOWNTEMPLATES in exports && exports[ComponentParser.KNOWNTEMPLATES] && name in exports[ComponentParser.KNOWNTEMPLATES]; + } + + private static addToComplexProperty(parent: componentBuilder.ComponentModule, complexProperty: ComponentParser.ComplexProperty, elementModule: componentBuilder.ComponentModule) { + // If property name is known collection we populate array with elements. + var parentComponent = parent.component; + if (ComponentParser.isKnownCollection(complexProperty.name, parent.exports)) { + complexProperty.items.push(elementModule.component); + } else if (parentComponent._addChildFromBuilder) { + parentComponent._addChildFromBuilder(complexProperty.name, elementModule.component); + } else { + // Or simply assign the value; + parentComponent[complexProperty.name] = elementModule.component; + } + } + + private static isKnownCollection(name: string, context: any): boolean { + return ComponentParser.KNOWNCOLLECTIONS in context && context[ComponentParser.KNOWNCOLLECTIONS] && name in context[ComponentParser.KNOWNCOLLECTIONS]; + } + } + + export namespace ComponentParser { + export interface ComplexProperty { + parent: componentBuilder.ComponentModule; + name: string; + items?: Array; + } + } +} \ No newline at end of file diff --git a/ui/builder/template-builder.d.ts b/ui/builder/template-builder.d.ts deleted file mode 100644 index f989952bd..000000000 --- a/ui/builder/template-builder.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -//@private -declare module "ui/builder/template-builder" { - import xml = require("xml"); - import page = require("ui/page"); - import componentBuilder = require("ui/builder/component-builder"); - - class TemplateBuilder { - constructor(templateProperty: TemplateProperty); - - elementName: string; - - /* - * Returns true if the template builder has finished parsing template and the parsing should continue. - * @param args - ParserEvent argument to handle. - */ - handleElement(args: xml.ParserEvent): boolean; - } - - export function isKnownTemplate(name: string, exports: any): boolean; - - interface TemplateProperty { - context?: any; - parent: componentBuilder.ComponentModule; - name: string; - elementName: string; - templateItems: Array - } -} \ No newline at end of file diff --git a/ui/builder/template-builder.ts b/ui/builder/template-builder.ts deleted file mode 100644 index 53877e215..000000000 --- a/ui/builder/template-builder.ts +++ /dev/null @@ -1,92 +0,0 @@ -import definition = require("ui/builder/template-builder"); -import builder = require("ui/builder"); -import view = require("ui/core/view"); -import page = require("ui/page"); -import xml = require("xml"); - -var KNOWNTEMPLATES = "knownTemplates"; - -export class TemplateBuilder { - private _context: any; - private _items: Array; - private _templateProperty: definition.TemplateProperty; - private _nestingLevel: number; - - constructor(templateProperty: definition.TemplateProperty) { - this._context = templateProperty.context; - this._items = new Array(); - this._templateProperty = templateProperty; - this._nestingLevel = 0; - } - - public get elementName(): string { - return this._templateProperty.elementName; - } - - handleElement(args: xml.ParserEvent): boolean { - if (args.eventType === xml.ParserEventType.StartElement) { - this.addStartElement(args.prefix, args.namespace, args.elementName, args.attributes); - } else if (args.eventType === xml.ParserEventType.EndElement) { - this.addEndElement(args.prefix, args.elementName); - } - - if (this.hasFinished()) { - this.build(); - return true; - } - else { - return false; - } - } - - private addStartElement(prefix: string, namespace: string, elementName: string, attributes: Object) { - this._nestingLevel++; - this._items.push("<" + - getElementNameWithPrefix(prefix, elementName) + - (namespace ? " " + getNamespace(prefix, namespace) : "") + - (attributes ? " " + getAttributesAsString(attributes) : "") + - ">"); - } - - private addEndElement(prefix: string, elementName: string) { - this._nestingLevel--; - if (!this.hasFinished()) { - this._items.push(""); - } - } - - private hasFinished() { - return this._nestingLevel < 0; - } - - private build() { - if (this._templateProperty.name in this._templateProperty.parent.component) { - var xml = this._items.join(""); - var context = this._context; - var template: view.Template = () => builder.parse(xml, context); - this._templateProperty.parent.component[this._templateProperty.name] = template; - } - } -} - -export function isKnownTemplate(name: string, exports: any): boolean { - return KNOWNTEMPLATES in exports && exports[KNOWNTEMPLATES] && name in exports[KNOWNTEMPLATES]; -} - -function getAttributesAsString(attributes: Object): string { - var result = []; - - for (var item in attributes) { - result.push(item + '="' + attributes[item] + '"'); - } - - return result.join(" "); -} - -function getElementNameWithPrefix(prefix: string, elementName: string): string { - return (prefix ? prefix + ":" : "") + elementName; -} - -function getNamespace(prefix: string, namespace: string): string { - return 'xmlns:' + prefix + '="' + namespace + '"'; -} \ No newline at end of file