/*1cycle.js22013-02-1934Public Domain.56NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.78This code should be minified before deployment.9See http://javascript.crockford.com/jsmin.html1011USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO12NOT CONTROL.13*/1415/*jslint evil: true, regexp: true */1617/*members $ref, apply, call, decycle, hasOwnProperty, length, prototype, push,18retrocycle, stringify, test, toString19*/2021var cycle = exports;2223cycle.decycle = function decycle(object) {24'use strict';2526// Make a deep copy of an object or array, assuring that there is at most27// one instance of each object or array in the resulting structure. The28// duplicate references (which might be forming cycles) are replaced with29// an object of the form30// {$ref: PATH}31// where the PATH is a JSONPath string that locates the first occurance.32// So,33// var a = [];34// a[0] = a;35// return JSON.stringify(JSON.decycle(a));36// produces the string '[{"$ref":"$"}]'.3738// JSONPath is used to locate the unique object. $ indicates the top level of39// the object or array. [NUMBER] or [STRING] indicates a child member or40// property.4142var objects = [], // Keep a reference to each unique object or array43paths = []; // Keep the path to each unique object or array4445return (function derez(value, path) {4647// The derez recurses through the object, producing the deep copy.4849var i, // The loop counter50name, // Property name51nu; // The new object or array5253// typeof null === 'object', so go on if this value is really an object but not54// one of the weird builtin objects.5556if (typeof value === 'object' && value !== null &&57!(value instanceof Boolean) &&58!(value instanceof Date) &&59!(value instanceof Number) &&60!(value instanceof RegExp) &&61!(value instanceof String)) {6263// If the value is an object or array, look to see if we have already64// encountered it. If so, return a $ref/path object. This is a hard way,65// linear search that will get slower as the number of unique objects grows.6667for (i = 0; i < objects.length; i += 1) {68if (objects[i] === value) {69return {$ref: paths[i]};70}71}7273// Otherwise, accumulate the unique value and its path.7475objects.push(value);76paths.push(path);7778// If it is an array, replicate the array.7980if (Object.prototype.toString.apply(value) === '[object Array]') {81nu = [];82for (i = 0; i < value.length; i += 1) {83nu[i] = derez(value[i], path + '[' + i + ']');84}85} else {8687// If it is an object, replicate the object.8889nu = {};90for (name in value) {91if (Object.prototype.hasOwnProperty.call(value, name)) {92nu[name] = derez(value[name],93path + '[' + JSON.stringify(name) + ']');94}95}96}97return nu;98}99return value;100}(object, '$'));101};102103104cycle.retrocycle = function retrocycle($) {105'use strict';106107// Restore an object that was reduced by decycle. Members whose values are108// objects of the form109// {$ref: PATH}110// are replaced with references to the value found by the PATH. This will111// restore cycles. The object will be mutated.112113// The eval function is used to locate the values described by a PATH. The114// root object is kept in a $ variable. A regular expression is used to115// assure that the PATH is extremely well formed. The regexp contains nested116// * quantifiers. That has been known to have extremely bad performance117// problems on some browsers for very long strings. A PATH is expected to be118// reasonably short. A PATH is allowed to belong to a very restricted subset of119// Goessner's JSONPath.120121// So,122// var s = '[{"$ref":"$"}]';123// return JSON.retrocycle(JSON.parse(s));124// produces an array containing a single element which is the array itself.125126var px =127/^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/;128129(function rez(value) {130131// The rez function walks recursively through the object looking for $ref132// properties. When it finds one that has a value that is a path, then it133// replaces the $ref object with a reference to the value that is found by134// the path.135136var i, item, name, path;137138if (value && typeof value === 'object') {139if (Object.prototype.toString.apply(value) === '[object Array]') {140for (i = 0; i < value.length; i += 1) {141item = value[i];142if (item && typeof item === 'object') {143path = item.$ref;144if (typeof path === 'string' && px.test(path)) {145value[i] = eval(path);146} else {147rez(item);148}149}150}151} else {152for (name in value) {153if (typeof value[name] === 'object') {154item = value[name];155if (item) {156path = item.$ref;157if (typeof path === 'string' && px.test(path)) {158value[name] = eval(path);159} else {160rez(item);161}162}163}164}165}166}167}($));168return $;169};170171172