/*! * jsoneditor.js * * @brief * JSONEditor is a web-based tool to view, edit, format, and validate JSON. * It has various modes such as a tree editor, a code editor, and a plain text * editor. * * Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+ * * @license * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * Copyright (c) 2011-2019 Jos de Jong, http://jsoneditoronline.org * * @author Jos de Jong, * @version 6.2.1 * @date 2019-08-01 */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["JSONEditor"] = factory(); else root["JSONEditor"] = factory(); })(window, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 16); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; __webpack_require__(10); var naturalSort = __webpack_require__(11); var jsonlint = __webpack_require__(23); var jsonMap = __webpack_require__(24); var translate = __webpack_require__(1).translate; var MAX_ITEMS_FIELDS_COLLECTION = 10000; /** * Parse JSON using the parser built-in in the browser. * On exception, the jsonString is validated and a detailed error is thrown. * @param {String} jsonString * @return {JSON} json */ exports.parse = function parse(jsonString) { try { return JSON.parse(jsonString); } catch (err) { // try to throw a more detailed error message using validate exports.validate(jsonString); // rethrow the original error throw err; } }; /** * Repair a JSON-like string containing. For example changes JavaScript * notation into JSON notation. * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" * into '{"a": 2, "b": {"c": "d"}' * @param {string} jsString * @returns {string} json */ exports.repair = function (jsString) { // TODO: refactor this function, it's too large and complicated now // escape all single and double quotes inside strings var chars = []; var i = 0; //If JSON starts with a function (characters/digits/"_-"), remove this function. //This is useful for "stripping" JSONP objects to become JSON //For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}] var match = jsString.match(/^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/); if (match) { jsString = match[3]; } var controlChars = { '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t' }; var quote = '\''; var quoteDbl = '"'; var quoteLeft = '\u2018'; var quoteRight = '\u2019'; var quoteDblLeft = '\u201C'; var quoteDblRight = '\u201D'; var graveAccent = '\u0060'; var acuteAccent = '\u00B4'; // helper functions to get the current/prev/next character function curr () { return jsString.charAt(i); } function next() { return jsString.charAt(i + 1); } function prev() { return jsString.charAt(i - 1); } function isWhiteSpace(c) { return c === ' ' || c === '\n' || c === '\r' || c === '\t'; } // get the last parsed non-whitespace character function lastNonWhitespace () { var p = chars.length - 1; while (p >= 0) { var pp = chars[p]; if (!isWhiteSpace(pp)) { return pp; } p--; } return ''; } // get at the first next non-white space character function nextNonWhiteSpace() { var iNext = i + 1; while (iNext < jsString.length && isWhiteSpace(jsString[iNext])) { iNext++; } return jsString[iNext]; } // skip a block comment '/* ... */' function skipBlockComment () { i += 2; while (i < jsString.length && (curr() !== '*' || next() !== '/')) { i++; } i += 2; } // skip a comment '// ...' function skipComment () { i += 2; while (i < jsString.length && (curr() !== '\n')) { i++; } } /** * parse single or double quoted string. Returns the parsed string * @param {string} endQuote * @return {string} */ function parseString(endQuote) { var string = ''; string += '"'; i++; var c = curr(); while (i < jsString.length && c !== endQuote) { if (c === '"' && prev() !== '\\') { // unescaped double quote, escape it string += '\\"'; } else if (controlChars.hasOwnProperty(c)) { // replace unescaped control characters with escaped ones string += controlChars[c] } else if (c === '\\') { // remove the escape character when followed by a single quote ', not needed i++; c = curr(); if (c !== '\'') { string += '\\'; } string += c; } else { // regular character string += c; } i++; c = curr(); } if (c === endQuote) { string += '"'; i++; } return string; } // parse an unquoted key function parseKey() { var specialValues = ['null', 'true', 'false']; var key = ''; var c = curr(); var regexp = /[a-zA-Z_$\d]/; // letter, number, underscore, dollar character while (regexp.test(c)) { key += c; i++; c = curr(); } if (specialValues.indexOf(key) === -1) { return '"' + key + '"'; } else { return key; } } function parseMongoDataType () { var c = curr(); var value; var dataType = ''; while (/[a-zA-Z_$]/.test(c)) { dataType += c i++; c = curr(); } if (dataType.length > 0 && c === '(') { // This is an MongoDB data type like {"_id": ObjectId("123")} i++; c = curr(); if (c === '"') { // a data type containing a string, like ISODate("2012-12-19T06:01:17.171Z") value = parseString(c); c = curr(); } else { // a data type containing a value, like 'NumberLong(2)' value = ''; while(c !== ')' && c !== '') { value += c; i++; c = curr(); } } if (c === ')') { // skip the closing bracket at the end i++; // return the value (strip the data type object) return value; } else { // huh? that's unexpected. don't touch it return dataType + '(' + value + c; } } else { // hm, no Mongo data type after all return dataType; } } function isSpecialWhiteSpace (c) { return ( c === '\u00A0' || (c >= '\u2000' && c <= '\u200A') || c === '\u202F' || c === '\u205F' || c === '\u3000') } while(i < jsString.length) { var c = curr(); if (c === '/' && next() === '*') { skipBlockComment(); } else if (c === '/' && next() === '/') { skipComment(); } else if (isSpecialWhiteSpace(c)) { // special white spaces (like non breaking space) chars.push(' '); i++ } else if (c === quote) { chars.push(parseString(c)); } else if (c === quoteDbl) { chars.push(parseString(quoteDbl)); } else if (c === graveAccent) { chars.push(parseString(acuteAccent)); } else if (c === quoteLeft) { chars.push(parseString(quoteRight)); } else if (c === quoteDblLeft) { chars.push(parseString(quoteDblRight)); } else if (c === ',' && [']', '}'].indexOf(nextNonWhiteSpace()) !== -1) { // skip trailing commas i++; } else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) { // an unquoted object key (like a in '{a:2}') chars.push(parseKey()); } else { if (/[a-zA-Z_$]/.test(c)) { chars.push(parseMongoDataType()); } else { chars.push(c); i++; } } } return chars.join(''); }; /** * Escape unicode characters. * For example input '\u2661' (length 1) will output '\\u2661' (length 5). * @param {string} text * @return {string} */ exports.escapeUnicodeChars = function (text) { // see https://www.wikiwand.com/en/UTF-16 // note: we leave surrogate pairs as two individual chars, // as JSON doesn't interpret them as a single unicode char. return text.replace(/[\u007F-\uFFFF]/g, function(c) { return '\\u'+('0000' + c.charCodeAt(0).toString(16)).slice(-4); }) }; /** * Validate a string containing a JSON object * This method uses JSONLint to validate the String. If JSONLint is not * available, the built-in JSON parser of the browser is used. * @param {String} jsonString String with an (invalid) JSON object * @throws Error */ exports.validate = function validate(jsonString) { if (typeof(jsonlint) != 'undefined') { jsonlint.parse(jsonString); } else { JSON.parse(jsonString); } }; /** * Extend object a with the properties of object b * @param {Object} a * @param {Object} b * @return {Object} a */ exports.extend = function extend(a, b) { for (var prop in b) { if (b.hasOwnProperty(prop)) { a[prop] = b[prop]; } } return a; }; /** * Remove all properties from object a * @param {Object} a * @return {Object} a */ exports.clear = function clear (a) { for (var prop in a) { if (a.hasOwnProperty(prop)) { delete a[prop]; } } return a; }; /** * Get the type of an object * @param {*} object * @return {String} type */ exports.type = function type (object) { if (object === null) { return 'null'; } if (object === undefined) { return 'undefined'; } if ((object instanceof Number) || (typeof object === 'number')) { return 'number'; } if ((object instanceof String) || (typeof object === 'string')) { return 'string'; } if ((object instanceof Boolean) || (typeof object === 'boolean')) { return 'boolean'; } if ((object instanceof RegExp) || (typeof object === 'regexp')) { return 'regexp'; } if (exports.isArray(object)) { return 'array'; } return 'object'; }; /** * Test whether a text contains a url (matches when a string starts * with 'http://*' or 'https://*' and has no whitespace characters) * @param {String} text */ var isUrlRegex = /^https?:\/\/\S+$/; exports.isUrl = function isUrl (text) { return (typeof text == 'string' || text instanceof String) && isUrlRegex.test(text); }; /** * Tes whether given object is an Array * @param {*} obj * @returns {boolean} returns true when obj is an array */ exports.isArray = function (obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; /** * Retrieve the absolute left value of a DOM element * @param {Element} elem A dom element, for example a div * @return {Number} left The absolute left position of this element * in the browser page. */ exports.getAbsoluteLeft = function getAbsoluteLeft(elem) { var rect = elem.getBoundingClientRect(); return rect.left + window.pageXOffset || document.scrollLeft || 0; }; /** * Retrieve the absolute top value of a DOM element * @param {Element} elem A dom element, for example a div * @return {Number} top The absolute top position of this element * in the browser page. */ exports.getAbsoluteTop = function getAbsoluteTop(elem) { var rect = elem.getBoundingClientRect(); return rect.top + window.pageYOffset || document.scrollTop || 0; }; /** * add a className to the given elements style * @param {Element} elem * @param {String} className */ exports.addClassName = function addClassName(elem, className) { var classes = elem.className.split(' '); if (classes.indexOf(className) == -1) { classes.push(className); // add the class to the array elem.className = classes.join(' '); } }; /** * remove all classes from the given elements style * @param {Element} elem */ exports.removeAllClassNames = function removeAllClassNames(elem) { elem.className = ""; }; /** * add a className to the given elements style * @param {Element} elem * @param {String} className */ exports.removeClassName = function removeClassName(elem, className) { var classes = elem.className.split(' '); var index = classes.indexOf(className); if (index != -1) { classes.splice(index, 1); // remove the class from the array elem.className = classes.join(' '); } }; /** * Strip the formatting from the contents of a div * the formatting from the div itself is not stripped, only from its childs. * @param {Element} divElement */ exports.stripFormatting = function stripFormatting(divElement) { var childs = divElement.childNodes; for (var i = 0, iMax = childs.length; i < iMax; i++) { var child = childs[i]; // remove the style if (child.style) { // TODO: test if child.attributes does contain style child.removeAttribute('style'); } // remove all attributes var attributes = child.attributes; if (attributes) { for (var j = attributes.length - 1; j >= 0; j--) { var attribute = attributes[j]; if (attribute.specified === true) { child.removeAttribute(attribute.name); } } } // recursively strip childs exports.stripFormatting(child); } }; /** * Set focus to the end of an editable div * code from Nico Burns * http://stackoverflow.com/users/140293/nico-burns * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity * @param {Element} contentEditableElement A content editable div */ exports.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { var range, selection; if(document.createRange) { range = document.createRange();//Create a range (a range is a like the selection but invisible) range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start selection = window.getSelection();//get the selection object (allows you to change selection) selection.removeAllRanges();//remove any selections already made selection.addRange(range);//make the range you have just created the visible selection } }; /** * Select all text of a content editable div. * http://stackoverflow.com/a/3806004/1262753 * @param {Element} contentEditableElement A content editable div */ exports.selectContentEditable = function selectContentEditable(contentEditableElement) { if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { return; } var sel, range; if (window.getSelection && document.createRange) { range = document.createRange(); range.selectNodeContents(contentEditableElement); sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } }; /** * Get text selection * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore * @return {Range | TextRange | null} range */ exports.getSelection = function getSelection() { if (window.getSelection) { var sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { return sel.getRangeAt(0); } } return null; }; /** * Set text selection * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore * @param {Range | TextRange | null} range */ exports.setSelection = function setSelection(range) { if (range) { if (window.getSelection) { var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } } }; /** * Get selected text range * @return {Object} params object containing parameters: * {Number} startOffset * {Number} endOffset * {Element} container HTML element holding the * selected text element * Returns null if no text selection is found */ exports.getSelectionOffset = function getSelectionOffset() { var range = exports.getSelection(); if (range && 'startOffset' in range && 'endOffset' in range && range.startContainer && (range.startContainer == range.endContainer)) { return { startOffset: range.startOffset, endOffset: range.endOffset, container: range.startContainer.parentNode }; } return null; }; /** * Set selected text range in given element * @param {Object} params An object containing: * {Element} container * {Number} startOffset * {Number} endOffset */ exports.setSelectionOffset = function setSelectionOffset(params) { if (document.createRange && window.getSelection) { var selection = window.getSelection(); if(selection) { var range = document.createRange(); if (!params.container.firstChild) { params.container.appendChild(document.createTextNode('')); } // TODO: do not suppose that the first child of the container is a textnode, // but recursively find the textnodes range.setStart(params.container.firstChild, params.startOffset); range.setEnd(params.container.firstChild, params.endOffset); exports.setSelection(range); } } }; /** * Get the inner text of an HTML element (for example a div element) * @param {Element} element * @param {Object} [buffer] * @return {String} innerText */ exports.getInnerText = function getInnerText(element, buffer) { var first = (buffer == undefined); if (first) { buffer = { 'text': '', 'flush': function () { var text = this.text; this.text = ''; return text; }, 'set': function (text) { this.text = text; } }; } // text node if (element.nodeValue) { return buffer.flush() + element.nodeValue; } // divs or other HTML elements if (element.hasChildNodes()) { var childNodes = element.childNodes; var innerText = ''; for (var i = 0, iMax = childNodes.length; i < iMax; i++) { var child = childNodes[i]; if (child.nodeName == 'DIV' || child.nodeName == 'P') { var prevChild = childNodes[i - 1]; var prevName = prevChild ? prevChild.nodeName : undefined; if (prevName && prevName != 'DIV' && prevName != 'P' && prevName != 'BR') { innerText += '\n'; buffer.flush(); } innerText += exports.getInnerText(child, buffer); buffer.set('\n'); } else if (child.nodeName == 'BR') { innerText += buffer.flush(); buffer.set('\n'); } else { innerText += exports.getInnerText(child, buffer); } } return innerText; } else { if (element.nodeName == 'P' && exports.getInternetExplorerVersion() != -1) { // On Internet Explorer, a

with hasChildNodes()==false is // rendered with a new line. Note that a

with // hasChildNodes()==true is rendered without a new line // Other browsers always ensure there is a
inside the

, // and if not, the

does not render a new line return buffer.flush(); } } // br or unknown return ''; }; /** * Test whether an element has the provided parent node somewhere up the node tree. * @param {Element} elem * @param {Element} parent * @return {boolean} */ exports.hasParentNode = function (elem, parent) { var e = elem ? elem.parentNode : undefined; while (e) { if (e === parent) { return true; } e = e.parentNode; } return false; } /** * Returns the version of Internet Explorer or a -1 * (indicating the use of another browser). * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx * @return {Number} Internet Explorer version, or -1 in case of an other browser */ exports.getInternetExplorerVersion = function getInternetExplorerVersion() { if (_ieVersion == -1) { var rv = -1; // Return value assumes failure. if (typeof navigator !== 'undefined' && navigator.appName == 'Microsoft Internet Explorer') { var ua = navigator.userAgent; var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); if (re.exec(ua) != null) { rv = parseFloat( RegExp.$1 ); } } _ieVersion = rv; } return _ieVersion; }; /** * Test whether the current browser is Firefox * @returns {boolean} isFirefox */ exports.isFirefox = function isFirefox () { return (typeof navigator !== 'undefined' && navigator.userAgent.indexOf("Firefox") !== -1); }; /** * cached internet explorer version * @type {Number} * @private */ var _ieVersion = -1; /** * Add and event listener. Works for all browsers * @param {Element} element An html element * @param {string} action The action, for example "click", * without the prefix "on" * @param {function} listener The callback function to be executed * @param {boolean} [useCapture] false by default * @return {function} the created event listener */ exports.addEventListener = function addEventListener(element, action, listener, useCapture) { if (element.addEventListener) { if (useCapture === undefined) useCapture = false; if (action === "mousewheel" && exports.isFirefox()) { action = "DOMMouseScroll"; // For Firefox } element.addEventListener(action, listener, useCapture); return listener; } else if (element.attachEvent) { // Old IE browsers var f = function () { return listener.call(element, window.event); }; element.attachEvent("on" + action, f); return f; } }; /** * Remove an event listener from an element * @param {Element} element An html dom element * @param {string} action The name of the event, for example "mousedown" * @param {function} listener The listener function * @param {boolean} [useCapture] false by default */ exports.removeEventListener = function removeEventListener(element, action, listener, useCapture) { if (element.removeEventListener) { if (useCapture === undefined) useCapture = false; if (action === "mousewheel" && exports.isFirefox()) { action = "DOMMouseScroll"; // For Firefox } element.removeEventListener(action, listener, useCapture); } else if (element.detachEvent) { // Old IE browsers element.detachEvent("on" + action, listener); } }; /** * Test if an element is a child of a parent element. * @param {Element} elem * @param {Element} parent * @return {boolean} returns true if elem is a child of the parent */ exports.isChildOf = function (elem, parent) { var e = elem.parentNode; while (e) { if (e === parent) { return true; } e = e.parentNode; } return false; }; /** * Parse a JSON path like '.items[3].name' into an array * @param {string} jsonPath * @return {Array} */ exports.parsePath = function parsePath(jsonPath) { var path = []; var i = 0; function parseProperty () { var prop = '' while (jsonPath[i] !== undefined && /[\w$]/.test(jsonPath[i])) { prop += jsonPath[i]; i++; } if (prop === '') { throw new Error('Invalid JSON path: property name expected at index ' + i); } return prop; } function parseIndex (end) { var name = '' while (jsonPath[i] !== undefined && jsonPath[i] !== end) { name += jsonPath[i]; i++; } if (jsonPath[i] !== end) { throw new Error('Invalid JSON path: unexpected end, character ' + end + ' expected') } return name; } while (jsonPath[i] !== undefined) { if (jsonPath[i] === '.') { i++; path.push(parseProperty()); } else if (jsonPath[i] === '[') { i++; if (jsonPath[i] === '\'' || jsonPath[i] === '"') { var end = jsonPath[i] i++; path.push(parseIndex(end)); if (jsonPath[i] !== end) { throw new Error('Invalid JSON path: closing quote \' expected at index ' + i) } i++; } else { var index = parseIndex(']').trim() if (index.length === 0) { throw new Error('Invalid JSON path: array value expected at index ' + i) } // Coerce numeric indices to numbers, but ignore star index = index === '*' ? index : JSON.parse(index); path.push(index); } if (jsonPath[i] !== ']') { throw new Error('Invalid JSON path: closing bracket ] expected at index ' + i) } i++; } else { throw new Error('Invalid JSON path: unexpected character "' + jsonPath[i] + '" at index ' + i); } } return path; }; /** * Stringify an array with a path in a JSON path like '.items[3].name' * @param {Array.} path * @returns {string} */ exports.stringifyPath = function stringifyPath(path) { return path .map(function (p) { if (typeof p === 'number'){ return ('[' + p + ']'); } else if(typeof p === 'string' && p.match(/^[A-Za-z0-9_$]+$/)) { return '.' + p; } else { return '["' + p + '"]'; } }) .join(''); }; /** * Improve the error message of a JSON schema error * @param {Object} error * @return {Object} The error */ exports.improveSchemaError = function (error) { if (error.keyword === 'enum' && Array.isArray(error.schema)) { var enums = error.schema; if (enums) { enums = enums.map(function (value) { return JSON.stringify(value); }); if (enums.length > 5) { var more = ['(' + (enums.length - 5) + ' more...)']; enums = enums.slice(0, 5); enums.push(more); } error.message = 'should be equal to one of: ' + enums.join(', '); } } if (error.keyword === 'additionalProperties') { error.message = 'should NOT have additional property: ' + error.params.additionalProperty; } return error; }; /** * Test whether something is a Promise * @param {*} object * @returns {boolean} Returns true when object is a promise, false otherwise */ exports.isPromise = function (object) { return object && typeof object.then === 'function' && typeof object.catch === 'function'; }; /** * Test whether a custom validation error has the correct structure * @param {*} validationError The error to be checked. * @returns {boolean} Returns true if the structure is ok, false otherwise */ exports.isValidValidationError = function (validationError) { return typeof validationError === 'object' && Array.isArray(validationError.path) && typeof validationError.message === 'string'; }; /** * Test whether the child rect fits completely inside the parent rect. * @param {ClientRect} parent * @param {ClientRect} child * @param {number} margin */ exports.insideRect = function (parent, child, margin) { var _margin = margin !== undefined ? margin : 0; return child.left - _margin >= parent.left && child.right + _margin <= parent.right && child.top - _margin >= parent.top && child.bottom + _margin <= parent.bottom; }; /** * Returns a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. * * Source: https://davidwalsh.name/javascript-debounce-function * * @param {function} func * @param {number} wait Number in milliseconds * @param {boolean} [immediate=false] If `immediate` is passed, trigger the * function on the leading edge, instead * of the trailing. * @return {function} Return the debounced function */ exports.debounce = function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; /** * Determines the difference between two texts. * Can only detect one removed or inserted block of characters. * @param {string} oldText * @param {string} newText * @return {{start: number, end: number}} Returns the start and end * of the changed part in newText. */ exports.textDiff = function textDiff(oldText, newText) { var len = newText.length; var start = 0; var oldEnd = oldText.length; var newEnd = newText.length; while (newText.charAt(start) === oldText.charAt(start) && start < len) { start++; } while (newText.charAt(newEnd - 1) === oldText.charAt(oldEnd - 1) && newEnd > start && oldEnd > 0) { newEnd--; oldEnd--; } return {start: start, end: newEnd}; }; /** * Return an object with the selection range or cursor position (if both have the same value) * Support also old browsers (IE8-) * Source: http://ourcodeworld.com/articles/read/282/how-to-get-the-current-cursor-position-and-selection-within-a-text-input-or-textarea-in-javascript * @param {DOMElement} el A dom element of a textarea or input text. * @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text. **/ exports.getInputSelection = function(el) { var startIndex = 0, endIndex = 0, normalizedValue, range, textInputRange, len, endRange; if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { startIndex = el.selectionStart; endIndex = el.selectionEnd; } else { range = document.selection.createRange(); if (range && range.parentElement() == el) { len = el.value.length; normalizedValue = el.value.replace(/\r\n/g, "\n"); // Create a working TextRange that lives only in the input textInputRange = el.createTextRange(); textInputRange.moveToBookmark(range.getBookmark()); // Check if the startIndex and endIndex of the selection are at the very end // of the input, since moveStart/moveEnd doesn't return what we want // in those cases endRange = el.createTextRange(); endRange.collapse(false); if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { startIndex = endIndex = len; } else { startIndex = -textInputRange.moveStart("character", -len); startIndex += normalizedValue.slice(0, startIndex).split("\n").length - 1; if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { endIndex = len; } else { endIndex = -textInputRange.moveEnd("character", -len); endIndex += normalizedValue.slice(0, endIndex).split("\n").length - 1; } } } } return { startIndex: startIndex, endIndex: endIndex, start: _positionForIndex(startIndex), end: _positionForIndex(endIndex) }; /** * Returns textarea row and column position for certain index * @param {Number} index text index * @returns {{row: Number, col: Number}} */ function _positionForIndex(index) { var textTillIndex = el.value.substring(0,index); var row = (textTillIndex.match(/\n/g) || []).length + 1; var col = textTillIndex.length - textTillIndex.lastIndexOf("\n"); return { row: row, column: col } } } /** * Returns the index for certaion position in text element * @param {DOMElement} el A dom element of a textarea or input text. * @param {Number} row row value, > 0, if exceeds rows number - last row will be returned * @param {Number} column column value, > 0, if exceeds column length - end of column will be returned * @returns {Number} index of position in text, -1 if not found */ exports.getIndexForPosition = function(el, row, column) { var text = el.value || ''; if (row > 0 && column > 0) { var rows = text.split('\n', row); row = Math.min(rows.length, row); column = Math.min(rows[row - 1].length, column - 1); var columnCount = (row == 1 ? column : column + 1); // count new line on multiple rows return rows.slice(0, row - 1).join('\n').length + columnCount; } return -1; } /** * Returns location of json paths in certain json string * @param {String} text json string * @param {Array} paths array of json paths * @returns {Array<{path: String, line: Number, row: Number}>} */ exports.getPositionForPath = function(text, paths) { var me = this; var result = []; var jsmap; if (!paths || !paths.length) { return result; } try { jsmap = jsonMap.parse(text); } catch (err) { return result; } paths.forEach(function (path) { var pathArr = me.parsePath(path); var pointerName = pathArr.length ? "/" + pathArr.join("/") : ""; var pointer = jsmap.pointers[pointerName]; if (pointer) { result.push({ path: path, line: pointer.key ? pointer.key.line : (pointer.value ? pointer.value.line : 0), column: pointer.key ? pointer.key.column : (pointer.value ? pointer.value.column : 0) }); } }); return result; } /** * Get the applied color given a color name or code * Source: https://stackoverflow.com/questions/6386090/validating-css-color-names/33184805 * @param {string} color * @returns {string | null} returns the color if the input is a valid * color, and returns null otherwise. Example output: * 'rgba(255,0,0,0.7)' or 'rgb(255,0,0)' */ exports.getColorCSS = function (color) { var ele = document.createElement('div'); ele.style.color = color; return ele.style.color.split(/\s+/).join('').toLowerCase() || null; } /** * Test if a string contains a valid color name or code. * @param {string} color * @returns {boolean} returns true if a valid color, false otherwise */ exports.isValidColor = function (color) { return !!exports.getColorCSS(color); } /** * Make a tooltip for a field based on the field's schema. * @param {object} schema JSON schema * @param {string} [locale] Locale code (for example, zh-CN) * @returns {string} Field tooltip, may be empty string if all relevant schema properties are missing */ exports.makeFieldTooltip = function (schema, locale) { if (!schema) { return ''; } var tooltip = ''; if (schema.title) { tooltip += schema.title; } if (schema.description) { if (tooltip.length > 0) { tooltip += '\n'; } tooltip += schema.description; } if (schema.default) { if (tooltip.length > 0) { tooltip += '\n\n'; } tooltip += translate('default', undefined, locale) + '\n'; tooltip += JSON.stringify(schema.default, null, 2); } if (Array.isArray(schema.examples) && schema.examples.length > 0) { if (tooltip.length > 0) { tooltip += '\n\n'; } tooltip += translate('examples', undefined, locale) + '\n'; schema.examples.forEach(function (example, index) { tooltip += JSON.stringify(example, null, 2); if (index !== schema.examples.length - 1) { tooltip += '\n'; } }); } return tooltip; } /** * Get a nested property from an object. * Returns undefined when the property does not exist. * @param {Object} object * @param {string[]} path * @return {*} */ exports.get = function (object, path) { var value = object for (var i = 0; i < path.length && value !== undefined && value !== null; i++) { value = value[path[i]] } return value; } /** * Find a unique name. Suffix the name with ' (copy)', '(copy 2)', etc * until a unique name is found * @param {string} name * @param {Array} existingPropNames Array with existing prop names */ exports.findUniqueName = function(name, existingPropNames) { var strippedName = name.replace(/ \(copy( \d+)?\)$/, '') var validName = strippedName var i = 1 while (existingPropNames.indexOf(validName) !== -1) { var copy = 'copy' + (i > 1 ? (' ' + i) : '') validName = strippedName + ' (' + copy + ')' i++ } return validName } /** * Get the child paths of an array * @param {JSON} json * @param {boolean} [includeObjects=false] If true, object and array paths are returned as well * @return {string[]} */ exports.getChildPaths = function (json, includeObjects) { var pathsMap = {}; function getObjectChildPaths (json, pathsMap, rootPath, includeObjects) { var isValue = !Array.isArray(json) && !exports.isObject(json) if (isValue || includeObjects) { pathsMap[rootPath || ''] = true; } if (exports.isObject(json)) { Object.keys(json).forEach(function (field) { getObjectChildPaths(json[field], pathsMap, rootPath + '.' + field, includeObjects); }); } } if (Array.isArray(json)) { var max = Math.min(json.length, MAX_ITEMS_FIELDS_COLLECTION); for (var i = 0; i < max; i++) { var item = json[i]; getObjectChildPaths(item, pathsMap, '', includeObjects); } } else { pathsMap[''] = true; } return Object.keys(pathsMap).sort(); } /** * Sort object keys using natural sort * @param {Array} array * @param {String} [path] JSON pointer * @param {'asc' | 'desc'} [direction] */ exports.sort = function (array, path, direction) { var parsedPath = path && path !== '.' ? exports.parsePath(path) : [] var sign = direction === 'desc' ? -1: 1 var sortedArray = array.slice() sortedArray.sort(function (a, b) { var aValue = exports.get(a, parsedPath); var bValue = exports.get(b, parsedPath); return sign * (aValue > bValue ? 1 : aValue < bValue ? -1 : 0); }) return sortedArray; } /** * Sort object keys using natural sort * @param {Object} object * @param {'asc' | 'desc'} [direction] */ exports.sortObjectKeys = function (object, direction) { var sign = (direction === 'desc') ? -1 : 1; var sortedFields = Object.keys(object).sort(function (a, b) { return sign * naturalSort(a, b); }); var sortedObject = {}; sortedFields.forEach(function (field) { sortedObject[field] = object[field]; }); return sortedObject; } /** * Cast contents of a string to the correct type. * This can be a string, a number, a boolean, etc * @param {String} str * @return {*} castedStr * @private */ exports.parseString = function(str) { var lower = str.toLowerCase(); var num = Number(str); // will nicely fail with '123ab' var numFloat = parseFloat(str); // will nicely fail with ' ' if (str === '') { return ''; } else if (lower === 'null') { return null; } else if (lower === 'true') { return true; } else if (lower === 'false') { return false; } else if (!isNaN(num) && !isNaN(numFloat)) { return num; } else { return str; } }; /** * Return a human readable document size * For example formatSize(7570718) outputs '7.2 MiB' * @param {number} size * @return {string} Returns a human readable size */ exports.formatSize = function (size) { if (size < 900) { return size.toFixed() + ' B'; } var KiB = size / 1024; if (KiB < 900) { return KiB.toFixed(1) + ' KiB'; } var MiB = KiB / 1024; if (MiB < 900) { return MiB.toFixed(1) + ' MiB'; } var GiB = MiB / 1024; if (GiB < 900) { return GiB.toFixed(1) + ' GiB'; } var TiB = GiB / 1024; return TiB.toFixed(1) + ' TiB'; } /** * Limit text to a maximum number of characters * @param {string} text * @param {number} maxCharacterCount * @return {string} Returns the limited text, * ending with '...' if the max was exceeded */ exports.limitCharacters = function (text, maxCharacterCount) { if (text.length <= maxCharacterCount) { return text; } return text.slice(0, maxCharacterCount) + '...'; } /** * Test whether a value is an Object * @param {*} value * @return {boolean} */ exports.isObject = function (value) { return typeof value === 'object' && value !== null && !Array.isArray(value) } /** * Helper function to test whether an array contains an item * @param {Array} array * @param {*} item * @return {boolean} Returns true if `item` is in `array`, returns false otherwise. */ exports.contains = function (array, item) { return array.indexOf(item) !== -1; } /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; __webpack_require__(10); var _locales = ['en', 'pt-BR', 'zh-CN', 'tr']; var _defs = { en: { array: 'Array', auto: 'Auto', appendText: 'Append', appendTitle: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', appendSubmenuTitle: 'Select the type of the field to be appended', appendTitleAuto: 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', ascending: 'Ascending', ascendingTitle: 'Sort the childs of this ${type} in ascending order', actionsMenu: 'Click to open the actions menu (Ctrl+M)', collapseAll: 'Collapse all fields', descending: 'Descending', descendingTitle: 'Sort the childs of this ${type} in descending order', drag: 'Drag to move this field (Alt+Shift+Arrows)', duplicateKey: 'duplicate key', duplicateText: 'Duplicate', duplicateTitle: 'Duplicate selected fields (Ctrl+D)', duplicateField: 'Duplicate this field (Ctrl+D)', duplicateFieldError: 'Duplicate field name', cannotParseFieldError: 'Cannot parse field into JSON', cannotParseValueError: 'Cannot parse value into JSON', empty: 'empty', expandAll: 'Expand all fields', expandTitle: 'Click to expand/collapse this field (Ctrl+E). \n' + 'Ctrl+Click to expand/collapse including all childs.', insert: 'Insert', insertTitle: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', insertSub: 'Select the type of the field to be inserted', object: 'Object', ok: 'Ok', redo: 'Redo (Ctrl+Shift+Z)', removeText: 'Remove', removeTitle: 'Remove selected fields (Ctrl+Del)', removeField: 'Remove this field (Ctrl+Del)', selectNode: 'Select a node...', showAll: 'show all', showMore: 'show more', showMoreStatus: 'displaying ${visibleChilds} of ${totalChilds} items.', sort: 'Sort', sortTitle: 'Sort the childs of this ${type}', sortTitleShort: 'Sort contents', sortFieldLabel: 'Field:', sortDirectionLabel: 'Direction:', sortFieldTitle: 'Select the nested field by which to sort the array or object', sortAscending: 'Ascending', sortAscendingTitle: 'Sort the selected field in ascending order', sortDescending: 'Descending', sortDescendingTitle: 'Sort the selected field in descending order', string: 'String', transform: 'Transform', transformTitle: 'Filter, sort, or transform the childs of this ${type}', transformTitleShort: 'Filter, sort, or transform contents', extract: 'Extract', extractTitle: 'Extract this ${type}', transformQueryTitle: 'Enter a JMESPath query', transformWizardLabel: 'Wizard', transformWizardFilter: 'Filter', transformWizardSortBy: 'Sort by', transformWizardSelectFields: 'Select fields', transformQueryLabel: 'Query', transformPreviewLabel: 'Preview', type: 'Type', typeTitle: 'Change the type of this field', openUrl: 'Ctrl+Click or Ctrl+Enter to open url in new window', undo: 'Undo last action (Ctrl+Z)', validationCannotMove: 'Cannot move a field into a child of itself', autoType: 'Field type "auto". ' + 'The field type is automatically determined from the value ' + 'and can be a string, number, boolean, or null.', objectType: 'Field type "object". ' + 'An object contains an unordered set of key/value pairs.', arrayType: 'Field type "array". ' + 'An array contains an ordered collection of values.', stringType: 'Field type "string". ' + 'Field type is not determined from the value, ' + 'but always returned as string.', modeCodeText: 'Code', modeCodeTitle: 'Switch to code highlighter', modeFormText: 'Form', modeFormTitle: 'Switch to form editor', modeTextText: 'Text', modeTextTitle: 'Switch to plain text editor', modeTreeText: 'Tree', modeTreeTitle: 'Switch to tree editor', modeViewText: 'View', modeViewTitle: 'Switch to tree view', modePreviewText: 'Preview', modePreviewTitle: 'Switch to preview mode', examples: 'Examples', default: 'Default', }, 'zh-CN': { array: '数组', auto: '自动', appendText: '追加', appendTitle: '在此字段后追加一个类型为“auto”的新字段 (Ctrl+Shift+Ins)', appendSubmenuTitle: '选择要追加的字段类型', appendTitleAuto: '追加类型为“auto”的新字段 (Ctrl+Shift+Ins)', ascending: '升序', ascendingTitle: '升序排列${type}的子节点', actionsMenu: '点击打开动作菜单(Ctrl+M)', collapseAll: '缩进所有字段', descending: '降序', descendingTitle: '降序排列${type}的子节点', drag: '拖拽移动该节点(Alt+Shift+Arrows)', duplicateKey: '重复键', duplicateText: '复制', duplicateTitle: '复制选中字段(Ctrl+D)', duplicateField: '复制该字段(Ctrl+D)', duplicateFieldError: '重复的字段名称', cannotParseFieldError: '无法将字段解析为JSON', cannotParseValueError: '无法将值解析为JSON', empty: '清空', expandAll: '展开所有字段', expandTitle: '点击 展开/收缩 该字段(Ctrl+E). \n' + 'Ctrl+Click 展开/收缩 包含所有子节点.', insert: '插入', insertTitle: '在此字段前插入类型为“auto”的新字段 (Ctrl+Ins)', insertSub: '选择要插入的字段类型', object: '对象', ok: 'Ok', redo: '重做 (Ctrl+Shift+Z)', removeText: '移除', removeTitle: '移除选中字段 (Ctrl+Del)', removeField: '移除该字段 (Ctrl+Del)', selectNode: '选择一个节点...', showAll: '展示全部', showMore: '展示更多', showMoreStatus: '显示${totalChilds}的${visibleChilds}项目.', sort: '排序', sortTitle: '排序${type}的子节点', sortTitleShort: '内容排序', sortFieldLabel: '字段:', sortDirectionLabel: '方向:', sortFieldTitle: '选择用于对数组或对象排序的嵌套字段', sortAscending: '升序排序', sortAscendingTitle: '按照该字段升序排序', sortDescending: '降序排序', sortDescendingTitle: '按照该字段降序排序', string: '字符串', transform: '变换', transformTitle: '筛选,排序,或者转换${type}的子节点', transformTitleShort: '筛选,排序,或者转换内容', extract: '提取', extractTitle: '提取这个 ${type}', transformQueryTitle: '输入JMESPath查询', transformWizardLabel: '向导', transformWizardFilter: '筛选', transformWizardSortBy: '排序', transformWizardSelectFields: '选择字段', transformQueryLabel: '查询', transformPreviewLabel: '预览', type: '类型', typeTitle: '更改字段类型', openUrl: 'Ctrl+Click 或者 Ctrl+Enter 在新窗口打开链接', undo: '撤销上次动作 (Ctrl+Z)', validationCannotMove: '无法将字段移入其子节点', autoType: '字段类型 "auto". ' + '字段类型由值自动确定 ' + '可以为 string,number,boolean,或者 null.', objectType: '字段类型 "object". ' + '对象包含一组无序的键/值对.', arrayType: '字段类型 "array". ' + '数组包含值的有序集合.', stringType: '字段类型 "string". ' + '字段类型由值自动确定,' + '但始终作为字符串返回.', modeCodeText: '代码', modeCodeTitle: '切换至代码高亮', modeFormText: '表单', modeFormTitle: '切换至表单编辑', modeTextText: '文本', modeTextTitle: '切换至文本编辑', modeTreeText: '树', modeTreeTitle: '切换至树编辑', modeViewText: '视图', modeViewTitle: '切换至树视图', modePreviewText: '预览', modePreviewTitle: '切换至预览模式', examples: '例子', default: '缺省', }, 'pt-BR': { array: 'Lista', auto: 'Automatico', appendText: 'Adicionar', appendTitle: 'Adicionar novo campo com tipo \'auto\' depois deste campo (Ctrl+Shift+Ins)', appendSubmenuTitle: 'Selecione o tipo do campo a ser adicionado', appendTitleAuto: 'Adicionar novo campo com tipo \'auto\' (Ctrl+Shift+Ins)', ascending: 'Ascendente', ascendingTitle: 'Organizar filhor do tipo ${type} em crescente', actionsMenu: 'Clique para abrir o menu de ações (Ctrl+M)', collapseAll: 'Fechar todos campos', descending: 'Descendente', descendingTitle: 'Organizar o filhos do tipo ${type} em decrescente', duplicateKey: 'chave duplicada', drag: 'Arraste para mover este campo (Alt+Shift+Arrows)', duplicateText: 'Duplicar', duplicateTitle: 'Duplicar campos selecionados (Ctrl+D)', duplicateField: 'Duplicar este campo (Ctrl+D)', duplicateFieldError: 'Nome do campo duplicado', cannotParseFieldError: 'Não é possível analisar o campo no JSON', cannotParseValueError: 'Não é possível analisar o valor em JSON', empty: 'vazio', expandAll: 'Expandir todos campos', expandTitle: 'Clique para expandir/encolher este campo (Ctrl+E). \n' + 'Ctrl+Click para expandir/encolher incluindo todos os filhos.', insert: 'Inserir', insertTitle: 'Inserir um novo campo do tipo \'auto\' antes deste campo (Ctrl+Ins)', insertSub: 'Selecionar o tipo de campo a ser inserido', object: 'Objeto', ok: 'Ok', redo: 'Refazer (Ctrl+Shift+Z)', removeText: 'Remover', removeTitle: 'Remover campos selecionados (Ctrl+Del)', removeField: 'Remover este campo (Ctrl+Del)', // TODO: correctly translate selectNode: 'Select a node...', // TODO: correctly translate showAll: 'mostre tudo', // TODO: correctly translate showMore: 'mostre mais', // TODO: correctly translate showMoreStatus: 'exibindo ${visibleChilds} de ${totalChilds} itens.', sort: 'Organizar', sortTitle: 'Organizar os filhos deste ${type}', // TODO: correctly translate sortTitleShort: 'Organizar os filhos', // TODO: correctly translate sortFieldLabel: 'Field:', // TODO: correctly translate sortDirectionLabel: 'Direction:', // TODO: correctly translate sortFieldTitle: 'Select the nested field by which to sort the array or object', // TODO: correctly translate sortAscending: 'Ascending', // TODO: correctly translate sortAscendingTitle: 'Sort the selected field in ascending order', // TODO: correctly translate sortDescending: 'Descending', // TODO: correctly translate sortDescendingTitle: 'Sort the selected field in descending order', string: 'Texto', // TODO: correctly translate transform: 'Transform', // TODO: correctly translate transformTitle: 'Filter, sort, or transform the childs of this ${type}', // TODO: correctly translate transformTitleShort: 'Filter, sort, or transform contents', // TODO: correctly translate transformQueryTitle: 'Enter a JMESPath query', // TODO: correctly translate transformWizardLabel: 'Wizard', // TODO: correctly translate transformWizardFilter: 'Filter', // TODO: correctly translate transformWizardSortBy: 'Sort by', // TODO: correctly translate transformWizardSelectFields: 'Select fields', // TODO: correctly translate transformQueryLabel: 'Query', // TODO: correctly translate transformPreviewLabel: 'Preview', type: 'Tipo', typeTitle: 'Mudar o tipo deste campo', openUrl: 'Ctrl+Click ou Ctrl+Enter para abrir link em nova janela', undo: 'Desfazer último ação (Ctrl+Z)', validationCannotMove: 'Não pode mover um campo como filho dele mesmo', autoType: 'Campo do tipo "auto". ' + 'O tipo do campo é determinao automaticamente a partir do seu valor ' + 'e pode ser texto, número, verdade/falso ou nulo.', objectType: 'Campo do tipo "objeto". ' + 'Um objeto contém uma lista de pares com chave e valor.', arrayType: 'Campo do tipo "lista". ' + 'Uma lista contem uma coleção de valores ordenados.', stringType: 'Campo do tipo "string". ' + 'Campo do tipo nao é determinado através do seu valor, ' + 'mas sempre retornara um texto.', examples: 'Exemplos', default: 'Revelia', }, tr: { array: 'Dizin', auto: 'Otomatik', appendText: 'Ekle', appendTitle: 'Bu alanın altına \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', appendSubmenuTitle: 'Eklenecek alanın tipini seç', appendTitleAuto: '\'Otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', ascending: 'Artan', ascendingTitle: '${type}\'ın alt tiplerini artan düzende sırala', actionsMenu: 'Aksiyon menüsünü açmak için tıklayın (Ctrl+M)', collapseAll: 'Tüm alanları kapat', descending: 'Azalan', descendingTitle: '${type}\'ın alt tiplerini azalan düzende sırala', drag: 'Bu alanı taşımak için sürükleyin (Alt+Shift+Arrows)', duplicateKey: 'Var olan anahtar', duplicateText: 'Aşağıya kopyala', duplicateTitle: 'Seçili alanlardan bir daha oluştur (Ctrl+D)', duplicateField: 'Bu alandan bir daha oluştur (Ctrl+D)', duplicateFieldError: 'Duplicate field name', cannotParseFieldError: 'Alan JSON\'a ayrıştırılamıyor', cannotParseValueError: 'JSON\'a değer ayrıştırılamıyor', empty: 'boş', expandAll: 'Tüm alanları aç', expandTitle: 'Bu alanı açmak/kapatmak için tıkla (Ctrl+E). \n' + 'Alt alanlarda dahil tüm alanları açmak için Ctrl+Click ', insert: 'Ekle', insertTitle: 'Bu alanın üstüne \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Ins)', insertSub: 'Araya eklenecek alanın tipini seç', object: 'Nesne', ok: 'Tamam', redo: 'Yeniden yap (Ctrl+Shift+Z)', removeText: 'Kaldır', removeTitle: 'Seçilen alanları kaldır (Ctrl+Del)', removeField: 'Bu alanı kaldır (Ctrl+Del)', selectNode: 'Bir nesne seç...', showAll: 'tümünü göster', showMore: 'daha fazla göster', showMoreStatus: '${totalChilds} alanın ${visibleChilds} alt alanları gösteriliyor', sort: 'Sırala', sortTitle: '${type}\'ın alt alanlarını sırala', sortTitleShort: 'İçerikleri sırala', sortFieldLabel: 'Alan:', sortDirectionLabel: 'Yön:', sortFieldTitle: 'Diziyi veya nesneyi sıralamak için iç içe geçmiş alanı seçin', sortAscending: 'Artan', sortAscendingTitle: 'Seçili alanı artan düzende sırala', sortDescending: 'Azalan', sortDescendingTitle: 'Seçili alanı azalan düzende sırala', string: 'Karakter Dizisi', transform: 'Dönüştür', transformTitle: '${type}\'ın alt alanlarını filtrele, sırala veya dönüştür', transformTitleShort: 'İçerikleri filterele, sırala veya dönüştür', transformQueryTitle: 'JMESPath sorgusu gir', transformWizardLabel: 'Sihirbaz', transformWizardFilter: 'Filtre', transformWizardSortBy: 'Sırala', transformWizardSelectFields: 'Alanları seç', transformQueryLabel: 'Sorgu', transformPreviewLabel: 'Önizleme', type: 'Tip', typeTitle: 'Bu alanın tipini değiştir', openUrl: 'URL\'i yeni bir pencerede açmak için Ctrl+Click veya Ctrl+Enter', undo: 'Son değişikliği geri al (Ctrl+Z)', validationCannotMove: 'Alt alan olarak taşınamıyor', autoType: 'Alan tipi "otomatik". ' + 'Alan türü otomatik olarak değerden belirlenir' + 've bir dize, sayı, boolean veya null olabilir.', objectType: 'Alan tipi "nesne". ' + 'Bir nesne, sıralanmamış bir anahtar / değer çifti kümesi içerir.', arrayType: 'Alan tipi "dizi". ' + 'Bir dizi, düzenli değerler koleksiyonu içerir.', stringType: 'Alan tipi "karakter dizisi". ' + 'Alan türü değerden belirlenmez,' + 'ancak her zaman karakter dizisi olarak döndürülür.', modeCodeText: 'Kod', modeCodeTitle: 'Kod vurgulayıcıya geç', modeFormText: 'Form', modeFormTitle: 'Form düzenleyiciye geç', modeTextText: 'Metin', modeTextTitle: 'Düz metin düzenleyiciye geç', modeTreeText: 'Ağaç', modeTreeTitle: 'Ağaç düzenleyiciye geç', modeViewText: 'Görünüm', modeViewTitle: 'Ağaç görünümüne geç', examples: 'Örnekler', default: 'Varsayılan', } }; var _defaultLang = 'en'; var _lang; var userLang = typeof navigator !== 'undefined' ? navigator.language || navigator.userLanguage : undefined; _lang = _locales.find(function (l) { return l === userLang; }); if (!_lang) { _lang = _defaultLang; } module.exports = { // supported locales _locales: _locales, _defs: _defs, _lang: _lang, setLanguage: function (lang) { if (!lang) { return; } var langFound = _locales.find(function (l) { return l === lang; }); if (langFound) { _lang = langFound; } else { console.error('Language not found'); } }, setLanguages: function (languages) { if (!languages) { return; } for (var key in languages) { var langFound = _locales.find(function (l) { return l === key; }); if (!langFound) { _locales.push(key); } _defs[key] = Object.assign({}, _defs[_defaultLang], _defs[key], languages[key]); } }, translate: function (key, data, lang) { if (!lang) { lang = _lang; } var text = _defs[lang][key]; if (data) { for (key in data) { text = text.replace('${' + key + '}', data[key]); } } return text || key; } }; /***/ }), /* 2 */ /***/ (function(module, exports) { exports.DEFAULT_MODAL_ANCHOR = document.body; exports.SIZE_LARGE = 10 * 1024 * 1024; // 10 MB exports.MAX_PREVIEW_CHARACTERS = 20000; exports.PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024; // 2 GB /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var createAbsoluteAnchor = __webpack_require__(12).createAbsoluteAnchor; var util = __webpack_require__(0); var translate = __webpack_require__(1).translate; /** * A context menu * @param {Object[]} items Array containing the menu structure * TODO: describe structure * @param {Object} [options] Object with options. Available options: * {function} close Callback called when the * context menu is being closed. * @constructor */ function ContextMenu (items, options) { this.dom = {}; var me = this; var dom = this.dom; this.anchor = undefined; this.items = items; this.eventListeners = {}; this.selection = undefined; // holds the selection before the menu was opened this.onClose = options ? options.close : undefined; // create root element var root = document.createElement('div'); root.className = 'jsoneditor-contextmenu-root'; dom.root = root; // create a container element var menu = document.createElement('div'); menu.className = 'jsoneditor-contextmenu'; dom.menu = menu; root.appendChild(menu); // create a list to hold the menu items var list = document.createElement('ul'); list.className = 'jsoneditor-menu'; menu.appendChild(list); dom.list = list; dom.items = []; // list with all buttons // create a (non-visible) button to set the focus to the menu var focusButton = document.createElement('button'); focusButton.type = 'button'; dom.focusButton = focusButton; var li = document.createElement('li'); li.style.overflow = 'hidden'; li.style.height = '0'; li.appendChild(focusButton); list.appendChild(li); function createMenuItems (list, domItems, items) { items.forEach(function (item) { if (item.type == 'separator') { // create a separator var separator = document.createElement('div'); separator.className = 'jsoneditor-separator'; li = document.createElement('li'); li.appendChild(separator); list.appendChild(li); } else { var domItem = {}; // create a menu item var li = document.createElement('li'); list.appendChild(li); // create a button in the menu item var button = document.createElement('button'); button.type = 'button'; button.className = item.className; domItem.button = button; if (item.title) { button.title = item.title; } if (item.click) { button.onclick = function (event) { event.preventDefault(); me.hide(); item.click(); }; } li.appendChild(button); // create the contents of the button if (item.submenu) { // add the icon to the button var divIcon = document.createElement('div'); divIcon.className = 'jsoneditor-icon'; button.appendChild(divIcon); var divText = document.createElement('div'); divText.className = 'jsoneditor-text' + (item.click ? '' : ' jsoneditor-right-margin'); divText.appendChild(document.createTextNode(item.text)); button.appendChild(divText); var buttonSubmenu; if (item.click) { // submenu and a button with a click handler button.className += ' jsoneditor-default'; var buttonExpand = document.createElement('button'); buttonExpand.type = 'button'; domItem.buttonExpand = buttonExpand; buttonExpand.className = 'jsoneditor-expand'; buttonExpand.innerHTML = '

'; li.appendChild(buttonExpand); if (item.submenuTitle) { buttonExpand.title = item.submenuTitle; } buttonSubmenu = buttonExpand; } else { // submenu and a button without a click handler var divExpand = document.createElement('div'); divExpand.className = 'jsoneditor-expand'; button.appendChild(divExpand); buttonSubmenu = button; } // attach a handler to expand/collapse the submenu buttonSubmenu.onclick = function (event) { event.preventDefault(); me._onExpandItem(domItem); buttonSubmenu.focus(); }; // create the submenu var domSubItems = []; domItem.subItems = domSubItems; var ul = document.createElement('ul'); domItem.ul = ul; ul.className = 'jsoneditor-menu'; ul.style.height = '0'; li.appendChild(ul); createMenuItems(ul, domSubItems, item.submenu); } else { // no submenu, just a button with clickhandler button.innerHTML = '
' + '
' + translate(item.text) + '
'; } domItems.push(domItem); } }); } createMenuItems(list, this.dom.items, items); // TODO: when the editor is small, show the submenu on the right instead of inline? // calculate the max height of the menu with one submenu expanded this.maxHeight = 0; // height in pixels items.forEach(function (item) { var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; me.maxHeight = Math.max(me.maxHeight, height); }); } /** * Get the currently visible buttons * @return {Array.} buttons * @private */ ContextMenu.prototype._getVisibleButtons = function () { var buttons = []; var me = this; this.dom.items.forEach(function (item) { buttons.push(item.button); if (item.buttonExpand) { buttons.push(item.buttonExpand); } if (item.subItems && item == me.expandedItem) { item.subItems.forEach(function (subItem) { buttons.push(subItem.button); if (subItem.buttonExpand) { buttons.push(subItem.buttonExpand); } // TODO: change to fully recursive method }); } }); return buttons; }; // currently displayed context menu, a singleton. We may only have one visible context menu ContextMenu.visibleMenu = undefined; /** * Attach the menu to an anchor * @param {HTMLElement} anchor Anchor where the menu will be attached as sibling. * @param {HTMLElement} frame The root of the JSONEditor window * @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute */ ContextMenu.prototype.show = function (anchor, frame, ignoreParent) { this.hide(); // determine whether to display the menu below or above the anchor var showBelow = true; var parent = anchor.parentNode; var anchorRect = anchor.getBoundingClientRect(); var parentRect = parent.getBoundingClientRect(); var frameRect = frame.getBoundingClientRect(); var me = this; this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, function () { me.hide() }); if (anchorRect.bottom + this.maxHeight < frameRect.bottom) { // fits below -> show below } else if (anchorRect.top - this.maxHeight > frameRect.top) { // fits above -> show above showBelow = false; } else { // doesn't fit above nor below -> show below } var topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top); // position the menu if (showBelow) { // display the menu below the anchor var anchorHeight = anchor.offsetHeight; this.dom.menu.style.left = '0'; this.dom.menu.style.top = topGap + anchorHeight + 'px'; this.dom.menu.style.bottom = ''; } else { // display the menu above the anchor this.dom.menu.style.left = '0'; this.dom.menu.style.top = ''; this.dom.menu.style.bottom = '0px'; } // attach the menu to the temporary, absolute anchor // parent.insertBefore(this.dom.root, anchor); this.dom.absoluteAnchor.appendChild(this.dom.root); // move focus to the first button in the context menu this.selection = util.getSelection(); this.anchor = anchor; setTimeout(function () { me.dom.focusButton.focus(); }, 0); if (ContextMenu.visibleMenu) { ContextMenu.visibleMenu.hide(); } ContextMenu.visibleMenu = this; }; /** * Hide the context menu if visible */ ContextMenu.prototype.hide = function () { // remove temporary absolutely positioned anchor if (this.dom.absoluteAnchor) { this.dom.absoluteAnchor.destroy(); delete this.dom.absoluteAnchor; } // remove the menu from the DOM if (this.dom.root.parentNode) { this.dom.root.parentNode.removeChild(this.dom.root); if (this.onClose) { this.onClose(); } } if (ContextMenu.visibleMenu == this) { ContextMenu.visibleMenu = undefined; } }; /** * Expand a submenu * Any currently expanded submenu will be hided. * @param {Object} domItem * @private */ ContextMenu.prototype._onExpandItem = function (domItem) { var me = this; var alreadyVisible = (domItem == this.expandedItem); // hide the currently visible submenu var expandedItem = this.expandedItem; if (expandedItem) { //var ul = expandedItem.ul; expandedItem.ul.style.height = '0'; expandedItem.ul.style.padding = ''; setTimeout(function () { if (me.expandedItem != expandedItem) { expandedItem.ul.style.display = ''; util.removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected'); } }, 300); // timeout duration must match the css transition duration this.expandedItem = undefined; } if (!alreadyVisible) { var ul = domItem.ul; ul.style.display = 'block'; var height = ul.clientHeight; // force a reflow in Firefox setTimeout(function () { if (me.expandedItem == domItem) { var childsHeight = 0; for (var i = 0; i < ul.childNodes.length; i++) { childsHeight += ul.childNodes[i].clientHeight; } ul.style.height = childsHeight + 'px'; ul.style.padding = '5px 10px'; } }, 0); util.addClassName(ul.parentNode, 'jsoneditor-selected'); this.expandedItem = domItem; } }; /** * Handle onkeydown event * @param {Event} event * @private */ ContextMenu.prototype._onKeyDown = function (event) { var target = event.target; var keynum = event.which; var handled = false; var buttons, targetIndex, prevButton, nextButton; if (keynum == 27) { // ESC // hide the menu on ESC key // restore previous selection and focus if (this.selection) { util.setSelection(this.selection); } if (this.anchor) { this.anchor.focus(); } this.hide(); handled = true; } else if (keynum == 9) { // Tab if (!event.shiftKey) { // Tab buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); if (targetIndex == buttons.length - 1) { // move to first button buttons[0].focus(); handled = true; } } else { // Shift+Tab buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); if (targetIndex == 0) { // move to last button buttons[buttons.length - 1].focus(); handled = true; } } } else if (keynum == 37) { // Arrow Left if (target.className == 'jsoneditor-expand') { buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); prevButton = buttons[targetIndex - 1]; if (prevButton) { prevButton.focus(); } } handled = true; } else if (keynum == 38) { // Arrow Up buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); prevButton = buttons[targetIndex - 1]; if (prevButton && prevButton.className == 'jsoneditor-expand') { // skip expand button prevButton = buttons[targetIndex - 2]; } if (!prevButton) { // move to last button prevButton = buttons[buttons.length - 1]; } if (prevButton) { prevButton.focus(); } handled = true; } else if (keynum == 39) { // Arrow Right buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); nextButton = buttons[targetIndex + 1]; if (nextButton && nextButton.className == 'jsoneditor-expand') { nextButton.focus(); } handled = true; } else if (keynum == 40) { // Arrow Down buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); nextButton = buttons[targetIndex + 1]; if (nextButton && nextButton.className == 'jsoneditor-expand') { // skip expand button nextButton = buttons[targetIndex + 2]; } if (!nextButton) { // move to first button nextButton = buttons[0]; } if (nextButton) { nextButton.focus(); handled = true; } handled = true; } // TODO: arrow left and right if (handled) { event.stopPropagation(); event.preventDefault(); } }; module.exports = ContextMenu; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { (function(exports) { "use strict"; function isArray(obj) { if (obj !== null) { return Object.prototype.toString.call(obj) === "[object Array]"; } else { return false; } } function isObject(obj) { if (obj !== null) { return Object.prototype.toString.call(obj) === "[object Object]"; } else { return false; } } function strictDeepEqual(first, second) { // Check the scalar case first. if (first === second) { return true; } // Check if they are the same type. var firstType = Object.prototype.toString.call(first); if (firstType !== Object.prototype.toString.call(second)) { return false; } // We know that first and second have the same type so we can just check the // first type from now on. if (isArray(first) === true) { // Short circuit if they're not the same length; if (first.length !== second.length) { return false; } for (var i = 0; i < first.length; i++) { if (strictDeepEqual(first[i], second[i]) === false) { return false; } } return true; } if (isObject(first) === true) { // An object is equal if it has the same key/value pairs. var keysSeen = {}; for (var key in first) { if (hasOwnProperty.call(first, key)) { if (strictDeepEqual(first[key], second[key]) === false) { return false; } keysSeen[key] = true; } } // Now check that there aren't any keys in second that weren't // in first. for (var key2 in second) { if (hasOwnProperty.call(second, key2)) { if (keysSeen[key2] !== true) { return false; } } } return true; } return false; } function isFalse(obj) { // From the spec: // A false value corresponds to the following values: // Empty list // Empty object // Empty string // False boolean // null value // First check the scalar values. if (obj === "" || obj === false || obj === null) { return true; } else if (isArray(obj) && obj.length === 0) { // Check for an empty array. return true; } else if (isObject(obj)) { // Check for an empty object. for (var key in obj) { // If there are any keys, then // the object is not empty so the object // is not false. if (obj.hasOwnProperty(key)) { return false; } } return true; } else { return false; } } function objValues(obj) { var keys = Object.keys(obj); var values = []; for (var i = 0; i < keys.length; i++) { values.push(obj[keys[i]]); } return values; } function merge(a, b) { var merged = {}; for (var key in a) { merged[key] = a[key]; } for (var key2 in b) { merged[key2] = b[key2]; } return merged; } var trimLeft; if (typeof String.prototype.trimLeft === "function") { trimLeft = function(str) { return str.trimLeft(); }; } else { trimLeft = function(str) { return str.match(/^\s*(.*)/)[1]; }; } // Type constants used to define functions. var TYPE_NUMBER = 0; var TYPE_ANY = 1; var TYPE_STRING = 2; var TYPE_ARRAY = 3; var TYPE_OBJECT = 4; var TYPE_BOOLEAN = 5; var TYPE_EXPREF = 6; var TYPE_NULL = 7; var TYPE_ARRAY_NUMBER = 8; var TYPE_ARRAY_STRING = 9; var TOK_EOF = "EOF"; var TOK_UNQUOTEDIDENTIFIER = "UnquotedIdentifier"; var TOK_QUOTEDIDENTIFIER = "QuotedIdentifier"; var TOK_RBRACKET = "Rbracket"; var TOK_RPAREN = "Rparen"; var TOK_COMMA = "Comma"; var TOK_COLON = "Colon"; var TOK_RBRACE = "Rbrace"; var TOK_NUMBER = "Number"; var TOK_CURRENT = "Current"; var TOK_EXPREF = "Expref"; var TOK_PIPE = "Pipe"; var TOK_OR = "Or"; var TOK_AND = "And"; var TOK_EQ = "EQ"; var TOK_GT = "GT"; var TOK_LT = "LT"; var TOK_GTE = "GTE"; var TOK_LTE = "LTE"; var TOK_NE = "NE"; var TOK_FLATTEN = "Flatten"; var TOK_STAR = "Star"; var TOK_FILTER = "Filter"; var TOK_DOT = "Dot"; var TOK_NOT = "Not"; var TOK_LBRACE = "Lbrace"; var TOK_LBRACKET = "Lbracket"; var TOK_LPAREN= "Lparen"; var TOK_LITERAL= "Literal"; // The "&", "[", "<", ">" tokens // are not in basicToken because // there are two token variants // ("&&", "[?", "<=", ">="). This is specially handled // below. var basicTokens = { ".": TOK_DOT, "*": TOK_STAR, ",": TOK_COMMA, ":": TOK_COLON, "{": TOK_LBRACE, "}": TOK_RBRACE, "]": TOK_RBRACKET, "(": TOK_LPAREN, ")": TOK_RPAREN, "@": TOK_CURRENT }; var operatorStartToken = { "<": true, ">": true, "=": true, "!": true }; var skipChars = { " ": true, "\t": true, "\n": true }; function isAlpha(ch) { return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ch === "_"; } function isNum(ch) { return (ch >= "0" && ch <= "9") || ch === "-"; } function isAlphaNum(ch) { return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || (ch >= "0" && ch <= "9") || ch === "_"; } function Lexer() { } Lexer.prototype = { tokenize: function(stream) { var tokens = []; this._current = 0; var start; var identifier; var token; while (this._current < stream.length) { if (isAlpha(stream[this._current])) { start = this._current; identifier = this._consumeUnquotedIdentifier(stream); tokens.push({type: TOK_UNQUOTEDIDENTIFIER, value: identifier, start: start}); } else if (basicTokens[stream[this._current]] !== undefined) { tokens.push({type: basicTokens[stream[this._current]], value: stream[this._current], start: this._current}); this._current++; } else if (isNum(stream[this._current])) { token = this._consumeNumber(stream); tokens.push(token); } else if (stream[this._current] === "[") { // No need to increment this._current. This happens // in _consumeLBracket token = this._consumeLBracket(stream); tokens.push(token); } else if (stream[this._current] === "\"") { start = this._current; identifier = this._consumeQuotedIdentifier(stream); tokens.push({type: TOK_QUOTEDIDENTIFIER, value: identifier, start: start}); } else if (stream[this._current] === "'") { start = this._current; identifier = this._consumeRawStringLiteral(stream); tokens.push({type: TOK_LITERAL, value: identifier, start: start}); } else if (stream[this._current] === "`") { start = this._current; var literal = this._consumeLiteral(stream); tokens.push({type: TOK_LITERAL, value: literal, start: start}); } else if (operatorStartToken[stream[this._current]] !== undefined) { tokens.push(this._consumeOperator(stream)); } else if (skipChars[stream[this._current]] !== undefined) { // Ignore whitespace. this._current++; } else if (stream[this._current] === "&") { start = this._current; this._current++; if (stream[this._current] === "&") { this._current++; tokens.push({type: TOK_AND, value: "&&", start: start}); } else { tokens.push({type: TOK_EXPREF, value: "&", start: start}); } } else if (stream[this._current] === "|") { start = this._current; this._current++; if (stream[this._current] === "|") { this._current++; tokens.push({type: TOK_OR, value: "||", start: start}); } else { tokens.push({type: TOK_PIPE, value: "|", start: start}); } } else { var error = new Error("Unknown character:" + stream[this._current]); error.name = "LexerError"; throw error; } } return tokens; }, _consumeUnquotedIdentifier: function(stream) { var start = this._current; this._current++; while (this._current < stream.length && isAlphaNum(stream[this._current])) { this._current++; } return stream.slice(start, this._current); }, _consumeQuotedIdentifier: function(stream) { var start = this._current; this._current++; var maxLength = stream.length; while (stream[this._current] !== "\"" && this._current < maxLength) { // You can escape a double quote and you can escape an escape. var current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === "\"")) { current += 2; } else { current++; } this._current = current; } this._current++; return JSON.parse(stream.slice(start, this._current)); }, _consumeRawStringLiteral: function(stream) { var start = this._current; this._current++; var maxLength = stream.length; while (stream[this._current] !== "'" && this._current < maxLength) { // You can escape a single quote and you can escape an escape. var current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === "'")) { current += 2; } else { current++; } this._current = current; } this._current++; var literal = stream.slice(start + 1, this._current - 1); return literal.replace("\\'", "'"); }, _consumeNumber: function(stream) { var start = this._current; this._current++; var maxLength = stream.length; while (isNum(stream[this._current]) && this._current < maxLength) { this._current++; } var value = parseInt(stream.slice(start, this._current)); return {type: TOK_NUMBER, value: value, start: start}; }, _consumeLBracket: function(stream) { var start = this._current; this._current++; if (stream[this._current] === "?") { this._current++; return {type: TOK_FILTER, value: "[?", start: start}; } else if (stream[this._current] === "]") { this._current++; return {type: TOK_FLATTEN, value: "[]", start: start}; } else { return {type: TOK_LBRACKET, value: "[", start: start}; } }, _consumeOperator: function(stream) { var start = this._current; var startingChar = stream[start]; this._current++; if (startingChar === "!") { if (stream[this._current] === "=") { this._current++; return {type: TOK_NE, value: "!=", start: start}; } else { return {type: TOK_NOT, value: "!", start: start}; } } else if (startingChar === "<") { if (stream[this._current] === "=") { this._current++; return {type: TOK_LTE, value: "<=", start: start}; } else { return {type: TOK_LT, value: "<", start: start}; } } else if (startingChar === ">") { if (stream[this._current] === "=") { this._current++; return {type: TOK_GTE, value: ">=", start: start}; } else { return {type: TOK_GT, value: ">", start: start}; } } else if (startingChar === "=") { if (stream[this._current] === "=") { this._current++; return {type: TOK_EQ, value: "==", start: start}; } } }, _consumeLiteral: function(stream) { this._current++; var start = this._current; var maxLength = stream.length; var literal; while(stream[this._current] !== "`" && this._current < maxLength) { // You can escape a literal char or you can escape the escape. var current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === "`")) { current += 2; } else { current++; } this._current = current; } var literalString = trimLeft(stream.slice(start, this._current)); literalString = literalString.replace("\\`", "`"); if (this._looksLikeJSON(literalString)) { literal = JSON.parse(literalString); } else { // Try to JSON parse it as "" literal = JSON.parse("\"" + literalString + "\""); } // +1 gets us to the ending "`", +1 to move on to the next char. this._current++; return literal; }, _looksLikeJSON: function(literalString) { var startingChars = "[{\""; var jsonLiterals = ["true", "false", "null"]; var numberLooking = "-0123456789"; if (literalString === "") { return false; } else if (startingChars.indexOf(literalString[0]) >= 0) { return true; } else if (jsonLiterals.indexOf(literalString) >= 0) { return true; } else if (numberLooking.indexOf(literalString[0]) >= 0) { try { JSON.parse(literalString); return true; } catch (ex) { return false; } } else { return false; } } }; var bindingPower = {}; bindingPower[TOK_EOF] = 0; bindingPower[TOK_UNQUOTEDIDENTIFIER] = 0; bindingPower[TOK_QUOTEDIDENTIFIER] = 0; bindingPower[TOK_RBRACKET] = 0; bindingPower[TOK_RPAREN] = 0; bindingPower[TOK_COMMA] = 0; bindingPower[TOK_RBRACE] = 0; bindingPower[TOK_NUMBER] = 0; bindingPower[TOK_CURRENT] = 0; bindingPower[TOK_EXPREF] = 0; bindingPower[TOK_PIPE] = 1; bindingPower[TOK_OR] = 2; bindingPower[TOK_AND] = 3; bindingPower[TOK_EQ] = 5; bindingPower[TOK_GT] = 5; bindingPower[TOK_LT] = 5; bindingPower[TOK_GTE] = 5; bindingPower[TOK_LTE] = 5; bindingPower[TOK_NE] = 5; bindingPower[TOK_FLATTEN] = 9; bindingPower[TOK_STAR] = 20; bindingPower[TOK_FILTER] = 21; bindingPower[TOK_DOT] = 40; bindingPower[TOK_NOT] = 45; bindingPower[TOK_LBRACE] = 50; bindingPower[TOK_LBRACKET] = 55; bindingPower[TOK_LPAREN] = 60; function Parser() { } Parser.prototype = { parse: function(expression) { this._loadTokens(expression); this.index = 0; var ast = this.expression(0); if (this._lookahead(0) !== TOK_EOF) { var t = this._lookaheadToken(0); var error = new Error( "Unexpected token type: " + t.type + ", value: " + t.value); error.name = "ParserError"; throw error; } return ast; }, _loadTokens: function(expression) { var lexer = new Lexer(); var tokens = lexer.tokenize(expression); tokens.push({type: TOK_EOF, value: "", start: expression.length}); this.tokens = tokens; }, expression: function(rbp) { var leftToken = this._lookaheadToken(0); this._advance(); var left = this.nud(leftToken); var currentToken = this._lookahead(0); while (rbp < bindingPower[currentToken]) { this._advance(); left = this.led(currentToken, left); currentToken = this._lookahead(0); } return left; }, _lookahead: function(number) { return this.tokens[this.index + number].type; }, _lookaheadToken: function(number) { return this.tokens[this.index + number]; }, _advance: function() { this.index++; }, nud: function(token) { var left; var right; var expression; switch (token.type) { case TOK_LITERAL: return {type: "Literal", value: token.value}; case TOK_UNQUOTEDIDENTIFIER: return {type: "Field", name: token.value}; case TOK_QUOTEDIDENTIFIER: var node = {type: "Field", name: token.value}; if (this._lookahead(0) === TOK_LPAREN) { throw new Error("Quoted identifier not allowed for function names."); } else { return node; } break; case TOK_NOT: right = this.expression(bindingPower.Not); return {type: "NotExpression", children: [right]}; case TOK_STAR: left = {type: "Identity"}; right = null; if (this._lookahead(0) === TOK_RBRACKET) { // This can happen in a multiselect, // [a, b, *] right = {type: "Identity"}; } else { right = this._parseProjectionRHS(bindingPower.Star); } return {type: "ValueProjection", children: [left, right]}; case TOK_FILTER: return this.led(token.type, {type: "Identity"}); case TOK_LBRACE: return this._parseMultiselectHash(); case TOK_FLATTEN: left = {type: TOK_FLATTEN, children: [{type: "Identity"}]}; right = this._parseProjectionRHS(bindingPower.Flatten); return {type: "Projection", children: [left, right]}; case TOK_LBRACKET: if (this._lookahead(0) === TOK_NUMBER || this._lookahead(0) === TOK_COLON) { right = this._parseIndexExpression(); return this._projectIfSlice({type: "Identity"}, right); } else if (this._lookahead(0) === TOK_STAR && this._lookahead(1) === TOK_RBRACKET) { this._advance(); this._advance(); right = this._parseProjectionRHS(bindingPower.Star); return {type: "Projection", children: [{type: "Identity"}, right]}; } else { return this._parseMultiselectList(); } break; case TOK_CURRENT: return {type: TOK_CURRENT}; case TOK_EXPREF: expression = this.expression(bindingPower.Expref); return {type: "ExpressionReference", children: [expression]}; case TOK_LPAREN: var args = []; while (this._lookahead(0) !== TOK_RPAREN) { if (this._lookahead(0) === TOK_CURRENT) { expression = {type: TOK_CURRENT}; this._advance(); } else { expression = this.expression(0); } args.push(expression); } this._match(TOK_RPAREN); return args[0]; default: this._errorToken(token); } }, led: function(tokenName, left) { var right; switch(tokenName) { case TOK_DOT: var rbp = bindingPower.Dot; if (this._lookahead(0) !== TOK_STAR) { right = this._parseDotRHS(rbp); return {type: "Subexpression", children: [left, right]}; } else { // Creating a projection. this._advance(); right = this._parseProjectionRHS(rbp); return {type: "ValueProjection", children: [left, right]}; } break; case TOK_PIPE: right = this.expression(bindingPower.Pipe); return {type: TOK_PIPE, children: [left, right]}; case TOK_OR: right = this.expression(bindingPower.Or); return {type: "OrExpression", children: [left, right]}; case TOK_AND: right = this.expression(bindingPower.And); return {type: "AndExpression", children: [left, right]}; case TOK_LPAREN: var name = left.name; var args = []; var expression, node; while (this._lookahead(0) !== TOK_RPAREN) { if (this._lookahead(0) === TOK_CURRENT) { expression = {type: TOK_CURRENT}; this._advance(); } else { expression = this.expression(0); } if (this._lookahead(0) === TOK_COMMA) { this._match(TOK_COMMA); } args.push(expression); } this._match(TOK_RPAREN); node = {type: "Function", name: name, children: args}; return node; case TOK_FILTER: var condition = this.expression(0); this._match(TOK_RBRACKET); if (this._lookahead(0) === TOK_FLATTEN) { right = {type: "Identity"}; } else { right = this._parseProjectionRHS(bindingPower.Filter); } return {type: "FilterProjection", children: [left, right, condition]}; case TOK_FLATTEN: var leftNode = {type: TOK_FLATTEN, children: [left]}; var rightNode = this._parseProjectionRHS(bindingPower.Flatten); return {type: "Projection", children: [leftNode, rightNode]}; case TOK_EQ: case TOK_NE: case TOK_GT: case TOK_GTE: case TOK_LT: case TOK_LTE: return this._parseComparator(left, tokenName); case TOK_LBRACKET: var token = this._lookaheadToken(0); if (token.type === TOK_NUMBER || token.type === TOK_COLON) { right = this._parseIndexExpression(); return this._projectIfSlice(left, right); } else { this._match(TOK_STAR); this._match(TOK_RBRACKET); right = this._parseProjectionRHS(bindingPower.Star); return {type: "Projection", children: [left, right]}; } break; default: this._errorToken(this._lookaheadToken(0)); } }, _match: function(tokenType) { if (this._lookahead(0) === tokenType) { this._advance(); } else { var t = this._lookaheadToken(0); var error = new Error("Expected " + tokenType + ", got: " + t.type); error.name = "ParserError"; throw error; } }, _errorToken: function(token) { var error = new Error("Invalid token (" + token.type + "): \"" + token.value + "\""); error.name = "ParserError"; throw error; }, _parseIndexExpression: function() { if (this._lookahead(0) === TOK_COLON || this._lookahead(1) === TOK_COLON) { return this._parseSliceExpression(); } else { var node = { type: "Index", value: this._lookaheadToken(0).value}; this._advance(); this._match(TOK_RBRACKET); return node; } }, _projectIfSlice: function(left, right) { var indexExpr = {type: "IndexExpression", children: [left, right]}; if (right.type === "Slice") { return { type: "Projection", children: [indexExpr, this._parseProjectionRHS(bindingPower.Star)] }; } else { return indexExpr; } }, _parseSliceExpression: function() { // [start:end:step] where each part is optional, as well as the last // colon. var parts = [null, null, null]; var index = 0; var currentToken = this._lookahead(0); while (currentToken !== TOK_RBRACKET && index < 3) { if (currentToken === TOK_COLON) { index++; this._advance(); } else if (currentToken === TOK_NUMBER) { parts[index] = this._lookaheadToken(0).value; this._advance(); } else { var t = this._lookahead(0); var error = new Error("Syntax error, unexpected token: " + t.value + "(" + t.type + ")"); error.name = "Parsererror"; throw error; } currentToken = this._lookahead(0); } this._match(TOK_RBRACKET); return { type: "Slice", children: parts }; }, _parseComparator: function(left, comparator) { var right = this.expression(bindingPower[comparator]); return {type: "Comparator", name: comparator, children: [left, right]}; }, _parseDotRHS: function(rbp) { var lookahead = this._lookahead(0); var exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR]; if (exprTokens.indexOf(lookahead) >= 0) { return this.expression(rbp); } else if (lookahead === TOK_LBRACKET) { this._match(TOK_LBRACKET); return this._parseMultiselectList(); } else if (lookahead === TOK_LBRACE) { this._match(TOK_LBRACE); return this._parseMultiselectHash(); } }, _parseProjectionRHS: function(rbp) { var right; if (bindingPower[this._lookahead(0)] < 10) { right = {type: "Identity"}; } else if (this._lookahead(0) === TOK_LBRACKET) { right = this.expression(rbp); } else if (this._lookahead(0) === TOK_FILTER) { right = this.expression(rbp); } else if (this._lookahead(0) === TOK_DOT) { this._match(TOK_DOT); right = this._parseDotRHS(rbp); } else { var t = this._lookaheadToken(0); var error = new Error("Sytanx error, unexpected token: " + t.value + "(" + t.type + ")"); error.name = "ParserError"; throw error; } return right; }, _parseMultiselectList: function() { var expressions = []; while (this._lookahead(0) !== TOK_RBRACKET) { var expression = this.expression(0); expressions.push(expression); if (this._lookahead(0) === TOK_COMMA) { this._match(TOK_COMMA); if (this._lookahead(0) === TOK_RBRACKET) { throw new Error("Unexpected token Rbracket"); } } } this._match(TOK_RBRACKET); return {type: "MultiSelectList", children: expressions}; }, _parseMultiselectHash: function() { var pairs = []; var identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER]; var keyToken, keyName, value, node; for (;;) { keyToken = this._lookaheadToken(0); if (identifierTypes.indexOf(keyToken.type) < 0) { throw new Error("Expecting an identifier token, got: " + keyToken.type); } keyName = keyToken.value; this._advance(); this._match(TOK_COLON); value = this.expression(0); node = {type: "KeyValuePair", name: keyName, value: value}; pairs.push(node); if (this._lookahead(0) === TOK_COMMA) { this._match(TOK_COMMA); } else if (this._lookahead(0) === TOK_RBRACE) { this._match(TOK_RBRACE); break; } } return {type: "MultiSelectHash", children: pairs}; } }; function TreeInterpreter(runtime) { this.runtime = runtime; } TreeInterpreter.prototype = { search: function(node, value) { return this.visit(node, value); }, visit: function(node, value) { var matched, current, result, first, second, field, left, right, collected, i; switch (node.type) { case "Field": if (value === null ) { return null; } else if (isObject(value)) { field = value[node.name]; if (field === undefined) { return null; } else { return field; } } else { return null; } break; case "Subexpression": result = this.visit(node.children[0], value); for (i = 1; i < node.children.length; i++) { result = this.visit(node.children[1], result); if (result === null) { return null; } } return result; case "IndexExpression": left = this.visit(node.children[0], value); right = this.visit(node.children[1], left); return right; case "Index": if (!isArray(value)) { return null; } var index = node.value; if (index < 0) { index = value.length + index; } result = value[index]; if (result === undefined) { result = null; } return result; case "Slice": if (!isArray(value)) { return null; } var sliceParams = node.children.slice(0); var computed = this.computeSliceParams(value.length, sliceParams); var start = computed[0]; var stop = computed[1]; var step = computed[2]; result = []; if (step > 0) { for (i = start; i < stop; i += step) { result.push(value[i]); } } else { for (i = start; i > stop; i += step) { result.push(value[i]); } } return result; case "Projection": // Evaluate left child. var base = this.visit(node.children[0], value); if (!isArray(base)) { return null; } collected = []; for (i = 0; i < base.length; i++) { current = this.visit(node.children[1], base[i]); if (current !== null) { collected.push(current); } } return collected; case "ValueProjection": // Evaluate left child. base = this.visit(node.children[0], value); if (!isObject(base)) { return null; } collected = []; var values = objValues(base); for (i = 0; i < values.length; i++) { current = this.visit(node.children[1], values[i]); if (current !== null) { collected.push(current); } } return collected; case "FilterProjection": base = this.visit(node.children[0], value); if (!isArray(base)) { return null; } var filtered = []; var finalResults = []; for (i = 0; i < base.length; i++) { matched = this.visit(node.children[2], base[i]); if (!isFalse(matched)) { filtered.push(base[i]); } } for (var j = 0; j < filtered.length; j++) { current = this.visit(node.children[1], filtered[j]); if (current !== null) { finalResults.push(current); } } return finalResults; case "Comparator": first = this.visit(node.children[0], value); second = this.visit(node.children[1], value); switch(node.name) { case TOK_EQ: result = strictDeepEqual(first, second); break; case TOK_NE: result = !strictDeepEqual(first, second); break; case TOK_GT: result = first > second; break; case TOK_GTE: result = first >= second; break; case TOK_LT: result = first < second; break; case TOK_LTE: result = first <= second; break; default: throw new Error("Unknown comparator: " + node.name); } return result; case TOK_FLATTEN: var original = this.visit(node.children[0], value); if (!isArray(original)) { return null; } var merged = []; for (i = 0; i < original.length; i++) { current = original[i]; if (isArray(current)) { merged.push.apply(merged, current); } else { merged.push(current); } } return merged; case "Identity": return value; case "MultiSelectList": if (value === null) { return null; } collected = []; for (i = 0; i < node.children.length; i++) { collected.push(this.visit(node.children[i], value)); } return collected; case "MultiSelectHash": if (value === null) { return null; } collected = {}; var child; for (i = 0; i < node.children.length; i++) { child = node.children[i]; collected[child.name] = this.visit(child.value, value); } return collected; case "OrExpression": matched = this.visit(node.children[0], value); if (isFalse(matched)) { matched = this.visit(node.children[1], value); } return matched; case "AndExpression": first = this.visit(node.children[0], value); if (isFalse(first) === true) { return first; } return this.visit(node.children[1], value); case "NotExpression": first = this.visit(node.children[0], value); return isFalse(first); case "Literal": return node.value; case TOK_PIPE: left = this.visit(node.children[0], value); return this.visit(node.children[1], left); case TOK_CURRENT: return value; case "Function": var resolvedArgs = []; for (i = 0; i < node.children.length; i++) { resolvedArgs.push(this.visit(node.children[i], value)); } return this.runtime.callFunction(node.name, resolvedArgs); case "ExpressionReference": var refNode = node.children[0]; // Tag the node with a specific attribute so the type // checker verify the type. refNode.jmespathType = TOK_EXPREF; return refNode; default: throw new Error("Unknown node type: " + node.type); } }, computeSliceParams: function(arrayLength, sliceParams) { var start = sliceParams[0]; var stop = sliceParams[1]; var step = sliceParams[2]; var computed = [null, null, null]; if (step === null) { step = 1; } else if (step === 0) { var error = new Error("Invalid slice, step cannot be 0"); error.name = "RuntimeError"; throw error; } var stepValueNegative = step < 0 ? true : false; if (start === null) { start = stepValueNegative ? arrayLength - 1 : 0; } else { start = this.capSliceRange(arrayLength, start, step); } if (stop === null) { stop = stepValueNegative ? -1 : arrayLength; } else { stop = this.capSliceRange(arrayLength, stop, step); } computed[0] = start; computed[1] = stop; computed[2] = step; return computed; }, capSliceRange: function(arrayLength, actualValue, step) { if (actualValue < 0) { actualValue += arrayLength; if (actualValue < 0) { actualValue = step < 0 ? -1 : 0; } } else if (actualValue >= arrayLength) { actualValue = step < 0 ? arrayLength - 1 : arrayLength; } return actualValue; } }; function Runtime(interpreter) { this._interpreter = interpreter; this.functionTable = { // name: [function, ] // The can be: // // { // args: [[type1, type2], [type1, type2]], // variadic: true|false // } // // Each arg in the arg list is a list of valid types // (if the function is overloaded and supports multiple // types. If the type is "any" then no type checking // occurs on the argument. Variadic is optional // and if not provided is assumed to be false. abs: {_func: this._functionAbs, _signature: [{types: [TYPE_NUMBER]}]}, avg: {_func: this._functionAvg, _signature: [{types: [TYPE_ARRAY_NUMBER]}]}, ceil: {_func: this._functionCeil, _signature: [{types: [TYPE_NUMBER]}]}, contains: { _func: this._functionContains, _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}, {types: [TYPE_ANY]}]}, "ends_with": { _func: this._functionEndsWith, _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]}, floor: {_func: this._functionFloor, _signature: [{types: [TYPE_NUMBER]}]}, length: { _func: this._functionLength, _signature: [{types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT]}]}, map: { _func: this._functionMap, _signature: [{types: [TYPE_EXPREF]}, {types: [TYPE_ARRAY]}]}, max: { _func: this._functionMax, _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]}, "merge": { _func: this._functionMerge, _signature: [{types: [TYPE_OBJECT], variadic: true}] }, "max_by": { _func: this._functionMaxBy, _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] }, sum: {_func: this._functionSum, _signature: [{types: [TYPE_ARRAY_NUMBER]}]}, "starts_with": { _func: this._functionStartsWith, _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]}, min: { _func: this._functionMin, _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]}, "min_by": { _func: this._functionMinBy, _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] }, type: {_func: this._functionType, _signature: [{types: [TYPE_ANY]}]}, keys: {_func: this._functionKeys, _signature: [{types: [TYPE_OBJECT]}]}, values: {_func: this._functionValues, _signature: [{types: [TYPE_OBJECT]}]}, sort: {_func: this._functionSort, _signature: [{types: [TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER]}]}, "sort_by": { _func: this._functionSortBy, _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] }, join: { _func: this._functionJoin, _signature: [ {types: [TYPE_STRING]}, {types: [TYPE_ARRAY_STRING]} ] }, reverse: { _func: this._functionReverse, _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}]}, "to_array": {_func: this._functionToArray, _signature: [{types: [TYPE_ANY]}]}, "to_string": {_func: this._functionToString, _signature: [{types: [TYPE_ANY]}]}, "to_number": {_func: this._functionToNumber, _signature: [{types: [TYPE_ANY]}]}, "not_null": { _func: this._functionNotNull, _signature: [{types: [TYPE_ANY], variadic: true}] } }; } Runtime.prototype = { callFunction: function(name, resolvedArgs) { var functionEntry = this.functionTable[name]; if (functionEntry === undefined) { throw new Error("Unknown function: " + name + "()"); } this._validateArgs(name, resolvedArgs, functionEntry._signature); return functionEntry._func.call(this, resolvedArgs); }, _validateArgs: function(name, args, signature) { // Validating the args requires validating // the correct arity and the correct type of each arg. // If the last argument is declared as variadic, then we need // a minimum number of args to be required. Otherwise it has to // be an exact amount. var pluralized; if (signature[signature.length - 1].variadic) { if (args.length < signature.length) { pluralized = signature.length === 1 ? " argument" : " arguments"; throw new Error("ArgumentError: " + name + "() " + "takes at least" + signature.length + pluralized + " but received " + args.length); } } else if (args.length !== signature.length) { pluralized = signature.length === 1 ? " argument" : " arguments"; throw new Error("ArgumentError: " + name + "() " + "takes " + signature.length + pluralized + " but received " + args.length); } var currentSpec; var actualType; var typeMatched; for (var i = 0; i < signature.length; i++) { typeMatched = false; currentSpec = signature[i].types; actualType = this._getTypeName(args[i]); for (var j = 0; j < currentSpec.length; j++) { if (this._typeMatches(actualType, currentSpec[j], args[i])) { typeMatched = true; break; } } if (!typeMatched) { throw new Error("TypeError: " + name + "() " + "expected argument " + (i + 1) + " to be type " + currentSpec + " but received type " + actualType + " instead."); } } }, _typeMatches: function(actual, expected, argValue) { if (expected === TYPE_ANY) { return true; } if (expected === TYPE_ARRAY_STRING || expected === TYPE_ARRAY_NUMBER || expected === TYPE_ARRAY) { // The expected type can either just be array, // or it can require a specific subtype (array of numbers). // // The simplest case is if "array" with no subtype is specified. if (expected === TYPE_ARRAY) { return actual === TYPE_ARRAY; } else if (actual === TYPE_ARRAY) { // Otherwise we need to check subtypes. // I think this has potential to be improved. var subtype; if (expected === TYPE_ARRAY_NUMBER) { subtype = TYPE_NUMBER; } else if (expected === TYPE_ARRAY_STRING) { subtype = TYPE_STRING; } for (var i = 0; i < argValue.length; i++) { if (!this._typeMatches( this._getTypeName(argValue[i]), subtype, argValue[i])) { return false; } } return true; } } else { return actual === expected; } }, _getTypeName: function(obj) { switch (Object.prototype.toString.call(obj)) { case "[object String]": return TYPE_STRING; case "[object Number]": return TYPE_NUMBER; case "[object Array]": return TYPE_ARRAY; case "[object Boolean]": return TYPE_BOOLEAN; case "[object Null]": return TYPE_NULL; case "[object Object]": // Check if it's an expref. If it has, it's been // tagged with a jmespathType attr of 'Expref'; if (obj.jmespathType === TOK_EXPREF) { return TYPE_EXPREF; } else { return TYPE_OBJECT; } } }, _functionStartsWith: function(resolvedArgs) { return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0; }, _functionEndsWith: function(resolvedArgs) { var searchStr = resolvedArgs[0]; var suffix = resolvedArgs[1]; return searchStr.indexOf(suffix, searchStr.length - suffix.length) !== -1; }, _functionReverse: function(resolvedArgs) { var typeName = this._getTypeName(resolvedArgs[0]); if (typeName === TYPE_STRING) { var originalStr = resolvedArgs[0]; var reversedStr = ""; for (var i = originalStr.length - 1; i >= 0; i--) { reversedStr += originalStr[i]; } return reversedStr; } else { var reversedArray = resolvedArgs[0].slice(0); reversedArray.reverse(); return reversedArray; } }, _functionAbs: function(resolvedArgs) { return Math.abs(resolvedArgs[0]); }, _functionCeil: function(resolvedArgs) { return Math.ceil(resolvedArgs[0]); }, _functionAvg: function(resolvedArgs) { var sum = 0; var inputArray = resolvedArgs[0]; for (var i = 0; i < inputArray.length; i++) { sum += inputArray[i]; } return sum / inputArray.length; }, _functionContains: function(resolvedArgs) { return resolvedArgs[0].indexOf(resolvedArgs[1]) >= 0; }, _functionFloor: function(resolvedArgs) { return Math.floor(resolvedArgs[0]); }, _functionLength: function(resolvedArgs) { if (!isObject(resolvedArgs[0])) { return resolvedArgs[0].length; } else { // As far as I can tell, there's no way to get the length // of an object without O(n) iteration through the object. return Object.keys(resolvedArgs[0]).length; } }, _functionMap: function(resolvedArgs) { var mapped = []; var interpreter = this._interpreter; var exprefNode = resolvedArgs[0]; var elements = resolvedArgs[1]; for (var i = 0; i < elements.length; i++) { mapped.push(interpreter.visit(exprefNode, elements[i])); } return mapped; }, _functionMerge: function(resolvedArgs) { var merged = {}; for (var i = 0; i < resolvedArgs.length; i++) { var current = resolvedArgs[i]; for (var key in current) { merged[key] = current[key]; } } return merged; }, _functionMax: function(resolvedArgs) { if (resolvedArgs[0].length > 0) { var typeName = this._getTypeName(resolvedArgs[0][0]); if (typeName === TYPE_NUMBER) { return Math.max.apply(Math, resolvedArgs[0]); } else { var elements = resolvedArgs[0]; var maxElement = elements[0]; for (var i = 1; i < elements.length; i++) { if (maxElement.localeCompare(elements[i]) < 0) { maxElement = elements[i]; } } return maxElement; } } else { return null; } }, _functionMin: function(resolvedArgs) { if (resolvedArgs[0].length > 0) { var typeName = this._getTypeName(resolvedArgs[0][0]); if (typeName === TYPE_NUMBER) { return Math.min.apply(Math, resolvedArgs[0]); } else { var elements = resolvedArgs[0]; var minElement = elements[0]; for (var i = 1; i < elements.length; i++) { if (elements[i].localeCompare(minElement) < 0) { minElement = elements[i]; } } return minElement; } } else { return null; } }, _functionSum: function(resolvedArgs) { var sum = 0; var listToSum = resolvedArgs[0]; for (var i = 0; i < listToSum.length; i++) { sum += listToSum[i]; } return sum; }, _functionType: function(resolvedArgs) { switch (this._getTypeName(resolvedArgs[0])) { case TYPE_NUMBER: return "number"; case TYPE_STRING: return "string"; case TYPE_ARRAY: return "array"; case TYPE_OBJECT: return "object"; case TYPE_BOOLEAN: return "boolean"; case TYPE_EXPREF: return "expref"; case TYPE_NULL: return "null"; } }, _functionKeys: function(resolvedArgs) { return Object.keys(resolvedArgs[0]); }, _functionValues: function(resolvedArgs) { var obj = resolvedArgs[0]; var keys = Object.keys(obj); var values = []; for (var i = 0; i < keys.length; i++) { values.push(obj[keys[i]]); } return values; }, _functionJoin: function(resolvedArgs) { var joinChar = resolvedArgs[0]; var listJoin = resolvedArgs[1]; return listJoin.join(joinChar); }, _functionToArray: function(resolvedArgs) { if (this._getTypeName(resolvedArgs[0]) === TYPE_ARRAY) { return resolvedArgs[0]; } else { return [resolvedArgs[0]]; } }, _functionToString: function(resolvedArgs) { if (this._getTypeName(resolvedArgs[0]) === TYPE_STRING) { return resolvedArgs[0]; } else { return JSON.stringify(resolvedArgs[0]); } }, _functionToNumber: function(resolvedArgs) { var typeName = this._getTypeName(resolvedArgs[0]); var convertedValue; if (typeName === TYPE_NUMBER) { return resolvedArgs[0]; } else if (typeName === TYPE_STRING) { convertedValue = +resolvedArgs[0]; if (!isNaN(convertedValue)) { return convertedValue; } } return null; }, _functionNotNull: function(resolvedArgs) { for (var i = 0; i < resolvedArgs.length; i++) { if (this._getTypeName(resolvedArgs[i]) !== TYPE_NULL) { return resolvedArgs[i]; } } return null; }, _functionSort: function(resolvedArgs) { var sortedArray = resolvedArgs[0].slice(0); sortedArray.sort(); return sortedArray; }, _functionSortBy: function(resolvedArgs) { var sortedArray = resolvedArgs[0].slice(0); if (sortedArray.length === 0) { return sortedArray; } var interpreter = this._interpreter; var exprefNode = resolvedArgs[1]; var requiredType = this._getTypeName( interpreter.visit(exprefNode, sortedArray[0])); if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) { throw new Error("TypeError"); } var that = this; // In order to get a stable sort out of an unstable // sort algorithm, we decorate/sort/undecorate (DSU) // by creating a new list of [index, element] pairs. // In the cmp function, if the evaluated elements are // equal, then the index will be used as the tiebreaker. // After the decorated list has been sorted, it will be // undecorated to extract the original elements. var decorated = []; for (var i = 0; i < sortedArray.length; i++) { decorated.push([i, sortedArray[i]]); } decorated.sort(function(a, b) { var exprA = interpreter.visit(exprefNode, a[1]); var exprB = interpreter.visit(exprefNode, b[1]); if (that._getTypeName(exprA) !== requiredType) { throw new Error( "TypeError: expected " + requiredType + ", received " + that._getTypeName(exprA)); } else if (that._getTypeName(exprB) !== requiredType) { throw new Error( "TypeError: expected " + requiredType + ", received " + that._getTypeName(exprB)); } if (exprA > exprB) { return 1; } else if (exprA < exprB) { return -1; } else { // If they're equal compare the items by their // order to maintain relative order of equal keys // (i.e. to get a stable sort). return a[0] - b[0]; } }); // Undecorate: extract out the original list elements. for (var j = 0; j < decorated.length; j++) { sortedArray[j] = decorated[j][1]; } return sortedArray; }, _functionMaxBy: function(resolvedArgs) { var exprefNode = resolvedArgs[1]; var resolvedArray = resolvedArgs[0]; var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]); var maxNumber = -Infinity; var maxRecord; var current; for (var i = 0; i < resolvedArray.length; i++) { current = keyFunction(resolvedArray[i]); if (current > maxNumber) { maxNumber = current; maxRecord = resolvedArray[i]; } } return maxRecord; }, _functionMinBy: function(resolvedArgs) { var exprefNode = resolvedArgs[1]; var resolvedArray = resolvedArgs[0]; var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]); var minNumber = Infinity; var minRecord; var current; for (var i = 0; i < resolvedArray.length; i++) { current = keyFunction(resolvedArray[i]); if (current < minNumber) { minNumber = current; minRecord = resolvedArray[i]; } } return minRecord; }, createKeyFunction: function(exprefNode, allowedTypes) { var that = this; var interpreter = this._interpreter; var keyFunc = function(x) { var current = interpreter.visit(exprefNode, x); if (allowedTypes.indexOf(that._getTypeName(current)) < 0) { var msg = "TypeError: expected one of " + allowedTypes + ", received " + that._getTypeName(current); throw new Error(msg); } return current; }; return keyFunc; } }; function compile(stream) { var parser = new Parser(); var ast = parser.parse(stream); return ast; } function tokenize(stream) { var lexer = new Lexer(); return lexer.tokenize(stream); } function search(data, expression) { var parser = new Parser(); // This needs to be improved. Both the interpreter and runtime depend on // each other. The runtime needs the interpreter to support exprefs. // There's likely a clean way to avoid the cyclic dependency. var runtime = new Runtime(); var interpreter = new TreeInterpreter(runtime); runtime._interpreter = interpreter; var node = parser.parse(expression); return interpreter.search(node, data); } exports.tokenize = tokenize; exports.compile = compile; exports.search = search; exports.strictDeepEqual = strictDeepEqual; })( false ? undefined : exports); /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { var picoModal = __webpack_require__(13); var translate = __webpack_require__(1).translate; var util = __webpack_require__(0); /** * Show advanced sorting modal * @param {HTMLElement} container The container where to center * the modal and create an overlay * @param {JSON} json The JSON data to be sorted. * @param {function} onSort Callback function, invoked with * an object containing the selected * path and direction * @param {Object} options * Available options: * - {string} path The selected path * - {'asc' | 'desc'} direction The selected direction */ function showSortModal (container, json, onSort, options) { var paths = Array.isArray(json) ? util.getChildPaths(json) : ['']; var selectedPath = options && options.path && util.contains(paths, options.path) ? options.path : paths[0] var selectedDirection = options && options.direction || 'asc' var content = '
' + '
' + translate('sort') + '
' + '
' + '' + '' + '' + ' ' + ' ' + '' + '' + ' ' + ' ' + '' + '' + '' + '' + '' + '
' + translate('sortFieldLabel') + ' ' + '
' + ' ' + '
' + '
' + translate('sortDirectionLabel') + ' ' + '
' + '' + '' + '
' + '
' + ' ' + '
' + '
' + '
'; picoModal({ parent: container, content: content, overlayClass: 'jsoneditor-modal-overlay', modalClass: 'jsoneditor-modal jsoneditor-modal-sort' }) .afterCreate(function (modal) { var form = modal.modalElem().querySelector('form'); var ok = modal.modalElem().querySelector('#ok'); var field = modal.modalElem().querySelector('#field'); var direction = modal.modalElem().querySelector('#direction'); function preprocessPath(path) { return (path === '') ? '@' : (path[0] === '.') ? path.slice(1) : path; } paths.forEach(function (path) { var option = document.createElement('option'); option.text = preprocessPath(path); option.value = path; field.appendChild(option); }); function setDirection(value) { direction.value = value; direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value; } field.value = selectedPath || paths[0]; setDirection(selectedDirection || 'asc'); direction.onclick = function (event) { setDirection(event.target.getAttribute('data-value')); }; ok.onclick = function (event) { event.preventDefault(); event.stopPropagation(); modal.close(); onSort({ path: field.value, direction: direction.value }) }; if (form) { // form is not available when JSONEditor is created inside a form form.onsubmit = ok.onclick; } }) .afterClose(function (modal) { modal.destroy(); }) .show(); } module.exports = showSortModal; /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { var jmespath = __webpack_require__(4); var picoModal = __webpack_require__(13); var Selectr = __webpack_require__(30); var translate = __webpack_require__(1).translate; var stringifyPartial = __webpack_require__(31).stringifyPartial; var util = __webpack_require__(0); var MAX_PREVIEW_CHARACTERS = __webpack_require__(2).MAX_PREVIEW_CHARACTERS var debounce = util.debounce; /** * Show advanced filter and transform modal using JMESPath * @param {HTMLElement} container The container where to center * the modal and create an overlay * @param {JSON} json The json data to be transformed * @param {function} onTransform Callback invoked with the created * query as callback */ function showTransformModal (container, json, onTransform) { var value = json; var content = '