Skip to content

Commit 5bef0ca

Browse files
authored
Merge pull request #12 from dmunozv04/develop
Parse json config files
2 parents 97109f7 + 1ac7062 commit 5bef0ca

1 file changed

Lines changed: 293 additions & 15 deletions

File tree

src/ConfigParser.js

Lines changed: 293 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,318 @@ function getContent(path) {
1313
}
1414
}
1515

16-
function parseContent(content) {
16+
ConfigParser.prototype.parseContent = function (content) {
1717
try {
18-
return libxml.parseXml(content);
18+
this.type = "json";
19+
return JSON.parse(content);
1920
} catch (e) {
20-
throw e;
21+
// If it's not JSON, try parsing it as XML
22+
try {
23+
this.type = "xml";
24+
return libxml.parseXml(content);
25+
} catch (e) {
26+
throw new Error('Invalid config file format');
27+
}
2128
}
2229
}
2330

2431
function ConfigParser(options) {
2532
if (typeof options === 'string') {
26-
options = {path: options};
33+
options = { path: options };
2734
}
2835

2936
var content = options.path ? getContent(options.path) : options.content;
30-
this.data = parseContent(content);
31-
32-
if (options.validate && !this.validate()) {
37+
this.data = this.parseContent(content);
38+
if (!this.validate()) {
3339
throw new Error('Validation Error: Invalid config.xml file');
3440
}
3541
}
3642

3743
ConfigParser.prototype.validate = function () {
38-
var res = request('GET', SCHEMA_URL);
39-
var body = res.getBody();
40-
var schema;
41-
try {
42-
schema = libxml.parseXml(body);
43-
} catch (e) {
44-
throw e;
44+
if (this.type === "xml") {
45+
var res = request('GET', SCHEMA_URL);
46+
var body = res.getBody();
47+
var schema;
48+
try {
49+
schema = libxml.parseXml(body);
50+
} catch (e) {
51+
throw e;
52+
}
53+
54+
return this.data.validate(schema);
55+
}
56+
else if (this.type === "json") {
57+
try {
58+
this.validateJson(body);
59+
return true;
60+
}
61+
catch (e) {
62+
throw e;
63+
}
4564
}
4665

47-
return this.data.validate(schema);
4866
};
4967

68+
function checkArrayFields(fields, place, required = false) {
69+
if (!Array.isArray(fields)) {
70+
fields = [fields];
71+
}
72+
73+
for (let field of fields) {
74+
if (place[field] === undefined) {
75+
if (required) {
76+
throw new TemplateParseException(`Missing required field: ${field}`);
77+
}
78+
79+
} else if (!Array.isArray(place[field])) {
80+
throw new TemplateParseException(`An array value was expected for the ${field} field`);
81+
}
82+
}
83+
}
84+
85+
function checkStringFields(fields, place, required = false, allowNull = false) {
86+
if (!Array.isArray(fields)) {
87+
fields = [fields];
88+
}
89+
90+
for (let field of fields) {
91+
if (place[field] === undefined) {
92+
if (required) {
93+
throw new TemplateParseException(`Missing required field: ${field}`);
94+
}
95+
96+
} else if (typeof place[field] !== 'string') {
97+
if (allowNull && place[field] === null) {
98+
continue;
99+
}
100+
throw new TemplateParseException(`A string value was expected for the ${field} field`);
101+
}
102+
}
103+
}
104+
105+
function checkBooleanFields(fields, place, required = false) {
106+
if (!Array.isArray(fields)) {
107+
fields = [fields];
108+
}
109+
110+
for (let field of fields) {
111+
if (place[field] === undefined) {
112+
if (required) {
113+
throw new TemplateParseException(`Missing required field: ${field}`);
114+
}
115+
} else if (typeof place[field] !== 'boolean') {
116+
throw new TemplateParseException(`A boolean value was expected for the ${field} field`);
117+
}
118+
}
119+
}
120+
121+
function checkIntegerFields(fields, place, required = false, allowCast = false) {
122+
if (!Array.isArray(fields)) {
123+
fields = [fields];
124+
}
125+
126+
for (let field of fields) {
127+
if (place[field] === undefined) {
128+
if (required) {
129+
throw new TemplateParseException(`Missing required field: ${field}`);
130+
}
131+
132+
} else if (typeof place[field] !== 'number' || !Number.isInteger(place[field])) {
133+
if (allowCast) {
134+
try {
135+
place[field] = parseInt(place[field]);
136+
} catch (e) {
137+
throw new TemplateParseException(`An integer value was expected for the ${field} field`);
138+
}
139+
} else {
140+
throw new TemplateParseException(`An integer value was expected for the ${field} field`);
141+
}
142+
}
143+
}
144+
}
145+
146+
function checkBehaviourViewFields(data) {
147+
checkComponentInfo(data, 'operator');
148+
checkComponentInfo(data, 'widget');
149+
checkArrayFields('connections', data, false);
150+
for (let connection of data['connections']) {
151+
checkStringFields(['sourcename', 'targetname'], connection, true);
152+
checkConnectionHandles(connection);
153+
}
154+
}
155+
156+
function checkContactsFields(fields, place, required = false) {
157+
if (!Array.isArray(fields)) {
158+
fields = [fields];
159+
}
160+
161+
for (let field of fields) {
162+
if (place[field] === undefined) {
163+
if (required) {
164+
throw new TemplateParseException(`Missing required field: ${field}`);
165+
}
166+
167+
place[field] = [];
168+
} else if (typeof place[field] === 'string' || Array.isArray(place[field]) || place[field] instanceof Tuple) {
169+
170+
} else {
171+
throw new TemplateParseException(`${field} field must be a list or string`);
172+
}
173+
}
174+
}
175+
176+
function checkContentsField(data, alternative = true) {
177+
if (typeof data === 'object' && data !== null) {
178+
checkStringFields('src', data, true);
179+
checkStringFields('contenttype', data, false, 'text/html');
180+
checkStringFields('charset', data, false, 'utf-8');
181+
182+
if (alternative === true) {
183+
checkStringFields('scope', data, true);
184+
} else {
185+
checkBooleanFields('cacheable', data, false, true);
186+
checkBooleanFields('useplatformstyle', data, false, false);
187+
}
188+
} else {
189+
throw new TemplateParseException('contents info must be an object');
190+
}
191+
}
192+
193+
function checkHandleField(data, name) {
194+
if (data[name] !== 'auto') {
195+
checkIntegerFields(['x', 'y'], data[name]);
196+
}
197+
}
198+
199+
function checkConnectionHandles(data) {
200+
checkHandleField(data, 'sourcehandle');
201+
checkHandleField(data, 'targethandle');
202+
}
203+
204+
function checkComponentInfo(data, componentType) {
205+
for (let [componentId, component] of Object.entries(data['components'][componentType])) {
206+
checkBooleanFields('collapsed', component, false);
207+
}
208+
}
209+
210+
211+
ConfigParser.prototype.validateJson = function () {
212+
checkStringFields(['title', 'description', 'longdescription', 'email', 'homepage', 'doc', 'changelog', 'image', 'smartphoneimage', 'license', 'licenseurl', 'issuetracker'], this.data);
213+
checkContactsFields(['authors', 'contributors'], this.data);
214+
// TODO ???checkStringFields(['type'], this.data, true)
215+
// Normalize/check preferences and properties (only for widgets and operators)
216+
if (this.data['type'] !== 'mashup') {
217+
checkArrayFields(['preferences', 'properties'], this.data);
218+
for (let preference of this.data['preferences']) {
219+
checkStringFields(['name', 'type'], preference, true);
220+
checkStringFields(['label', 'description', 'default'], reference);
221+
checkBooleanFields(['readonly', 'secure'], preference);
222+
checkStringFields(['value'], preference, undefined, true);
223+
checkBooleanFields('required', preference);
224+
}
225+
226+
for (let prop of this.data['properties']) {
227+
checkStringFields(['name', 'type'], prop, true);
228+
checkStringFields(['label', 'description', 'default'], prop);
229+
checkBooleanFields(['secure'], prop);
230+
checkBooleanFields(['multiuser'], prop);
231+
}
232+
}
233+
234+
if (this.data['type'] === 'widget') {
235+
checkArrayFields(['altcontents'], this.data);
236+
if (this.data['contents'] === null || typeof this.data['contents'] !== 'object') {
237+
throw new TemplateParseException('Missing widget content info');
238+
}
239+
240+
checkContentsField(this.data['contents'], false);
241+
if(this.data['altcontents'] !== undefined){
242+
for (let altcontent of this.data['altcontents']) {
243+
checkContentsField(altcontent);
244+
}
245+
}
246+
} else if (this.data['type'] === 'mashup') {
247+
checkArrayFields(['params', 'tabs', 'embedded'], this.data);
248+
249+
for (let tab of this.data['tabs']) {
250+
checkStringFields(['name'], tab, true);
251+
checkStringFields(['title'], tab, false);
252+
checkArrayFields(['resources'], tab);
253+
for (let widget of tab['resources']) {
254+
let rendering = widget['rendering'] || {};
255+
checkIntegerFields(['layout'], rendering, undefined, true);
256+
checkBooleanFields(['relwidth'], rendering);
257+
checkBooleanFields(['relheight'], rendering);
258+
259+
let position = widget['position'] || {};
260+
checkStringFields(['anchor'], position);
261+
checkBooleanFields(['relx'], position);
262+
checkBooleanFields(['rely'], position);
263+
}
264+
}
265+
266+
for (let preference of this.data['params']) {
267+
checkStringFields(['name', 'type'], preference, true);
268+
checkStringFields(['label', 'description', 'default'], preference);
269+
checkBooleanFields('readonly', preference);
270+
checkStringFields(['value'], preference, undefined, true);
271+
checkBooleanFields('required', preference);
272+
}
273+
274+
for (let component of this.data['embedded']) {
275+
if (typeof component === 'object') {
276+
checkStringFields(['vendor', 'name', 'version', 'src'], component, true);
277+
} else {
278+
throw new TemplateParseException('embedded component info must be an object');
279+
}
280+
}
281+
282+
// if (this.data['wiring'] === undefined) {
283+
// this.data['wiring'] = get_wiring_skeleton();
284+
// }
285+
286+
checkStringFields(['version'], this.data['wiring']);
287+
288+
if (this.data['wiring']['version'] === '1.0') {
289+
// TODO: update to the new wiring format
290+
let inputs = this.data['wiring']['inputs'];
291+
let outputs = this.data['wiring']['outputs'];
292+
//this.data['wiring'] = parse_wiring_old_version(this.data['wiring']);
293+
this.data['wiring']['inputs'] = inputs;
294+
this.data['wiring']['outputs'] = outputs;
295+
// END TODO
296+
} else if (this.data['wiring']['version'] === '2.0') {
297+
if (!('visualdescription' in this.data['wiring'])) {
298+
this.data['wiring']['visualdescription'] = {};
299+
}
300+
301+
checkArrayFields('behaviours', this.data['wiring']['visualdescription'], false);
302+
checkBehaviourViewFields(this.data['wiring']['visualdescription']);
303+
for (let behaviour of this.data['wiring']['visualdescription']['behaviours']) {
304+
checkBehaviourViewFields(behaviour);
305+
}
306+
}
307+
308+
// if (this.data['wiring'] === undefined) {
309+
// this.data['wiring'] = {};
310+
// }
311+
312+
checkArrayFields(['inputs', 'outputs'], this.data['wiring'], false);
313+
314+
// Translations
315+
checkStringFields(['default_lang'], this.data);
316+
317+
// Requirements
318+
checkArrayFields(['requirements'], this.data);
319+
}
320+
}
321+
322+
class TemplateParseException extends Error {
323+
constructor(message) {
324+
super(message);
325+
this.name = "TemplateParseException";
326+
}
327+
}
50328
ConfigParser.prototype.getData = function (configFile) {
51329
return {
52330
name: this.data.root()._attr('name').value(),

0 commit comments

Comments
 (0)