3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
21 import _cloneDeep
from 'lodash/cloneDeep';
22 import _isArray
from 'lodash/isArray';
23 import _map
from 'lodash/map';
24 import _flatten
from 'lodash/flatten';
25 import _find
from 'lodash/find';
26 import _toNumber
from 'lodash/toNumber';
27 import _isNumber
from 'lodash/isNumber';
28 import _clone
from 'lodash/clone';
31 addAuthorizationStub(xhr
) {
32 xhr
.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
34 getSearchParams(url
) {
35 var a
= document
.createElement('a');
38 var items
= a
.search
.replace('?', '').split('&');
39 for (var i
= 0; i
< items
.length
; i
++) {
40 if (items
[i
].length
> 0) {
41 var key_value
= items
[i
].split('=');
42 params
[key_value
[0]] = decodeURIComponent(key_value
[1]);
47 parseJSONIgnoreErrors(txt
) {
49 return JSON
.parse(txt
);
54 resolvePath(obj
, path
) {
55 // supports a.b, a[1] and foo[bar], etc.
56 // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
57 // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
58 path
= path
.split(/[\.\[\]]/).filter(d
=> d
);
59 return path
.reduce((r
, p
) => {
65 assignPathValue(obj
, path
, value
) {
66 path
= path
.split(/[\.\[\]]/).filter(d
=> d
);
67 // enable look-ahead to determine if type is array or object
68 const pathCopy
= path
.slice();
69 // last item in path used to assign value on the resolved object
70 const name
= path
.pop();
71 const resolvedObj
= path
.reduce((r
, p
, i
) => {
72 if (typeof (r
[p
]) !== 'object') {
73 // look-ahead to see if next path item is a number
74 const isArray
= !isNaN(parseInt(pathCopy
[i
+ 1], 10));
75 r
[p
] = isArray
? [] : {}
79 resolvedObj
[name
] = value
;
81 mergePathData(obj
, path
, data
) {
82 path
= path
.split(/[\.\[\]]/).filter(d
=> d
);
83 // enable look-ahead to determine if type is array or object
84 const pathCopy
= path
.slice();
85 let resolvedObj
= obj
;
87 // last item in path used to assign value on the resolved object
88 const name
= path
.pop();
89 resolvedObj
= path
.reduce((r
, p
, i
) => {
90 if (typeof (r
[p
]) !== 'object') {
91 // look-ahead to see if next path item is a number
92 const isArray
= !isNaN(parseInt(pathCopy
[i
+ 1], 10));
93 r
[p
] = isArray
? [] : {}
98 Object
.assign(resolvedObj
, data
);
100 updatePathValue(obj
, path
, value
, isCase
) {
101 // todo: replace implementation of assignPathValue with this impl and
102 // remove updatePathValue (only need one function, not both)
103 // same as assignPathValue except removes property if value is undefined
104 path
= path
.split(/[\.\[\]]/).filter(d
=> d
);
105 const isRemove
= typeof value
=== 'undefined';
106 // enable look-ahead to determine if type is array or object
107 const pathCopy
= path
.slice();
108 // last item in path used to assign value on the resolved object
109 const name
= path
.pop();
110 const resolvedObj
= path
.reduce((r
, p
, i
) => {
111 // look-ahead to see if next path item is a number
112 const index
= parseInt(pathCopy
[i
+ 1], 10);
113 const isArray
= !isNaN(index
);
114 if (typeof (r
[p
]) !== 'object') {
115 r
[p
] = isArray
? [] : {}
117 if (isRemove
&& ((i
+ 1) === path
.length
)) {
119 r
[p
] = r
[p
].filter((d
, i
) => i
!== index
);
136 resolvedObj
[name
] = value
;
139 removePathValue(obj
, path
, isCase
) {
140 // note updatePathValue removes value if third argument is undefined
141 return this.updatePathValue(obj
, path
, undefined, isCase
);
144 suffixAsInteger
: (field
) => {
146 const str
= String(obj
[field
]);
147 const value
= str
.replace(str
.replace(/[\d]+$/, ''), '');
148 return 1 + parseInt(value
, 10) || 0;
152 toBiggestValue
: (maxIndex
, curIndex
) => Math
.max(maxIndex
, curIndex
),
154 isRelativePath(path
) {
155 if (path
.split('/')[0] == '..') {
161 getResults(topLevelObject
, pathArray
) {
162 let objectCopy
= _cloneDeep(topLevelObject
);
163 let i
= pathArray
.length
;
166 while (pathArray
[pathArray
.length
- i
]) {
167 if (_isArray(objectCopy
[pathArray
[pathArray
.length
- i
]])) {
169 results
= _map(objectCopy
[pathArray
[pathArray
.length
- i
]], pathArray
[pathArray
.length
- 1]);
171 objectCopy
= objectCopy
[pathArray
[pathArray
.length
- i
]];
173 } else if (_isArray(objectCopy
)) {
174 objectCopy
.map((object
) => {
175 if (_isArray(object
[pathArray
[pathArray
.length
- i
]])) {
177 results
= results
.concat(_map(object
[pathArray
[pathArray
.length
- i
]], pathArray
[pathArray
.length
- 1]));
188 getAbsoluteResults(topLevelObject
, pathArray
) {
189 let i
= pathArray
.length
;
190 let objectCopy
= _cloneDeep(topLevelObject
);
193 let fragment
= pathArray
[pathArray
.length
- i
]
198 if (_isArray(objectCopy
)) {
199 // results will be obtained from a map
200 results
= _map(objectCopy
, fragment
);
203 if (fragment
.match(/\[.*\]/g)) {
204 // contains a predicate
205 // shouldn't reach here
206 console
.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
208 // contains no predicate
212 results
.push(objectCopy
[fragment
]);
216 if (_isArray(objectCopy
)) {
218 objectCopy
= _map(objectCopy
, fragment
);
220 // If any of the deeper object is an array, flatten the entire list.
221 // This would usually be a bad leafref going out of its scope.
223 for (let i
= 0; i
< objectCopy
.length
; i
++) {
224 if (_isArray(objectCopy
[i
])) {
225 objectCopy
= _flatten(objectCopy
);
226 console
.log('This might be a bad leafref. Verify with backend team.')
232 if (fragment
.match(/\[.*\]/g)) {
233 // contains a predicate
234 let predicateStr
= fragment
.match(/\[.*\]/g)[0];
235 // Clip leading [ and trailing ]
236 predicateStr
= predicateStr
.substr(1, predicateStr
.length
- 2);
237 const predicateKeyValue
= predicateStr
.split('=');
238 const predicateKey
= predicateKeyValue
[0];
239 const predicateValue
= predicateKeyValue
[1];
240 // get key for object to search into
241 let key
= fragment
.split('[')[0];
242 let searchObject
= {};
243 searchObject
[predicateKey
] = predicateValue
;
244 let found
= _find(objectCopy
[key
], searchObject
);
248 // check for numerical value
249 if (predicateValue
!= "" &&
250 predicateValue
!= null &&
251 predicateValue
!= NaN
&&
252 predicateValue
!= Infinity
&&
253 predicateValue
!= -Infinity
) {
254 let numericalPredicateValue
= _toNumber(predicateValue
);
255 if (_isNumber(numericalPredicateValue
)) {
256 searchObject
[predicateKey
] = numericalPredicateValue
;
257 found
= _find(objectCopy
[key
], searchObject
);
267 // contains no predicate
271 objectCopy
= objectCopy
[fragment
];
280 fragment
= pathArray
[pathArray
.length
- i
];
286 resolveCurrentPredicate(leafRefPath
, container
, pathCopy
) {
287 if (leafRefPath
.indexOf('current()') != -1) {
290 // Get the relative path from current
291 let relativePath
= leafRefPath
.match("current\\(\\)\/(.*)\]");
292 let relativePathArray
= relativePath
[1].split('/');
294 while (relativePathArray
[0] == '..') {
296 relativePathArray
.shift();
299 // Supports only one relative path up
300 // To support deeper paths, will need to massage the string more
301 // i.e. change '/'' to '.'
302 const searchPath
= pathCopy
.join('.').concat('.', relativePathArray
[0]);
303 const searchValue
= this.resolvePath(container
.model
, searchPath
);
305 const predicateStr
= leafRefPath
.match("(current.*)\]")[1];
306 leafRefPath
= leafRefPath
.replace(predicateStr
, searchValue
);
311 cleanupFieldKeyArray (fieldKeyArray
) {
312 fieldKeyArray
.map((fieldKey
, fieldKeyIndex
) => {
313 fieldKeyArray
[fieldKeyIndex
] = fieldKey
.replace(/.*\/(.*)/, '$1');
315 return fieldKeyArray
;
318 resolveLeafRefPath(catalogs
, leafRefPath
, fieldKey
, path
, container
) {
319 let pathCopy
= _clone(path
);
320 // Strip any prefixes
321 let leafRefPathCopy
= leafRefPath
.replace(/[-\w]*:/g, '');
323 leafRefPathCopy
= leafRefPathCopy
.replace(/\s/g, '');
325 // resolve any current paths
326 leafRefPathCopy
= this.resolveCurrentPredicate(leafRefPathCopy
, container
, pathCopy
);
328 // Split on delimiter (/)
329 const pathArray
= leafRefPathCopy
.split('/');
331 let fieldKeyArray
= fieldKey
.split(':');
333 // strip prepending qualifiers from fieldKeys
334 fieldKeyArray
= this.cleanupFieldKeyArray(fieldKeyArray
);
337 // Check if relative path or not
338 // TODO: Below works but
339 // better to convert the pathCopy to absolute/rooted path
340 // and use the absolute module instead
341 if (this.isRelativePath(leafRefPathCopy
)) {
342 let i
= pathArray
.length
;
343 while (pathArray
[pathArray
.length
- i
] == '..') {
344 fieldKeyArray
.splice(-1, 1);
345 if (!isNaN(Number(fieldKeyArray
[fieldKeyArray
.length
- 1]))) {
346 // found a number, so an index. strip it
347 fieldKeyArray
.splice(-1, 1);
352 // traversed all .. - now traverse down
353 if (fieldKeyArray
.length
== 1) {
354 for (let key
in catalogs
) {
355 for (let subKey
in catalogs
[key
]) {
356 let found
= _find(catalogs
[key
][subKey
], {
360 results
= this.getAbsoluteResults(found
, pathArray
.splice(-i
, i
));
365 } else if (fieldKeyArray
.length
== 2) {
366 for (let key
in catalogs
) {
367 for (let subKey
in catalogs
[key
]) {
368 console
.log(key
, subKey
);
369 var found
= _find(catalogs
[key
][subKey
], {
373 for (let foundKey
in found
) {
374 if (_isArray(found
[foundKey
])) {
375 let topLevel
= _find(found
[foundKey
], {
379 results
= this.getAbsoluteResults(topLevel
, pathArray
.splice(-i
, i
));
383 if (foundKey
== fieldKeyArray
[1]) {
384 results
= this.getAbsoluteResults(found
[foundKey
], pathArray
.splice(-i
, i
));
392 } else if (fieldKeyArray
.length
== 3) {
393 for (let key
in catalogs
) {
394 for (let subKey
in catalogs
[key
]) {
395 let found
= _find(catalogs
[key
][subKey
], {
399 for (let foundKey
in found
) {
400 if (_isArray(found
[foundKey
])) {
401 let topLevel
= _find(found
[foundKey
], {
405 results
= this.getAbsoluteResults(topLevel
, pathArray
.splice(-i
, i
));
409 if (foundKey
== fieldKeyArray
[1]) {
410 results
= this.getAbsoluteResults(found
[foundKey
], pathArray
.splice(-i
, i
));
419 // not supported - too many levels deep ... maybe some day
420 console
.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
424 if (pathArray
[0] == "") {
425 pathArray
.splice(0, 1);
428 let catalogKey
= pathArray
[0];
429 let topLevelObject
= {};
431 topLevelObject
[catalogKey
] = catalogs
[catalogKey
];
433 results
= this.getAbsoluteResults(topLevelObject
, pathArray
);