Skip to content

Commit cc033ef

Browse files
committed
Refactoring xml builder
1 parent 2265798 commit cc033ef

File tree

3 files changed

+315
-166
lines changed

3 files changed

+315
-166
lines changed

ui/builder/builder.ts

Lines changed: 134 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -43,83 +43,165 @@ export function parse(value: string | view.Template, context: any): view.View {
4343
}
4444

4545
function parseInternal(value: string, context: any, uri?: string): componentBuilder.ComponentModule {
46-
var currentPage: page.Page;
47-
var rootComponentModule: componentBuilder.ComponentModule;
48-
// Temporary collection used for parent scope.
49-
var parents = new Array<componentBuilder.ComponentModule>();
50-
var complexProperties = new Array<ComplexProperty>();
46+
47+
var start: xml2ui.XmlStringParser;
48+
var ui: xml2ui.UIParser;
49+
50+
var errorFormat = (debug.debug && uri) ? xml2ui.SourceErrorFormat(uri) : xml2ui.PositionErrorFormat;
51+
52+
(start = new xml2ui.XmlStringParser(errorFormat))
53+
.pipe(new xml2ui.PlatformFilter())
54+
.pipe(ui = new xml2ui.UIParser(context));
5155

52-
var templateBuilder: templateBuilderDef.TemplateBuilder;
56+
start.parseString(value);
57+
58+
return ui.rootComponentModule;
59+
}
5360

54-
var currentPlatformContext: string;
61+
namespace xml2ui {
62+
63+
export class XmlStream {
64+
private next: XmlStream;
65+
66+
public pipe<Next extends XmlStream>(next: Next): Next {
67+
this.next = next;
68+
return next;
69+
}
70+
71+
public parse(args: xml.ParserEvent): XmlStream {
72+
this.next = this.next.parse(args);
73+
return this;
74+
}
75+
}
76+
77+
export class XmlStringParser extends XmlStream {
78+
private error: ErrorFormatter;
79+
80+
constructor(error?: ErrorFormatter) {
81+
super();
82+
this.error = error || PositionErrorFormat;
83+
}
84+
85+
public parseString(value: string) {
86+
var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => {
87+
try {
88+
super.parse(args);
89+
} catch(e) {
90+
throw this.error(e, args.position);
91+
}
92+
}, (e, p) => {
93+
throw this.error(e, p);
94+
}, true);
95+
96+
if (types.isString(value)) {
97+
value = value.replace(/xmlns=("|')http:\/\/((www)|(schemas))\.nativescript\.org\/tns\.xsd\1/, "");
98+
xmlParser.parse(value);
99+
}
100+
}
101+
}
102+
103+
interface ErrorFormatter {
104+
(e: Error, p: xml.Position): Error;
105+
}
106+
107+
export function PositionErrorFormat(e: Error, p: xml.Position): Error {
108+
return new debug.ScopeError(e, "Parsing XML at " + p.line + ":" + p.column);
109+
}
55110

56-
var wrapSource: (e: Error, p: xml.Position) => Error;
57-
if (debug.debug && uri) {
58-
wrapSource = (e: Error, p: xml.Position) => {
111+
export function SourceErrorFormat(uri): ErrorFormatter {
112+
return (e: Error, p: xml.Position) => {
59113
var source = new debug.Source(uri, p.line, p.column);
60114
e = new debug.SourceError(e, source, "Building UI from XML.");
61115
return e;
62116
}
63-
} else {
64-
wrapSource = e => e; // no-op identity
65-
}
66-
67-
// Parse the XML.
68-
var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => {
69-
try {
70-
if (args.eventType === xml.ParserEventType.StartElement) {
117+
}
118+
119+
export class PlatformFilter extends XmlStream {
120+
private currentPlatformContext: string;
121+
122+
public parse(args: xml.ParserEvent): XmlStream {
123+
if (args.eventType === xml.ParserEventType.StartElement) {
71124
if (isPlatform(args.elementName)) {
72125

73-
if (currentPlatformContext) {
74-
throw new Error("Already in '" + currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested.");
126+
if (this.currentPlatformContext) {
127+
throw new Error("Already in '" + this.currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested.");
75128
}
76129

77-
currentPlatformContext = args.elementName;
78-
return;
130+
this.currentPlatformContext = args.elementName;
131+
return this;
79132
}
80133
}
81134

82135
if (args.eventType === xml.ParserEventType.EndElement) {
83136
if (isPlatform(args.elementName)) {
84-
currentPlatformContext = undefined;
85-
return;
137+
this.currentPlatformContext = undefined;
138+
return this;
86139
}
87140
}
88141

89-
if (currentPlatformContext && !isCurentPlatform(currentPlatformContext)) {
90-
return;
142+
if (this.currentPlatformContext && !isCurentPlatform(this.currentPlatformContext)) {
143+
return this;
91144
}
145+
146+
return super.parse(args);
147+
}
148+
}
92149

93-
if (templateBuilder) {
94-
var finished = templateBuilder.handleElement(args);
95-
if (finished) {
96-
// Clean-up and continnue
97-
templateBuilder = undefined;
98-
}
99-
else {
100-
// Skip processing untill the template builder finishes his job.
101-
return;
102-
}
150+
export class TemplateParser extends XmlStream {
151+
152+
private templateBuilder: templateBuilderDef.TemplateBuilder;
153+
private previous: XmlStream;
154+
155+
constructor(previous: XmlStream, template: templateBuilderDef.TemplateProperty) {
156+
super();
157+
this.previous = previous;
158+
this.templateBuilder = new templateBuilderDef.TemplateBuilder(template);
159+
}
160+
161+
public parse(args: xml.ParserEvent): XmlStream {
162+
if (this.templateBuilder.handleElement(args)) {
163+
return this.previous;
164+
} else {
165+
return this;
103166
}
167+
}
168+
}
169+
170+
export class UIParser extends XmlStream {
171+
172+
public rootComponentModule: componentBuilder.ComponentModule;
173+
174+
private context: any;
175+
176+
private currentPage: page.Page;
177+
private parents = new Array<componentBuilder.ComponentModule>();
178+
private complexProperties = new Array<ComplexProperty>();
179+
180+
constructor(context: any) {
181+
super();
182+
this.context = context;
183+
}
184+
185+
public parse(args: xml.ParserEvent): XmlStream {
104186

105187
// Get the current parent.
106-
var parent = parents[parents.length - 1];
107-
var complexProperty = complexProperties[complexProperties.length - 1];
188+
var parent = this.parents[this.parents.length - 1];
189+
var complexProperty = this.complexProperties[this.complexProperties.length - 1];
108190

109191
// Create component instance from every element declaration.
110192
if (args.eventType === xml.ParserEventType.StartElement) {
111193
if (isComplexProperty(args.elementName)) {
112194

113195
var name = getComplexProperty(args.elementName);
114196

115-
complexProperties.push({
197+
this.complexProperties.push({
116198
parent: parent,
117199
name: name,
118200
items: [],
119201
});
120202

121203
if (templateBuilderDef.isKnownTemplate(name, parent.exports)) {
122-
templateBuilder = new templateBuilderDef.TemplateBuilder({
204+
return new TemplateParser(this, {
123205
context: parent ? getExports(parent.component) : null, // Passing 'context' won't work if you set "codeFile" on the page
124206
parent: parent,
125207
name: name,
@@ -134,39 +216,31 @@ function parseInternal(value: string, context: any, uri?: string): componentBuil
134216

135217
if (args.prefix && args.namespace) {
136218
// Custom components
137-
componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, context, currentPage);
219+
componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, this.context, this.currentPage);
138220
} else {
139221
// Default components
140-
componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, context);
222+
componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, this.context);
141223
}
142224

143225
if (componentModule) {
144226
if (parent) {
145-
if (componentModule.component instanceof view.View) {
146-
if (complexProperty) {
147-
// Add to complex property to component.
148-
addToComplexProperty(parent, complexProperty, componentModule)
149-
} else if ((<any>parent.component)._addChildFromBuilder) {
150-
// Add component to visual tree
151-
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
152-
}
153-
} else if (complexProperty) {
227+
if (complexProperty) {
154228
// Add component to complex property of parent component.
155229
addToComplexProperty(parent, complexProperty, componentModule);
156230
} else if ((<any>parent.component)._addChildFromBuilder) {
157231
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
158232
}
159-
} else if (parents.length === 0) {
233+
} else if (this.parents.length === 0) {
160234
// Set root component.
161-
rootComponentModule = componentModule;
235+
this.rootComponentModule = componentModule;
162236

163-
if (rootComponentModule && rootComponentModule.component instanceof page.Page) {
164-
currentPage = <page.Page>rootComponentModule.component;
237+
if (this.rootComponentModule && this.rootComponentModule.component instanceof page.Page) {
238+
this.currentPage = <page.Page>this.rootComponentModule.component;
165239
}
166240
}
167241

168242
// Add the component instance to the parents scope collection.
169-
parents.push(componentModule);
243+
this.parents.push(componentModule);
170244
}
171245
}
172246

@@ -180,27 +254,17 @@ function parseInternal(value: string, context: any, uri?: string): componentBuil
180254
}
181255
}
182256
// Remove the last complexProperty from the complexProperties collection (move to the previous complexProperty scope).
183-
complexProperties.pop();
257+
this.complexProperties.pop();
184258

185259
} else {
186260
// Remove the last parent from the parents collection (move to the previous parent scope).
187-
parents.pop();
261+
this.parents.pop();
188262
}
189263
}
190-
191-
} catch(e) {
192-
throw wrapSource(e, args.position);
264+
265+
return this;
193266
}
194-
}, (e, p) => {
195-
throw wrapSource(new Error("XML parse error: " + e.message), p);
196-
}, true);
197-
198-
if (types.isString(value)) {
199-
value = value.replace(/xmlns=("|')http:\/\/((www)|(schemas))\.nativescript\.org\/tns\.xsd\1/, "");
200-
xmlParser.parse(value);
201267
}
202-
203-
return rootComponentModule;
204268
}
205269

206270
function loadCustomComponent(componentPath: string, componentName?: string, attributes?: Object, context?: Object, parentPage?: page.Page): componentBuilder.ComponentModule {

0 commit comments

Comments
 (0)