@@ -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
2431function 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
3743ConfigParser . 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+ }
50328ConfigParser . prototype . getData = function ( configFile ) {
51329 return {
52330 name : this . data . root ( ) . _attr ( 'name' ) . value ( ) ,
0 commit comments