jgexml
Version:
The Just-Good-Enough XML Toolkit
256 lines (232 loc) • 6.69 kB
JavaScript
;
/**
* escapes JSON Pointer using ~0 for ~ and ~1 for /
* @param s the string to escape
* @return the escaped string
*/
function jpescape(s) {
return s.replace(/\~/g, '~0').replace(/\//g, '~1');
}
/**
* unescapes JSON Pointer using ~0 for ~ and ~1 for /
* @param s the string to unescape
* @return the unescaped string
*/
function jpunescape(s) {
return s.replace(/\~1/g, '/').replace(/~0/g, '~');
}
// JSON Pointer specification: http://tools.ietf.org/html/rfc6901
/**
* from obj, return the property with a JSON Pointer prop, optionally setting it
* to newValue
* @param obj the object to point into
* @param prop the JSON Pointer or JSON Reference
* @param newValue optional value to set the property to
* @return the found property, or false
*/
function jptr(obj, prop, newValue) {
if (typeof obj === 'undefined') return false;
if (!prop || typeof prop !== 'string' || (prop === '#')) return (typeof newValue !== 'undefined' ? newValue : obj);
if (prop.indexOf('#')>=0) {
let parts = prop.split('#');
let uri = parts[0];
if (uri) return false; // we do internal resolution only
prop = parts[1];
prop = decodeURIComponent(prop.slice(1).split('+').join(' '));
}
if (prop.startsWith('/')) prop = prop.slice(1);
let components = prop.split('/');
for (let i=0;i<components.length;i++) {
components[i] = jpunescape(components[i]);
let setAndLast = (typeof newValue !== 'undefined') && (i == components.length-1);
let index = parseInt(components[i],10);
if (!Array.isArray(obj) || isNaN(index) || (index.toString() !== components[i])) {
index = (Array.isArray(obj) && components[i] === '-') ? -2 : -1;
}
else {
components[i] = (i > 0) ? components[i-1] : ''; // backtrack to indexed property name
}
if ((index != -1) || obj.hasOwnProperty(components[i])) {
if (index >= 0) {
if (setAndLast) {
obj[index] = newValue;
}
obj = obj[index];
}
else if (index === -2) {
if (setAndLast) {
if (Array.isArray(obj)) {
obj.push(newValue);
}
return newValue;
}
else return undefined;
}
else {
if (setAndLast) {
obj[components[i]] = newValue;
}
obj = obj[components[i]];
}
}
else {
if ((typeof newValue !== 'undefined') && (typeof obj === 'object') &&
(!Array.isArray(obj))) {
obj[components[i]] = (setAndLast ? newValue : ((components[i+1] === '0' || components[i+1] === '-') ? [] : {}));
obj = obj[components[i]];
}
else return false;
}
}
return obj;
}
// simple object accessor using dotted notation and [] for array indices
function fetchFromObject(obj, prop, newValue) {
//property not found
if (typeof obj === 'undefined') return false;
if (!prop) {
if (typeof newValue != 'undefined') {
obj = newValue;
}
return obj;
}
var props = prop.split('.');
var arr = props[0].split(/[\[\]]+/);
var index = -1;
if (arr.length>1) {
index = parseInt(arr[1],10);
}
//property split found; recursive call
if (props.length>1) {
var pos = prop.indexOf('.');
//get object at property (before split), pass on remainder
if (index>=0) {
return fetchFromObject(obj[arr[0]][index], prop.substr(pos+1), newValue); //was props
}
else {
return fetchFromObject(obj[arr[0]], prop.substr(pos+1), newValue);
}
}
//no split; get property[index] or property
var source = obj;
if (arr[0]) source = obj[prop];
if (index>=0) {
if (typeof newValue != 'undefined') source[index] = newValue;
return source[index];
}
else {
if (typeof newValue != 'undefined') obj[prop] = newValue;
return obj[prop];
}
}
function traverse(obj,prefix,depth,parent) {
var result = [];
for (var key in obj) {
// skip loop if the property is from prototype
if (!obj.hasOwnProperty(key)) continue;
var display = key;
var sep = '.';
if (Array.isArray(obj)) {
display = '['+key+']';
sep = '';
}
var item = {};
item.prefix = prefix;
item.key = key;
item.display = display;
item.value = obj[key];
item.depth = depth;
item.parent = parent;
result.push(item);
if (typeof obj[key] === 'object') {
result = result.concat(traverse(obj[key],prefix+sep+display,depth+1,obj));
}
}
return result;
}
function path(item,bracketed) {
if (bracketed) {
var result = '';
var parents = item.prefix.split('.');
for (var p=0;p<parents.length;p++) {
result += "['" + parents[p] + "']";
}
if (item.display.charAt(0) == '[') {
result += item.display;
}
else {
result += '[' + item.display + ']';
}
return result;
}
else {
var sep = '.';
if ((typeof(item.value) === 'object') && (Array.isArray(item.parent)) && (item.prefix != '$')) {
sep = '';
}
if (item.display.charAt(0) == '[') {
sep = '';
}
return item.prefix+sep+item.display;
}
}
function selectRegex(tree,expr,bracketed) {
// not currently working, we are going to need some serious escaping of the regex
if (!expr) {
expr = /[*]/;
}
var result = [];
for (var i=0;i<tree.length;i++) {
var p = path(tree[i],bracketed);
if (p.match(expr)) {
result.push(tree[i]);
}
}
return result;
}
function select(tree,target,bracketed) {
var result = [];
var returnParent = false;
var checkEnd = false;
// ^
if (target.endsWith('^')) { // unoffical JSONPath extension
target = target.substring(0,target.length-1);
returnParent = true;
}
// .*
if (target.endsWith('.*') && (target != '$..*')) {
target = target.substring(0,target.length-2);
}
// [*]
target = target.split('[*]').join('[]');
// ..
if ((target.indexOf('..') > 0) && (target != '$..*')) {
var x = target.split('..');
target = x[x.length-1];
target = target.split('$').join('');
checkEnd = true;
}
for (var i=0;i<tree.length;i++) {
var p = path(tree[i],bracketed);
if ((target == '*') || (target == '$..*') || (p == target) || ((p.endsWith(target) && checkEnd))) {
if (returnParent) {
result.push(tree[i].parent);
}
else {
result.push(tree[i]);
}
}
}
return result;
}
module.exports = {
build : function(obj) {
return traverse(obj,'$',0,{});
},
select : select,
selectRegex : selectRegex,
path : path,
fetchFromObject : fetchFromObject,
jptr : jptr,
jpescape : jpescape
};