mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Merge pull request #1125 from NativeScript/cankov/xml-source-tracing
Cankov/xml source tracing
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<Page xmlns="http://schemas.nativescript.org/tns.xsd"
|
||||
xmlns:tc="xml-declaration/template-builder-tests/template-view">
|
||||
<tc:TemplateView id="template-view">
|
||||
<tc:TemplateView.template>
|
||||
<StackLayout>
|
||||
<!--
|
||||
At first I was going to put "MenuItem",
|
||||
as per https://github.com/NativeScript/NativeScript/issues/501,
|
||||
but then again we may re-introduce "MenuItem" in future and blow this test up.
|
||||
So here is something unique that has better chance not to appear.
|
||||
This comment also offsets error's row and column numbers so if you edit,
|
||||
please do so beyond the unicorn.
|
||||
-->
|
||||
<Unicorn backgroundColor="pink" />
|
||||
<Label text="Modal Page" />
|
||||
</StackLayout>
|
||||
</tc:TemplateView.template>
|
||||
</tc:TemplateView>
|
||||
</Page>
|
||||
14
apps/tests/xml-declaration/errors/non-existing-element.xml
Normal file
14
apps/tests/xml-declaration/errors/non-existing-element.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<Page shownModally="onShownModally">
|
||||
<StackLayout>
|
||||
<!--
|
||||
At first I was going to put "MenuItem",
|
||||
as per https://github.com/NativeScript/NativeScript/issues/501,
|
||||
but then again we may re-introduce "MenuItem" in future and blow this test up.
|
||||
So here is something unique that has better chance not to appear.
|
||||
This comment also offsets error's row and column numbers so if you edit,
|
||||
please do so beyond the unicorn.
|
||||
-->
|
||||
<Unicorn backgroundColor="pink" />
|
||||
<Label text="Modal Page" />
|
||||
</StackLayout>
|
||||
</Page>
|
||||
@@ -835,4 +835,49 @@ export function test_parse_template_property() {
|
||||
var button = <Button>templateView.getChildAt(0);
|
||||
TKUnit.assert(button, "Expected the TemplateView's template to create a button child.");
|
||||
TKUnit.assertEqual(button.text, "Click!", "Expected child Button to have text 'Click!'");
|
||||
}
|
||||
|
||||
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" +
|
||||
" ↳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;
|
||||
try {
|
||||
builder.load(__dirname + "/errors/non-existing-element.xml");
|
||||
} 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");
|
||||
}
|
||||
|
||||
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 = <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");
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"eventType":"StartElement","elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","elementName":"First.Element"}{"eventType":"StartElement","elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","data":"\n Pre-Text "}{"eventType":"StartElement","elementName":"Inline"}{"eventType":"Text","data":"Inlined text"}{"eventType":"EndElement","elementName":"Inline"}{"eventType":"Text","data":" Post-text.\n "}{"eventType":"EndElement","elementName":"SecondElement"}{"eventType":"StartElement","elementName":"entities"}{"eventType":"Text","data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","elementName":"entities"}{"eventType":"StartElement","elementName":"script"}{"eventType":"CDATA","data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","elementName":"script"}{"eventType":"Comment","data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","elementName":"DocumentElement"}
|
||||
{"eventType":"StartElement","position":{"line":2,"column":1},"elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","position":{"line":3,"column":3},"elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","position":{"line":3,"column":41},"data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","position":{"line":5,"column":3},"elementName":"First.Element"}{"eventType":"StartElement","position":{"line":7,"column":3},"elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","position":{"line":7,"column":37},"data":"\n Pre-Text "}{"eventType":"StartElement","position":{"line":8,"column":14},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":22},"data":"Inlined text"}{"eventType":"EndElement","position":{"line":8,"column":34},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":43},"data":" Post-text.\n "}{"eventType":"EndElement","position":{"line":9,"column":3},"elementName":"SecondElement"}{"eventType":"StartElement","position":{"line":10,"column":3},"elementName":"entities"}{"eventType":"Text","position":{"line":10,"column":13},"data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","position":{"line":10,"column":123},"elementName":"entities"}{"eventType":"StartElement","position":{"line":11,"column":3},"elementName":"script"}{"eventType":"CDATA","position":{"line":12,"column":5},"data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","position":{"line":18,"column":3},"elementName":"script"}{"eventType":"Comment","position":{"line":19,"column":3},"data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","position":{"line":23,"column":1},"elementName":"DocumentElement"}
|
||||
@@ -469,11 +469,24 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
, stop // используется при разборе "namespace" . если встретился неизвестное пространство то события не генерируются
|
||||
, _nsmatrix
|
||||
, ok
|
||||
, pos = 0, ln = 0, lnStart = -2, lnEnd = -1
|
||||
;
|
||||
|
||||
function getStringNode() {
|
||||
return xml.substring(i, j+1)
|
||||
};
|
||||
function findLineAndColumnFromPos() {
|
||||
while (lnStart < lnEnd && lnEnd < pos) {
|
||||
lnStart = lnEnd;
|
||||
lnEnd = xml.indexOf("\n", lnEnd + 1);
|
||||
++ln;
|
||||
}
|
||||
return { line: ln, column: pos - lnStart };
|
||||
}
|
||||
function position(p) {
|
||||
pos = p;
|
||||
return findLineAndColumnFromPos;
|
||||
}
|
||||
|
||||
while(j !== -1) {
|
||||
stop = stopIndex > 0;
|
||||
@@ -487,7 +500,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
if (i === -1) { // конец разбора
|
||||
|
||||
if (nodestack.length) {
|
||||
this.onError('end file');
|
||||
this.onError('end file', position(j));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -495,7 +508,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
};
|
||||
|
||||
if (j !== i && !stop) {
|
||||
ok = this.onTextNode(xml.substring(j, i), unEntities);
|
||||
ok = this.onTextNode(xml.substring(j, i), unEntities, position(j));
|
||||
if (ok === false) return;
|
||||
};
|
||||
|
||||
@@ -506,13 +519,13 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
if (w === 91 && xml.substr(i+3, 6) === 'CDATA[') { // 91 == "["
|
||||
j = xml.indexOf(']]>', i);
|
||||
if (j === -1) {
|
||||
this.onError('cdata');
|
||||
this.onError('cdata', position(i));
|
||||
return;
|
||||
};
|
||||
|
||||
//x = xml.substring(i+9, j);
|
||||
if (!stop) {
|
||||
ok = this.onCDATA(xml.substring(i+9, j), false);
|
||||
ok = this.onCDATA(xml.substring(i+9, j), false, position(i));
|
||||
if (ok === false) return;
|
||||
};
|
||||
|
||||
@@ -523,13 +536,13 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
if (w === 45 && xml.charCodeAt(i+3) === 45) { // 45 == "-"
|
||||
j = xml.indexOf('-->', i);
|
||||
if (j === -1) {
|
||||
this.onError('expected -->');
|
||||
this.onError('expected -->', position(i));
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
if (this.is_onComment && !stop) {
|
||||
ok = this.onComment(xml.substring(i+4, j), unEntities);
|
||||
ok = this.onComment(xml.substring(i+4, j), unEntities, position(i));
|
||||
if (ok === false) return;
|
||||
};
|
||||
|
||||
@@ -539,12 +552,12 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
|
||||
j = xml.indexOf('>', i+1);
|
||||
if (j === -1) {
|
||||
this.onError('expected ">"');
|
||||
this.onError('expected ">"', position(i + 1));
|
||||
return;
|
||||
};
|
||||
|
||||
if (this.is_onAttention && !stop) {
|
||||
ok = this.onAttention(xml.substring(i, j+1), unEntities);
|
||||
ok = this.onAttention(xml.substring(i, j+1), unEntities, position(i));
|
||||
if (ok === false) return;
|
||||
};
|
||||
|
||||
@@ -555,12 +568,12 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
if (w === 63) { // "?"
|
||||
j = xml.indexOf('?>', i);
|
||||
if (j === -1) { // error
|
||||
this.onError('...?>');
|
||||
this.onError('...?>', position(i));
|
||||
return;
|
||||
};
|
||||
|
||||
if (this.is_onQuestion) {
|
||||
ok = this.onQuestion(xml.substring(i, j+2));
|
||||
ok = this.onQuestion(xml.substring(i, j+2), position(i));
|
||||
if (ok === false) return;
|
||||
};
|
||||
|
||||
@@ -572,7 +585,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
j = xml.indexOf('>', i+1);
|
||||
|
||||
if (j == -1) { // error
|
||||
this.onError('...>');
|
||||
this.onError('...>', position(i + 1));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -589,7 +602,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
|
||||
//console.log()
|
||||
if (xml.substring(i+2, q) !== x) {
|
||||
this.onError('close tagname');
|
||||
this.onError('close tagname', position(i + 2));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -601,7 +614,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
continue;
|
||||
};
|
||||
|
||||
this.onError('close tag');
|
||||
this.onError('close tag', position(i + 2));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -619,7 +632,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
};
|
||||
|
||||
if ( !(w > 96 && w < 123 || w > 64 && w <91) ) {
|
||||
this.onError('first char nodeName');
|
||||
this.onError('first char nodeName', position(i + 1));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -636,7 +649,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
break;
|
||||
};
|
||||
|
||||
this.onError('invalid nodeName');
|
||||
this.onError('invalid nodeName', position(i + 1));
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -718,7 +731,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
|
||||
var that = this;
|
||||
ok = this.onStartNode(elem, function() { return that.getAttrs() }, unEntities, tagend
|
||||
, getStringNode
|
||||
, getStringNode, position(i)
|
||||
);
|
||||
|
||||
if (ok === false) {
|
||||
@@ -730,7 +743,7 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
|
||||
if (tagend) {
|
||||
ok = this.onEndNode(elem, unEntities, tagstart
|
||||
, getStringNode
|
||||
, getStringNode, position(i)
|
||||
);
|
||||
|
||||
if (ok === false) {
|
||||
|
||||
@@ -319,8 +319,8 @@
|
||||
"apps/tests/xml-declaration/mainPage.ts",
|
||||
"apps/tests/xml-declaration/mymodule/MyControl.ts",
|
||||
"apps/tests/xml-declaration/mymodulewithxml/MyControl.ts",
|
||||
"apps/tests/xml-declaration/xml-declaration-tests.ts",
|
||||
"apps/tests/xml-declaration/template-builder-tests/template-view.ts",
|
||||
"apps/tests/xml-declaration/xml-declaration-tests.ts",
|
||||
"apps/tests/xml-parser-tests/xml-parser-tests.ts",
|
||||
"apps/transforms/app.ts",
|
||||
"apps/transforms/main-page.ts",
|
||||
@@ -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",
|
||||
@@ -658,6 +656,8 @@
|
||||
"ui/web-view/web-view.android.ts",
|
||||
"ui/web-view/web-view.d.ts",
|
||||
"ui/web-view/web-view.ios.ts",
|
||||
"utils/debug.d.ts",
|
||||
"utils/debug.ts",
|
||||
"utils/module-merge.ts",
|
||||
"utils/number-utils.ts",
|
||||
"utils/types.d.ts",
|
||||
|
||||
@@ -3,23 +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");
|
||||
|
||||
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 debug = require("utils/debug");
|
||||
import builder = require("ui/builder");
|
||||
|
||||
export function parse(value: string | view.Template, context: any): view.View {
|
||||
if (types.isString(value)) {
|
||||
@@ -41,151 +31,20 @@ export function parse(value: string | view.Template, context: any): view.View {
|
||||
}
|
||||
}
|
||||
|
||||
function parseInternal(value: string, context: any): componentBuilder.ComponentModule {
|
||||
var currentPage: page.Page;
|
||||
var rootComponentModule: componentBuilder.ComponentModule;
|
||||
// Temporary collection used for parent scope.
|
||||
var parents = new Array<componentBuilder.ComponentModule>();
|
||||
var complexProperties = new Array<ComplexProperty>();
|
||||
function parseInternal(value: string, context: any, uri?: string): componentBuilder.ComponentModule {
|
||||
|
||||
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;
|
||||
|
||||
// Parse the XML.
|
||||
var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => {
|
||||
|
||||
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 ((<any>parent.component)._addChildFromBuilder) {
|
||||
// Add component to visual tree
|
||||
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
|
||||
}
|
||||
} else if (complexProperty) {
|
||||
// Add component to complex property of parent component.
|
||||
addToComplexProperty(parent, complexProperty, componentModule);
|
||||
} else if ((<any>parent.component)._addChildFromBuilder) {
|
||||
(<any>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 = <page.Page>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 && (<any>parent.component)._addArrayFromBuilder) {
|
||||
// If parent is AddArrayFromBuilder call the interface method to populate the array property.
|
||||
(<any>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();
|
||||
}
|
||||
}
|
||||
|
||||
}, (e) => {
|
||||
throw new Error("XML parse error: " + e.message);
|
||||
}, 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 {
|
||||
@@ -271,7 +130,7 @@ function loadInternal(fileName: string, context?: any): componentBuilder.Compone
|
||||
throw new Error("Error loading file " + fileName + " :" + error.message);
|
||||
}
|
||||
var text = file.readTextSync(onError);
|
||||
componentModule = parseInternal(text, context);
|
||||
componentModule = parseInternal(text, context, fileName);
|
||||
}
|
||||
|
||||
if (componentModule && componentModule.component) {
|
||||
@@ -282,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 = <any>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<any>;
|
||||
}
|
||||
|
||||
function getExports(instance: view.View): any {
|
||||
var parent = instance.parent;
|
||||
|
||||
@@ -329,3 +150,400 @@ function getExports(instance: view.View): any {
|
||||
|
||||
return parent ? (<any>parent).exports : undefined;
|
||||
}
|
||||
|
||||
namespace xml2ui {
|
||||
|
||||
/**
|
||||
* Pipes and filters:
|
||||
* https://en.wikipedia.org/wiki/Pipeline_(software)
|
||||
*/
|
||||
interface XmlProducer {
|
||||
pipe<Next extends XmlConsumer>(next: Next): Next;
|
||||
}
|
||||
|
||||
interface XmlConsumer {
|
||||
parse(args: xml.ParserEvent);
|
||||
}
|
||||
|
||||
export class XmlProducerBase implements XmlProducer {
|
||||
private _next: XmlConsumer;
|
||||
public pipe<Next extends XmlConsumer>(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<string>;
|
||||
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<xml.ParserEvent>;
|
||||
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<xml.ParserEvent>();
|
||||
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<componentBuilder.ComponentModule>();
|
||||
private complexProperties = new Array<ComponentParser.ComplexProperty>();
|
||||
|
||||
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 ((<any>parent.component)._addChildFromBuilder) {
|
||||
(<any>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 = <page.Page>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 && (<any>parent.component)._addArrayFromBuilder) {
|
||||
// If parent is AddArrayFromBuilder call the interface method to populate the array property.
|
||||
(<any>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 = <any>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<any>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import fs = require("file-system");
|
||||
import bindingBuilder = require("./binding-builder");
|
||||
import platform = require("platform");
|
||||
import pages = require("ui/page");
|
||||
import debug = require("utils/debug");
|
||||
|
||||
//the imports below are needed for special property registration
|
||||
import "ui/layouts/dock-layout";
|
||||
@@ -60,7 +61,7 @@ export function getComponentModule(elementName: string, namespace: string, attri
|
||||
// Create instance of the component.
|
||||
instance = new instanceType();
|
||||
} catch (ex) {
|
||||
throw new Error("Cannot create module " + moduleId + ". " + ex + ". StackTrace: " + ex.stack);
|
||||
throw new debug.ScopeError(ex, "Module '" + moduleId + "' not found for element '" + (namespace ? namespace + ":" : "") + elementName + "'.");
|
||||
}
|
||||
|
||||
if (attributes) {
|
||||
|
||||
28
ui/builder/template-builder.d.ts
vendored
28
ui/builder/template-builder.d.ts
vendored
@@ -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<string>
|
||||
}
|
||||
}
|
||||
@@ -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<string>;
|
||||
private _templateProperty: definition.TemplateProperty;
|
||||
private _nestingLevel: number;
|
||||
|
||||
constructor(templateProperty: definition.TemplateProperty) {
|
||||
this._context = templateProperty.context;
|
||||
this._items = new Array<string>();
|
||||
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("</" + getElementNameWithPrefix(prefix, elementName) + ">");
|
||||
}
|
||||
}
|
||||
|
||||
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 + '"';
|
||||
}
|
||||
92
utils/debug.d.ts
vendored
Normal file
92
utils/debug.d.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
declare module "utils/debug" {
|
||||
/**
|
||||
* A runtime option indicating whether the build has debugging enabled.
|
||||
*/
|
||||
export var debug: boolean;
|
||||
|
||||
/**
|
||||
* A class encapsulating information for source code origin.
|
||||
*/
|
||||
export class Source {
|
||||
|
||||
/**
|
||||
* Creates a new Source instance by given uri, line and column.
|
||||
*/
|
||||
constructor(uri: string, line: number, column: number);
|
||||
|
||||
/**
|
||||
* Gets the URI of the source document;
|
||||
*/
|
||||
uri: string;
|
||||
|
||||
/**
|
||||
* Gets the line in the source document.
|
||||
*/
|
||||
line: number;
|
||||
|
||||
/**
|
||||
* Gets the position in the source document.
|
||||
*/
|
||||
column: number;
|
||||
|
||||
/**
|
||||
* Get the source of an object.
|
||||
*/
|
||||
public static get(object: any): Source;
|
||||
|
||||
/**
|
||||
* Set the source of an object.
|
||||
*/
|
||||
public static set(object: any, src: Source);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Error class that provides additional context to an error.
|
||||
*/
|
||||
export class ScopeError implements Error {
|
||||
/**
|
||||
* Creates a new ScopeError providing addtional context to the child error.
|
||||
* @param child The child error to extend.
|
||||
* @param message Additional message to prepend to the child error.
|
||||
*/
|
||||
constructor(child: Error, message?: string);
|
||||
|
||||
/**
|
||||
* Gets the child error.
|
||||
*/
|
||||
child: Error;
|
||||
|
||||
/**
|
||||
* Gets the error message.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Gets the stack trace.
|
||||
*/
|
||||
stack: string;
|
||||
|
||||
/**
|
||||
* Gets the error name.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scope error providing addiot
|
||||
*/
|
||||
export class SourceError extends ScopeError {
|
||||
/**
|
||||
* Creates a new SourceError by child error, source and optional message.
|
||||
* @param child The child error to extend.
|
||||
* @param source The source where the error occured.
|
||||
* @param message Additonal message to prepend along the source location and the child error's message.
|
||||
*/
|
||||
constructor(child: Error, source: Source, message?: string);
|
||||
|
||||
/**
|
||||
* Gets the error source.
|
||||
*/
|
||||
source: Source;
|
||||
}
|
||||
}
|
||||
83
utils/debug.ts
Normal file
83
utils/debug.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { knownFolders } from "file-system"
|
||||
|
||||
export var debug = true;
|
||||
|
||||
// TODO: Get this from the runtimes...
|
||||
var applicationRootPath = knownFolders.currentApp().path;
|
||||
applicationRootPath = applicationRootPath.substr(0, applicationRootPath.length - "app/".length);
|
||||
|
||||
export class Source {
|
||||
private _uri: string;
|
||||
private _line: number;
|
||||
private _column: number;
|
||||
|
||||
private static _source: symbol = Symbol("source");
|
||||
private static _appRoot: string;
|
||||
|
||||
constructor(uri: string, line: number, column: number) {
|
||||
if (uri.length > applicationRootPath.length && uri.substr(0, applicationRootPath.length) === applicationRootPath) {
|
||||
this._uri = "file://" + uri.substr(applicationRootPath.length);
|
||||
} else {
|
||||
this._uri = uri;
|
||||
}
|
||||
this._line = line;
|
||||
this._column = column;
|
||||
}
|
||||
|
||||
get uri(): string { return this._uri; }
|
||||
get line(): number { return this._line; }
|
||||
get column(): number { return this._column; }
|
||||
|
||||
public toString() {
|
||||
return this._uri + ":" + this._line + ":" + this._column;
|
||||
}
|
||||
|
||||
public static get(object: any): Source {
|
||||
return object[Source._source];
|
||||
}
|
||||
|
||||
public static set(object: any, src: Source) {
|
||||
object[Source._source] = src;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScopeError implements Error {
|
||||
private _child: Error;
|
||||
private _message: string;
|
||||
|
||||
constructor(child: Error, message?: string) {
|
||||
if (!child) {
|
||||
throw new Error("Required child error!");
|
||||
}
|
||||
this._child = child;
|
||||
this._message = message;
|
||||
}
|
||||
|
||||
get child() { return this._child; }
|
||||
get message() {
|
||||
if (this._message && this._childMessage) {
|
||||
// It is a ↳ but the ios fails to show this symbol at the moment.
|
||||
return this._message + "\n \u21B3" + this._childMessage.replace("\n", "\n ");
|
||||
}
|
||||
return this._message || this._childMessage || undefined;
|
||||
}
|
||||
get name() { return this.child.name; }
|
||||
get stack() { return (<any>this.child).stack; }
|
||||
|
||||
private get _childMessage(): string {
|
||||
return this.child.message;
|
||||
}
|
||||
|
||||
public toString() { return "Error: " + this.message; }
|
||||
}
|
||||
|
||||
export class SourceError extends ScopeError {
|
||||
private _source: Source;
|
||||
|
||||
constructor(child: Error, source: Source, message?: string) {
|
||||
super(child, message ? message + " @" + source + "" : source + "");
|
||||
this._source = source;
|
||||
}
|
||||
|
||||
get source() { return this._source; }
|
||||
}
|
||||
42
xml/xml.d.ts
vendored
42
xml/xml.d.ts
vendored
@@ -33,6 +33,21 @@ declare module "xml" {
|
||||
*/
|
||||
static Comment: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a position within string, in line and column form.
|
||||
*/
|
||||
interface Position {
|
||||
/**
|
||||
* The line number. The first line is at index 1.
|
||||
*/
|
||||
line: number;
|
||||
|
||||
/**
|
||||
* The column number. The first character is at index 1.
|
||||
*/
|
||||
column: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides information for a parser event.
|
||||
@@ -43,6 +58,11 @@ declare module "xml" {
|
||||
* Returns the type of the parser event. This is one of the ParserEventType static members.
|
||||
*/
|
||||
eventType: string;
|
||||
|
||||
/**
|
||||
* Get the position in the xml string where the event was generated.
|
||||
*/
|
||||
position: Position;
|
||||
|
||||
/**
|
||||
* If namespace processing is enabled, returns the prefix of the element in case the eventType is ParserEventType.StartElement or ParserEventType.EndElement.
|
||||
@@ -80,18 +100,18 @@ declare module "xml" {
|
||||
*/
|
||||
class XmlParser {
|
||||
|
||||
/**
|
||||
* Creates a new instance of the XmlParser class.
|
||||
* @param onEvent The callback to execute when a parser event occurs. The 'event' parameter contains information about the event.
|
||||
* @param onError The callback to execute when a parser error occurs. The 'error' parameter contains the error.
|
||||
* @param processNamespaces Specifies whether namespaces should be processed.
|
||||
*/
|
||||
constructor(onEvent: (event: ParserEvent) => void, onError?: (error: Error) => void, processNamespaces?: boolean, angularSyntax?: boolean);
|
||||
/**
|
||||
* Creates a new instance of the XmlParser class.
|
||||
* @param onEvent The callback to execute when a parser event occurs. The 'event' parameter contains information about the event.
|
||||
* @param onError The callback to execute when a parser error occurs. The 'error' parameter contains the error.
|
||||
* @param processNamespaces Specifies whether namespaces should be processed.
|
||||
*/
|
||||
constructor(onEvent: (event: ParserEvent) => void, onError?: (error: Error, position: Position) => void, processNamespaces?: boolean, angularSyntax?: boolean);
|
||||
|
||||
/**
|
||||
* Parses the supplied xml string.
|
||||
* @param xmlString The string containing the xml to parse.
|
||||
*/
|
||||
/**
|
||||
* Parses the supplied xml string.
|
||||
* @param xmlString The string containing the xml to parse.
|
||||
*/
|
||||
parse(xmlString: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
35
xml/xml.ts
35
xml/xml.ts
@@ -11,14 +11,16 @@ export class ParserEventType implements definition.ParserEventType {
|
||||
|
||||
export class ParserEvent implements definition.ParserEvent {
|
||||
private _eventType: string;
|
||||
private _position: definition.Position;
|
||||
private _prefix: string;
|
||||
private _namespace: string;
|
||||
private _elementName: string;
|
||||
private _attributes: Object;
|
||||
private _data: string;
|
||||
|
||||
constructor(eventType: string, prefix?: string, namespace?: string, elementName?: string, attributes?: Object, data?: string) {
|
||||
constructor(eventType: string, position: definition.Position, prefix?: string, namespace?: string, elementName?: string, attributes?: Object, data?: string) {
|
||||
this._eventType = eventType;
|
||||
this._position = position;
|
||||
this._prefix = prefix;
|
||||
this._namespace = namespace;
|
||||
this._elementName = elementName;
|
||||
@@ -29,6 +31,7 @@ export class ParserEvent implements definition.ParserEvent {
|
||||
public toString(): string {
|
||||
return JSON.stringify({
|
||||
eventType: this.eventType,
|
||||
position: this.position,
|
||||
prefix: this.prefix,
|
||||
namespace: this.namespace,
|
||||
elementName: this.elementName,
|
||||
@@ -40,6 +43,10 @@ export class ParserEvent implements definition.ParserEvent {
|
||||
public get eventType(): string {
|
||||
return this._eventType;
|
||||
}
|
||||
|
||||
public get position(): definition.Position {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
public get prefix(): string {
|
||||
return this._prefix;
|
||||
@@ -103,12 +110,12 @@ export class XmlParser implements definition.XmlParser {
|
||||
private _processNamespaces: boolean;
|
||||
private _namespaceStack: Array<any>;
|
||||
|
||||
constructor(onEvent: (event: definition.ParserEvent) => void, onError?: (error: Error) => void, processNamespaces?: boolean) {
|
||||
constructor(onEvent: (event: definition.ParserEvent) => void, onError?: (error: Error, position: definition.Position) => void, processNamespaces?: boolean) {
|
||||
this._processNamespaces = processNamespaces;
|
||||
this._parser = new easysax.EasySAXParser();
|
||||
|
||||
var that = this;
|
||||
this._parser.on('startNode', function (elem, attr, uq, str, tagend) {
|
||||
this._parser.on('startNode', function (elem, attr, uq, tagend, str, pos) {
|
||||
var attributes = attr();
|
||||
|
||||
if (attributes === true) {//HACK: For some reason easysax returns the true literal when an element has no attributes.
|
||||
@@ -139,15 +146,15 @@ export class XmlParser implements definition.XmlParser {
|
||||
name = resolved.name;
|
||||
}
|
||||
|
||||
onEvent(new ParserEvent(ParserEventType.StartElement, prefix, namespace, name, attributes, undefined));
|
||||
onEvent(new ParserEvent(ParserEventType.StartElement, pos(), prefix, namespace, name, attributes, undefined));
|
||||
});
|
||||
|
||||
this._parser.on('textNode', function (text, uq) {
|
||||
this._parser.on('textNode', function (text, uq, pos) {
|
||||
var data = uq(XmlParser._dereferenceEntities(text));// Decode entity references such as < and >
|
||||
onEvent(new ParserEvent(ParserEventType.Text, undefined, undefined, undefined, undefined, data));
|
||||
onEvent(new ParserEvent(ParserEventType.Text, pos(), undefined, undefined, undefined, undefined, data));
|
||||
});
|
||||
|
||||
this._parser.on('endNode', function (elem, uq, tagstart, str) {
|
||||
this._parser.on('endNode', function (elem, uq, tagstart, str, pos) {
|
||||
|
||||
var prefix = undefined;
|
||||
var namespace = undefined;
|
||||
@@ -160,24 +167,24 @@ export class XmlParser implements definition.XmlParser {
|
||||
name = resolved.name;
|
||||
}
|
||||
|
||||
onEvent(new ParserEvent(ParserEventType.EndElement, prefix, namespace, name, undefined, undefined));
|
||||
onEvent(new ParserEvent(ParserEventType.EndElement, pos(), prefix, namespace, name, undefined, undefined));
|
||||
|
||||
if (that._processNamespaces) {
|
||||
that._namespaceStack.pop();
|
||||
}
|
||||
});
|
||||
|
||||
this._parser.on('cdata', function (data) {
|
||||
onEvent(new ParserEvent(ParserEventType.CDATA, undefined, undefined, undefined, undefined, data));
|
||||
this._parser.on('cdata', function (data, res, pos) {
|
||||
onEvent(new ParserEvent(ParserEventType.CDATA, pos(), undefined, undefined, undefined, undefined, data));
|
||||
});
|
||||
|
||||
this._parser.on('comment', function (text) {
|
||||
onEvent(new ParserEvent(ParserEventType.Comment, undefined, undefined, undefined, undefined, text));
|
||||
this._parser.on('comment', function (text, uq, pos) {
|
||||
onEvent(new ParserEvent(ParserEventType.Comment, pos(), undefined, undefined, undefined, undefined, text));
|
||||
});
|
||||
|
||||
if (onError) {
|
||||
this._parser.on('error', function (msg) {
|
||||
onError(new Error(msg));
|
||||
this._parser.on('error', function (msg, pos) {
|
||||
onError(new Error(msg), pos());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user