UNPKG

react-json-ide

Version:

A stylish, editor-like, modular, react component for viewing, editing, and debugging javascript object syntax!

2,088 lines • 65.2 kB
/** @license react-json-ide v2.6.0
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread";
import React, { Component } from 'react';
import themes from './themes';
import { identical, getType } from './mitsuketa';
import err from './err';
import { format } from './locale';
import defaultLocale from './locale/en';

class JSONInput extends Component {
  constructor(props) {
    super(props);
    this.updateInternalProps = this.updateInternalProps.bind(this);
    this.createMarkup = this.createMarkup.bind(this);
    this.onClick = this.onClick.bind(this);
    this.onBlur = this.onBlur.bind(this);
    this.update = this.update.bind(this);
    this.getCursorPosition = this.getCursorPosition.bind(this);
    this.setCursorPosition = this.setCursorPosition.bind(this);
    this.scheduledUpdate = this.scheduledUpdate.bind(this);
    this.setUpdateTime = this.setUpdateTime.bind(this);
    this.renderLabels = this.renderLabels.bind(this);
    this.newSpan = this.newSpan.bind(this);
    this.renderErrorMessage = this.renderErrorMessage.bind(this);
    this.onScroll = this.onScroll.bind(this);
    this.showPlaceholder = this.showPlaceholder.bind(this);
    this.tokenize = this.tokenize.bind(this);
    this.onKeyPress = this.onKeyPress.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.onPaste = this.onPaste.bind(this);
    this.stopEvent = this.stopEvent.bind(this);
    this.refContent = null;
    this.refLabels = null;
    this.updateInternalProps();
    this.renderCount = 1;
    this.state = {
      prevPlaceholder: '',
      markupText: '',
      plainText: '',
      json: '',
      jsObject: undefined,
      lines: false,
      error: false
    };

    if (!this.props.locale) {
      console.warn("[react-json-ide - Deprecation Warning] You did not provide a 'locale' prop for your JSON input - This will be required in a future version. English has been set as a default.");
    }
  }

  updateInternalProps() {
    let colors = {},
        style = {},
        theme = themes.dark_vscode_tribute;
    if ('theme' in this.props) if (typeof this.props.theme === 'string') if (this.props.theme in themes) theme = themes[this.props.theme];
    colors = theme;
    if ('colors' in this.props) colors = {
      default: 'default' in this.props.colors ? this.props.colors.default : colors.default,
      string: 'string' in this.props.colors ? this.props.colors.string : colors.string,
      number: 'number' in this.props.colors ? this.props.colors.number : colors.number,
      colon: 'colon' in this.props.colors ? this.props.colors.colon : colors.colon,
      keys: 'keys' in this.props.colors ? this.props.colors.keys : colors.keys,
      keys_whiteSpace: 'keys_whiteSpace' in this.props.colors ? this.props.colors.keys_whiteSpace : colors.keys_whiteSpace,
      primitive: 'primitive' in this.props.colors ? this.props.colors.primitive : colors.primitive,
      error: 'error' in this.props.colors ? this.props.colors.error : colors.error,
      background: 'background' in this.props.colors ? this.props.colors.background : colors.background,
      background_warning: 'background_warning' in this.props.colors ? this.props.colors.background_warning : colors.background_warning
    };
    this.colors = colors;
    if ('style' in this.props) style = {
      outerBox: 'outerBox' in this.props.style ? this.props.style.outerBox : {},
      container: 'container' in this.props.style ? this.props.style.container : {},
      warningBox: 'warningBox' in this.props.style ? this.props.style.warningBox : {},
      errorMessage: 'errorMessage' in this.props.style ? this.props.style.errorMessage : {},
      body: 'body' in this.props.style ? this.props.style.body : {},
      labelColumn: 'labelColumn' in this.props.style ? this.props.style.labelColumn : {},
      labels: 'labels' in this.props.style ? this.props.style.labels : {},
      contentBox: 'contentBox' in this.props.style ? this.props.style.contentBox : {}
    };else style = {
      outerBox: {},
      container: {},
      warningBox: {},
      errorMessage: {},
      body: {},
      labelColumn: {},
      labels: {},
      contentBox: {}
    };
    this.style = style;
    this.confirmGood = 'confirmGood' in this.props ? this.props.confirmGood : true;
    const totalHeight = this.props.height || '610px',
          totalWidth = this.props.width || '479px';
    this.totalHeight = totalHeight;
    this.totalWidth = totalWidth;

    if (!('onKeyPressUpdate' in this.props) || this.props.onKeyPressUpdate) {
      if (!this.timer) this.timer = setInterval(this.scheduledUpdate, 100);
    } else if (this.timer) {
      clearInterval(this.timer);
      this.timer = false;
    }

    this.updateTime = false;
    this.waitAfterKeyPress = 'waitAfterKeyPress' in this.props ? this.props.waitAfterKeyPress : 1000;
    this.resetConfiguration = 'reset' in this.props ? this.props.reset : false;
  }

  render() {
    const id = this.props.id,
          markupText = this.state.markupText,
          error = this.state.error,
          colors = this.colors,
          style = this.style,
          confirmGood = this.confirmGood,
          totalHeight = this.totalHeight,
          totalWidth = this.totalWidth,
          hasError = error ? 'token' in error : false;
    this.renderCount++;
    return React.createElement("div", {
      name: "outer-box",
      id: id && id + '-outer-box',
      style: _objectSpread({
        display: 'block',
        overflow: 'none',
        height: totalHeight,
        width: totalWidth,
        margin: 0,
        boxSizing: 'border-box',
        position: 'relative'
      }, style.outerBox)
    }, confirmGood ? React.createElement("div", {
      style: {
        opacity: hasError ? 0 : 1,
        height: '30px',
        width: '30px',
        position: 'absolute',
        top: 0,
        right: 0,
        transform: 'translate(-25%,25%)',
        pointerEvents: 'none',
        transitionDuration: '0.2s',
        transitionTimingFunction: 'cubic-bezier(0, 1, 0.5, 1)'
      }
    }, React.createElement("svg", {
      height: "30px",
      width: "30px",
      viewBox: "0 0 100 100"
    }, React.createElement("path", {
      fillRule: "evenodd",
      clipRule: "evenodd",
      fill: "green",
      opacity: "0.85",
      d: "M39.363,79L16,55.49l11.347-11.419L39.694,56.49L72.983,23L84,34.085L39.363,79z"
    }))) : void 0, React.createElement("div", {
      name: "container",
      id: id && id + '-container',
      style: _objectSpread({
        display: 'block',
        height: totalHeight,
        width: totalWidth,
        margin: 0,
        boxSizing: 'border-box',
        overflow: 'hidden',
        fontFamily: 'Roboto, sans-serif'
      }, style.container),
      onClick: this.onClick
    }, React.createElement("div", {
      name: "warning-box",
      id: id && id + '-warning-box',
      style: _objectSpread({
        display: 'block',
        overflow: 'hidden',
        height: hasError ? '60px' : '0px',
        width: '100%',
        margin: 0,
        backgroundColor: colors.background_warning,
        transitionDuration: '0.2s',
        transitionTimingFunction: 'cubic-bezier(0, 1, 0.5, 1)'
      }, style.warningBox),
      onClick: this.onClick
    }, React.createElement("span", {
      style: {
        display: 'inline-block',
        height: '60px',
        width: '60px',
        margin: 0,
        boxSizing: 'border-box',
        overflow: 'hidden',
        verticalAlign: 'top',
        pointerEvents: 'none'
      },
      onClick: this.onClick
    }, React.createElement("div", {
      style: {
        position: 'relative',
        top: 0,
        left: 0,
        height: '60px',
        width: '60px',
        margin: 0,
        pointerEvents: 'none'
      },
      onClick: this.onClick
    }, React.createElement("div", {
      style: {
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        pointerEvents: 'none'
      },
      onClick: this.onClick
    }, React.createElement("svg", {
      height: "25px",
      width: "25px",
      viewBox: "0 0 100 100"
    }, React.createElement("path", {
      fillRule: "evenodd",
      clipRule: "evenodd",
      fill: "red",
      d: "M73.9,5.75c0.467-0.467,1.067-0.7,1.8-0.7c0.7,0,1.283,0.233,1.75,0.7l16.8,16.8  c0.467,0.5,0.7,1.084,0.7,1.75c0,0.733-0.233,1.334-0.7,1.801L70.35,50l23.9,23.95c0.5,0.467,0.75,1.066,0.75,1.8  c0,0.667-0.25,1.25-0.75,1.75l-16.8,16.75c-0.534,0.467-1.117,0.7-1.75,0.7s-1.233-0.233-1.8-0.7L50,70.351L26.1,94.25  c-0.567,0.467-1.167,0.7-1.8,0.7c-0.667,0-1.283-0.233-1.85-0.7L5.75,77.5C5.25,77,5,76.417,5,75.75c0-0.733,0.25-1.333,0.75-1.8  L29.65,50L5.75,26.101C5.25,25.667,5,25.066,5,24.3c0-0.666,0.25-1.25,0.75-1.75l16.8-16.8c0.467-0.467,1.05-0.7,1.75-0.7  c0.733,0,1.333,0.233,1.8,0.7L50,29.65L73.9,5.75z"
    }))))), React.createElement("span", {
      style: {
        display: 'inline-block',
        height: '60px',
        width: 'calc(100% - 60px)',
        margin: 0,
        overflow: 'hidden',
        verticalAlign: 'top',
        position: 'absolute',
        pointerEvents: 'none'
      },
      onClick: this.onClick
    }, this.renderErrorMessage())), React.createElement("div", {
      name: "body",
      id: id && id + '-body',
      style: _objectSpread({
        display: 'flex',
        overflow: 'none',
        height: hasError ? 'calc(100% - 60px)' : '100%',
        width: '',
        margin: 0,
        resize: 'none',
        fontFamily: 'Roboto Mono, Monaco, monospace',
        fontSize: '11px',
        backgroundColor: colors.background,
        transitionDuration: '0.2s',
        transitionTimingFunction: 'cubic-bezier(0, 1, 0.5, 1)'
      }, style.body),
      onClick: this.onClick
    }, React.createElement("span", {
      name: "labels",
      id: id && id + '-labels',
      ref: ref => this.refLabels = ref,
      style: _objectSpread({
        display: 'inline-block',
        boxSizing: 'border-box',
        verticalAlign: 'top',
        height: '100%',
        width: '44px',
        margin: 0,
        padding: '5px 0px 5px 10px',
        overflow: 'hidden',
        color: '#D4D4D4'
      }, style.labelColumn),
      onClick: this.onClick
    }, this.renderLabels()), React.createElement("span", {
      id: id,
      ref: ref => this.refContent = ref,
      contentEditable: true,
      style: _objectSpread({
        display: 'inline-block',
        boxSizing: 'border-box',
        verticalAlign: 'top',
        height: '100%',
        width: '',
        flex: 1,
        margin: 0,
        padding: '5px',
        overflowX: 'hidden',
        overflowY: 'auto',
        wordWrap: 'break-word',
        whiteSpace: 'pre-line',
        color: '#D4D4D4',
        outline: 'none'
      }, style.contentBox),
      dangerouslySetInnerHTML: this.createMarkup(markupText),
      onKeyPress: this.onKeyPress,
      onKeyDown: this.onKeyDown,
      onClick: this.onClick,
      onBlur: this.onBlur,
      onScroll: this.onScroll,
      onPaste: this.onPaste,
      autoComplete: "off",
      autoCorrect: "off",
      autoCapitalize: "off",
      spellCheck: false
    }))));
  }

  renderErrorMessage() {
    const locale = this.props.locale || defaultLocale,
          error = this.state.error,
          style = this.style;
    if (!error) return void 0;
    return React.createElement("p", {
      style: _objectSpread({
        color: 'red',
        fontSize: '12px',
        position: 'absolute',
        width: 'calc(100% - 60px)',
        height: '60px',
        boxSizing: 'border-box',
        margin: 0,
        padding: 0,
        paddingRight: '10px',
        overflowWrap: 'break-word',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center'
      }, style.errorMessage)
    }, format(locale.format, error));
  }

  renderLabels() {
    const colors = this.colors,
          style = this.style,
          errorLine = this.state.error ? this.state.error.line : -1,
          lines = this.state.lines ? this.state.lines : 1;
    let labels = new Array(lines);

    for (var i = 0; i < lines - 1; i++) labels[i] = i + 1;

    return labels.map(number => {
      const color = number !== errorLine ? colors.default : 'red';
      return React.createElement("div", {
        key: number,
        style: _objectSpread({}, style.labels, {
          color: color
        })
      }, number);
    });
  }

  createMarkup(markupText) {
    if (markupText === undefined) return {
      __html: ''
    };
    return {
      __html: '' + markupText
    };
  }

  newSpan(i, token, depth) {
    let colors = this.colors,
        type = token.type,
        string = token.string;
    let color = '';

    switch (type) {
      case 'string':
      case 'number':
      case 'primitive':
      case 'error':
        color = colors[token.type];
        break;

      case 'key':
        if (string === ' ') color = colors.keys_whiteSpace;else color = colors.keys;
        break;

      case 'symbol':
        if (string === ':') color = colors.colon;else color = colors.default;
        break;

      default:
        color = colors.default;
        break;
    }

    if (string.length !== string.replace(/</g, '').replace(/>/g, '').length) string = '<xmp style=display:inline;>' + string + '</xmp>';
    return '<span' + ' type="' + type + '"' + ' value="' + string + '"' + ' depth="' + depth + '"' + ' style="color:' + color + '"' + '>' + string + '</span>';
  }

  getCursorPosition(countBR) {
    /**
     * Need to deprecate countBR
     * It is used to differenciate between good markup render, and aux render when error found
     * Adjustments based on coundBR account for usage of <br> instead of <span> for linebreaks to determine acurate cursor position
     * Find a way to consolidate render styles
     */
    const isChildOf = node => {
      while (node !== null) {
        if (node === this.refContent) return true;
        node = node.parentNode;
      }

      return false;
    };

    let selection = window.getSelection(),
        charCount = -1,
        linebreakCount = 0,
        node;

    if (selection.focusNode && isChildOf(selection.focusNode)) {
      node = selection.focusNode;
      charCount = selection.focusOffset;

      while (node) {
        if (node === this.refContent) break;

        if (node.previousSibling) {
          node = node.previousSibling;
          if (countBR) if (node.nodeName === 'BR') linebreakCount++;
          charCount += node.textContent.length;
        } else {
          node = node.parentNode;
          if (node === null) break;
        }
      }
    }

    return charCount + linebreakCount;
  }

  setCursorPosition(nextPosition) {
    if ([false, null, undefined].indexOf(nextPosition) > -1) return;

    const createRange = (node, chars, range) => {
      if (!range) {
        range = document.createRange();
        range.selectNode(node);
        range.setStart(node, 0);
      }

      if (chars.count === 0) {
        range.setEnd(node, chars.count);
      } else if (node && chars.count > 0) {
        if (node.nodeType === Node.TEXT_NODE) {
          if (node.textContent.length < chars.count) chars.count -= node.textContent.length;else {
            range.setEnd(node, chars.count);
            chars.count = 0;
          }
        } else for (var lp = 0; lp < node.childNodes.length; lp++) {
          range = createRange(node.childNodes[lp], chars, range);
          if (chars.count === 0) break;
        }
      }

      return range;
    };

    const setPosition = chars => {
      if (chars < 0) return;
      let selection = window.getSelection(),
          range = createRange(this.refContent, {
        count: chars
      });
      if (!range) return;
      range.collapse(false);
      selection.removeAllRanges();
      selection.addRange(range);
    };

    if (nextPosition > 0) setPosition(nextPosition);else this.refContent.focus();
  }

  update(cursorOffset = 0, updateCursorPosition = true) {
    const container = this.refContent,
          data = this.tokenize(container);
    if ('onChange' in this.props) this.props.onChange({
      plainText: data.indented,
      markupText: data.markup,
      json: data.json,
      jsObject: data.jsObject,
      lines: data.lines,
      error: data.error
    });
    let cursorPosition = this.getCursorPosition(data.error) + cursorOffset;
    this.setState({
      plainText: data.indented,
      markupText: data.markup,
      json: data.json,
      jsObject: data.jsObject,
      lines: data.lines,
      error: data.error
    });
    this.updateTime = false;
    if (updateCursorPosition) this.setCursorPosition(cursorPosition);
  }

  scheduledUpdate() {
    if ('onKeyPressUpdate' in this.props) if (this.props.onKeyPressUpdate === false) return;
    const {
      updateTime
    } = this;
    if (updateTime === false) return;
    if (updateTime > new Date().getTime()) return;
    this.update();
  }

  setUpdateTime() {
    if ('onKeyPressUpdate' in this.props) if (this.props.onKeyPressUpdate === false) return;
    this.updateTime = new Date().getTime() + this.waitAfterKeyPress;
  }

  stopEvent(event) {
    if (!event) return;
    event.preventDefault();
    event.stopPropagation();
  }

  onKeyPress(event) {
    const ctrlOrMetaIsPressed = event.ctrlKey || event.metaKey;
    if (this.props.viewOnly && !ctrlOrMetaIsPressed) this.stopEvent(event);
    if (!ctrlOrMetaIsPressed) this.setUpdateTime();
  }

  onKeyDown(event) {
    const viewOnly = !!this.props.viewOnly;
    const ctrlOrMetaIsPressed = event.ctrlKey || event.metaKey;

    switch (event.key) {
      case 'Tab':
        this.stopEvent(event);
        if (viewOnly) break;
        document.execCommand("insertText", false, "  ");
        this.setUpdateTime();
        break;

      case 'Backspace':
      case 'Delete':
        if (viewOnly) this.stopEvent(event);
        this.setUpdateTime();
        break;

      case 'ArrowLeft':
      case 'ArrowRight':
      case 'ArrowUp':
      case 'ArrowDown':
        this.setUpdateTime();
        break;

      case 'a':
      case 'c':
        if (viewOnly && !ctrlOrMetaIsPressed) this.stopEvent(event);
        break;

      default:
        if (viewOnly) this.stopEvent(event);
        break;
    }
  }

  onPaste(event) {
    if (this.props.viewOnly) {
      this.stopEvent(event);
    } else {
      event.preventDefault();
      var text = event.clipboardData.getData('text/plain');
      document.execCommand('insertText', false, text);
    }

    this.update();
  }

  onClick() {
    if ('viewOnly' in this.props) if (this.props.viewOnly) return;
  }

  onBlur() {
    if ('viewOnly' in this.props) if (this.props.viewOnly) return;
    this.update(0, false);
  }

  onScroll(event) {
    this.refLabels.scrollTop = event.target.scrollTop;
  }

  componentDidUpdate() {
    this.updateInternalProps();
    this.showPlaceholder();
  }

  componentDidMount() {
    this.showPlaceholder();
  }

  componentWillUnmount() {
    if (this.timer) clearInterval(this.timer);
  }

  showPlaceholder() {
    const placeholderDoesNotExist = !('placeholder' in this.props);
    if (placeholderDoesNotExist) return;
    const {
      placeholder
    } = this.props;
    const placeholderHasEmptyValues = [undefined, null].indexOf(placeholder) > -1;
    if (placeholderHasEmptyValues) return;
    const {
      prevPlaceholder,
      jsObject
    } = this.state;
    const {
      resetConfiguration
    } = this;
    const placeholderDataType = getType(placeholder);
    const unexpectedDataType = ['object', 'array'].indexOf(placeholderDataType) === -1;
    if (unexpectedDataType) err.throwError('showPlaceholder', 'placeholder', 'either an object or an array');
    const samePlaceholderValues = identical(placeholder, prevPlaceholder); // Component will always re-render when new placeholder value is any different from previous placeholder value. 

    let componentShouldUpdate = !samePlaceholderValues;

    if (!componentShouldUpdate) {
      if (resetConfiguration) {
        /**
         * If 'reset' property is set true or is truthy,
         * any difference between placeholder and current value
         * should trigger component re-render
         */
        if (jsObject !== undefined) componentShouldUpdate = !identical(placeholder, jsObject);
      }
    }

    if (!componentShouldUpdate) return;
    const data = this.tokenize(placeholder);
    this.setState({
      prevPlaceholder: placeholder,
      plainText: data.indentation,
      markupText: data.markup,
      lines: data.lines,
      error: data.error
    });
  }

  tokenize(something) {
    if (typeof something !== 'object') return console.error('tokenize() expects object type properties only. Got \'' + typeof something + '\' type instead.');
    const locale = this.props.locale || defaultLocale;
    const newSpan = this.newSpan;
    /**
     *     DOM NODE || ONBLUR OR UPDATE
     */

    if ('nodeType' in something) {
      const containerNode = something.cloneNode(true),
            hasChildren = containerNode.hasChildNodes();
      if (!hasChildren) return '';
      const children = containerNode.childNodes;
      let buffer = {
        tokens_unknown: [],
        tokens_proto: [],
        tokens_split: [],
        tokens_fallback: [],
        tokens_normalize: [],
        tokens_merge: [],
        tokens_plainText: '',
        indented: '',
        json: '',
        jsObject: undefined,
        markup: ''
      };

      for (var i = 0; i < children.length; i++) {
        let child = children[i];
        let info = {};

        switch (child.nodeName) {
          case 'SPAN':
            info = {
              string: child.textContent,
              type: child.attributes.type.textContent
            };
            buffer.tokens_unknown.push(info);
            break;

          case 'DIV':
            buffer.tokens_unknown.push({
              string: child.textContent,
              type: 'unknown'
            });
            break;

          case 'BR':
            if (child.textContent === '') buffer.tokens_unknown.push({
              string: '\n',
              type: 'unknown'
            });
            break;

          case '#text':
            buffer.tokens_unknown.push({
              string: child.wholeText,
              type: 'unknown'
            });
            break;

          case 'FONT':
            buffer.tokens_unknown.push({
              string: child.textContent,
              type: 'unknown'
            });
            break;

          default:
            console.error('Unrecognized node:', {
              child
            });
            break;
        }
      }

      function quarkize(text, prefix = '') {
        let buffer = {
          active: false,
          string: '',
          number: '',
          symbol: '',
          space: '',
          delimiter: '',
          quarks: []
        };

        function pushAndStore(char, type) {
          switch (type) {
            case 'symbol':
            case 'delimiter':
              if (buffer.active) buffer.quarks.push({
                string: buffer[buffer.active],
                type: prefix + '-' + buffer.active
              });
              buffer[buffer.active] = '';
              buffer.active = type;
              buffer[buffer.active] = char;
              break;

            default:
              if (type !== buffer.active || [buffer.string, char].indexOf('\n') > -1) {
                if (buffer.active) buffer.quarks.push({
                  string: buffer[buffer.active],
                  type: prefix + '-' + buffer.active
                });
                buffer[buffer.active] = '';
                buffer.active = type;
                buffer[buffer.active] = char;
              } else buffer[type] += char;

              break;
          }
        }

        function finalPush() {
          if (buffer.active) {
            buffer.quarks.push({
              string: buffer[buffer.active],
              type: prefix + '-' + buffer.active
            });
            buffer[buffer.active] = '';
            buffer.active = false;
          }
        }

        for (var i = 0; i < text.length; i++) {
          const char = text.charAt(i);

          switch (char) {
            case '"':
            case "'":
              pushAndStore(char, 'delimiter');
              break;

            case ' ':
            case '\u00A0':
              pushAndStore(char, 'space');
              break;

            case '{':
            case '}':
            case '[':
            case ']':
            case ':':
            case ',':
              pushAndStore(char, 'symbol');
              break;

            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
              if (buffer.active === 'string') pushAndStore(char, 'string');else pushAndStore(char, 'number');
              break;

            case '-':
              if (i < text.length - 1) if ('0123456789'.indexOf(text.charAt(i + 1)) > -1) {
                pushAndStore(char, 'number');
                break;
              }

            case '.':
              if (i < text.length - 1 && i > 0) if ('0123456789'.indexOf(text.charAt(i + 1)) > -1 && '0123456789'.indexOf(text.charAt(i - 1)) > -1) {
                pushAndStore(char, 'number');
                break;
              }

            default:
              pushAndStore(char, 'string');
              break;
          }
        }

        finalPush();
        return buffer.quarks;
      }

      for (var i = 0; i < buffer.tokens_unknown.length; i++) {
        let token = buffer.tokens_unknown[i];
        buffer.tokens_proto = buffer.tokens_proto.concat(quarkize(token.string, 'proto'));
      }

      function validToken(string, type) {
        const quotes = '\'"';
        let firstChar = '',
            lastChar = '',
            quoteType = false;

        switch (type) {
          case 'primitive':
            if (['true', 'false', 'null', 'undefined'].indexOf(string) === -1) return false;
            break;

          case 'string':
            if (string.length < 2) return false;
            firstChar = string.charAt(0), lastChar = string.charAt(string.length - 1), quoteType = quotes.indexOf(firstChar);
            if (quoteType === -1) return false;
            if (firstChar !== lastChar) return false;

            for (var i = 0; i < string.length; i++) {
              if (i > 0 && i < string.length - 1) if (string.charAt(i) === quotes[quoteType]) if (string.charAt(i - 1) !== '\\') return false;
            }

            break;

          case 'key':
            if (string.length === 0) return false;
            firstChar = string.charAt(0), lastChar = string.charAt(string.length - 1), quoteType = quotes.indexOf(firstChar);

            if (quoteType > -1) {
              if (string.length === 1) return false;
              if (firstChar !== lastChar) return false;

              for (var i = 0; i < string.length; i++) {
                if (i > 0 && i < string.length - 1) if (string.charAt(i) === quotes[quoteType]) if (string.charAt(i - 1) !== '\\') return false;
              }
            } else {
              const nonAlphanumeric = '\'"`.,:;{}[]&<>=~*%\\|/-+!?@^ \xa0';

              for (var i = 0; i < nonAlphanumeric.length; i++) {
                const nonAlpha = nonAlphanumeric.charAt(i);
                if (string.indexOf(nonAlpha) > -1) return false;
              }
            }

            break;

          case 'number':
            for (var i = 0; i < string.length; i++) {
              if ('0123456789'.indexOf(string.charAt(i)) === -1) if (i === 0) {
                if ('-' !== string.charAt(0)) return false;
              } else if ('.' !== string.charAt(i)) return false;
            }

            break;

          case 'symbol':
            if (string.length > 1) return false;
            if ('{[:]},'.indexOf(string) === -1) return false;
            break;

          case 'colon':
            if (string.length > 1) return false;
            if (':' !== string) return false;
            break;

          default:
            return true;
            break;
        }

        return true;
      }

      for (var i = 0; i < buffer.tokens_proto.length; i++) {
        let token = buffer.tokens_proto[i];

        if (token.type.indexOf('proto') === -1) {
          if (!validToken(token.string, token.type)) {
            buffer.tokens_split = buffer.tokens_split.concat(quarkize(token.string, 'split'));
          } else buffer.tokens_split.push(token);
        } else buffer.tokens_split.push(token);
      }

      for (var i = 0; i < buffer.tokens_split.length; i++) {
        let token = buffer.tokens_split[i];
        let type = token.type,
            string = token.string,
            length = string.length,
            fallback = [];

        if (type.indexOf('-') > -1) {
          type = type.slice(type.indexOf('-') + 1);
          if (type !== 'string') fallback.push('string');
          fallback.push('key');
          fallback.push('error');
        }

        let tokul = {
          string: string,
          length: length,
          type: type,
          fallback: fallback
        };
        buffer.tokens_fallback.push(tokul);
      }

      function tokenFollowed() {
        const last = buffer.tokens_normalize.length - 1;
        if (last < 1) return false;

        for (var i = last; i >= 0; i--) {
          const previousToken = buffer.tokens_normalize[i];

          switch (previousToken.type) {
            case 'space':
            case 'linebreak':
              break;

            default:
              return previousToken;
              break;
          }
        }

        return false;
      }

      let buffer2 = {
        brackets: [],
        stringOpen: false,
        isValue: false
      };

      for (var i = 0; i < buffer.tokens_fallback.length; i++) {
        let token = buffer.tokens_fallback[i];
        const type = token.type,
              string = token.string;
        let normalToken = {
          type: type,
          string: string
        };

        switch (type) {
          case 'symbol':
          case 'colon':
            if (buffer2.stringOpen) {
              if (buffer2.isValue) normalToken.type = 'string';else normalToken.type = 'key';
              break;
            }

            switch (string) {
              case '[':
              case '{':
                buffer2.brackets.push(string);
                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                break;

              case ']':
              case '}':
                buffer2.brackets.pop();
                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                break;

              case ',':
                if (tokenFollowed().type === 'colon') break;
                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                break;

              case ':':
                normalToken.type = 'colon';
                buffer2.isValue = true;
                break;
            }

            break;

          case 'delimiter':
            if (buffer2.isValue) normalToken.type = 'string';else normalToken.type = 'key';

            if (!buffer2.stringOpen) {
              buffer2.stringOpen = string;
              break;
            }

            if (i > 0) {
              const previousToken = buffer.tokens_fallback[i - 1],
                    _string = previousToken.string,
                    _type = previousToken.type,
                    _char = _string.charAt(_string.length - 1);

              if (_type === 'string' && _char === '\\') break;
            }

            if (buffer2.stringOpen === string) {
              buffer2.stringOpen = false;
              break;
            }

            break;

          case 'primitive':
          case 'string':
            if (['false', 'true', 'null', 'undefined'].indexOf(string) > -1) {
              const lastIndex = buffer.tokens_normalize.length - 1;

              if (lastIndex >= 0) {
                if (buffer.tokens_normalize[lastIndex].type !== 'string') {
                  normalToken.type = 'primitive';
                  break;
                }

                normalToken.type = 'string';
                break;
              }

              normalToken.type = 'primitive';
              break;
            }

            if (string === '\n') if (!buffer2.stringOpen) {
              normalToken.type = 'linebreak';
              break;
            }
            if (buffer2.isValue) normalToken.type = 'string';else normalToken.type = 'key';
            break;

          case 'space':
            if (buffer2.stringOpen) if (buffer2.isValue) normalToken.type = 'string';else normalToken.type = 'key';
            break;

          case 'number':
            if (buffer2.stringOpen) if (buffer2.isValue) normalToken.type = 'string';else normalToken.type = 'key';
            break;

          default:
            break;
        }

        buffer.tokens_normalize.push(normalToken);
      }

      for (var i = 0; i < buffer.tokens_normalize.length; i++) {
        const token = buffer.tokens_normalize[i];
        let mergedToken = {
          string: token.string,
          type: token.type,
          tokens: [i]
        };
        if (['symbol', 'colon'].indexOf(token.type) === -1) if (i + 1 < buffer.tokens_normalize.length) {
          let count = 0;

          for (var u = i + 1; u < buffer.tokens_normalize.length; u++) {
            const nextToken = buffer.tokens_normalize[u];
            if (token.type !== nextToken.type) break;
            mergedToken.string += nextToken.string;
            mergedToken.tokens.push(u);
            count++;
          }

          i += count;
        }
        buffer.tokens_merge.push(mergedToken);
      }

      const quotes = '\'"',
            alphanumeric = 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '0123456789' + '_$';
      var error = false,
          line = buffer.tokens_merge.length > 0 ? 1 : 0;
      buffer2 = {
        brackets: [],
        stringOpen: false,
        isValue: false
      };

      function setError(tokenID, reason, offset = 0) {
        error = {
          token: tokenID,
          line: line,
          reason: reason
        };
        buffer.tokens_merge[tokenID + offset].type = 'error';
      }

      function followedBySymbol(tokenID, options) {
        if (tokenID === undefined) console.error('tokenID argument must be an integer.');
        if (options === undefined) console.error('options argument must be an array.');
        if (tokenID === buffer.tokens_merge.length - 1) return false;

        for (var i = tokenID + 1; i < buffer.tokens_merge.length; i++) {
          const nextToken = buffer.tokens_merge[i];

          switch (nextToken.type) {
            case 'space':
            case 'linebreak':
              break;

            case 'symbol':
            case 'colon':
              if (options.indexOf(nextToken.string) > -1) return i;else return false;
              break;

            default:
              return false;
              break;
          }
        }

        return false;
      }

      function followsSymbol(tokenID, options) {
        if (tokenID === undefined) console.error('tokenID argument must be an integer.');
        if (options === undefined) console.error('options argument must be an array.');
        if (tokenID === 0) return false;

        for (var i = tokenID - 1; i >= 0; i--) {
          const previousToken = buffer.tokens_merge[i];

          switch (previousToken.type) {
            case 'space':
            case 'linebreak':
              break;

            case 'symbol':
            case 'colon':
              if (options.indexOf(previousToken.string) > -1) return true;
              return false;
              break;

            default:
              return false;
              break;
          }
        }

        return false;
      }

      function typeFollowed(tokenID) {
        if (tokenID === undefined) console.error('tokenID argument must be an integer.');
        if (tokenID === 0) return false;

        for (var i = tokenID - 1; i >= 0; i--) {
          const previousToken = buffer.tokens_merge[i];

          switch (previousToken.type) {
            case 'space':
            case 'linebreak':
              break;

            default:
              return previousToken.type;
              break;
          }
        }

        return false;
      }

      let bracketList = [];

      for (var i = 0; i < buffer.tokens_merge.length; i++) {
        if (error) break;
        let token = buffer.tokens_merge[i],
            string = token.string,
            type = token.type,
            found = false;

        switch (type) {
          case 'space':
            break;

          case 'linebreak':
            line++;
            break;

          case 'symbol':
            switch (string) {
              case '{':
              case '[':
                found = followsSymbol(i, ['}', ']']);

                if (found) {
                  setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                    firstToken: buffer.tokens_merge[found].string,
                    secondToken: string
                  }));
                  break;
                }

                if (string === '[' && i > 0) if (!followsSymbol(i, [':', '[', ','])) {
                  setError(i, format(locale.invalidToken.tokenSequence.permitted, {
                    firstToken: "[",
                    secondToken: [":", "[", ","]
                  }));
                  break;
                }
                if (string === '{') if (followsSymbol(i, ['{'])) {
                  setError(i, format(locale.invalidToken.double, {
                    token: "{"
                  }));
                  break;
                }
                buffer2.brackets.push(string);
                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                bracketList.push({
                  i: i,
                  line: line,
                  string: string
                });
                break;

              case '}':
              case ']':
                if (string === '}') if (buffer2.brackets[buffer2.brackets.length - 1] !== '{') {
                  setError(i, format(locale.brace.curly.missingOpen));
                  break;
                }
                if (string === '}') if (followsSymbol(i, [','])) {
                  setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                    firstToken: ",",
                    secondToken: "}"
                  }));
                  break;
                }
                if (string === ']') if (buffer2.brackets[buffer2.brackets.length - 1] !== '[') {
                  setError(i, format(locale.brace.square.missingOpen));
                  break;
                }
                if (string === ']') if (followsSymbol(i, [':'])) {
                  setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                    firstToken: ":",
                    secondToken: "]"
                  }));
                  break;
                }
                buffer2.brackets.pop();
                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                bracketList.push({
                  i: i,
                  line: line,
                  string: string
                });
                break;

              case ',':
                found = followsSymbol(i, ['{']);

                if (found) {
                  if (followedBySymbol(i, ['}'])) {
                    setError(i, format(locale.brace.curly.cannotWrap, {
                      token: ","
                    }));
                    break;
                  }

                  setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                    firstToken: "{",
                    secondToken: ","
                  }));
                  break;
                }

                if (followedBySymbol(i, ['}', ',', ']'])) {
                  setError(i, format(locale.noTrailingOrLeadingComma));
                  break;
                }

                found = typeFollowed(i);

                switch (found) {
                  case 'key':
                  case 'colon':
                    setError(i, format(locale.invalidToken.termSequence.prohibited, {
                      firstTerm: found === 'key' ? locale.types.key : locale.symbols.colon,
                      secondTerm: locale.symbols.comma
                    }));
                    break;

                  case 'symbol':
                    if (followsSymbol(i, ['{'])) {
                      setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                        firstToken: "{",
                        secondToken: ","
                      }));
                      break;
                    }

                    break;

                  default:
                    break;
                }

                buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
                break;

              default:
                break;
            }

            buffer.json += string;
            break;

          case 'colon':
            found = followsSymbol(i, ['[']);

            if (found && followedBySymbol(i, [']'])) {
              setError(i, format(locale.brace.square.cannotWrap, {
                token: ":"
              }));
              break;
            }

            if (found) {
              setError(i, format(locale.invalidToken.tokenSequence.prohibited, {
                firstToken: "[",
                secondToken: ":"
              }));
              break;
            }

            if (typeFollowed(i) !== 'key') {
              setError(i, format(locale.invalidToken.termSequence.permitted, {
                firstTerm: locale.symbols.colon,
                secondTerm: locale.types.key
              }));
              break;
            }

            if (followedBySymbol(i, ['}', ']'])) {
              setError(i, format(locale.invalidToken.termSequence.permitted, {
                firstTerm: locale.symbols.colon,
                secondTerm: locale.types.value
              }));
              break;
            }

            buffer2.isValue = true;
            buffer.json += string;
            break;

          case 'key':
          case 'string':
            let firstChar = string.charAt(0),
                lastChar = string.charAt(string.length - 1),
                quote_primary = quotes.indexOf(firstChar);
            if (quotes.indexOf(firstChar) === -1) if (quotes.indexOf(lastChar) !== -1) {
              setError(i, format(locale.string.missingOpen, {
                quote: firstChar
              }));
              break;
            }
            if (quotes.indexOf(lastChar) === -1) if (quotes.indexOf(firstChar) !== -1) {
              setError(i, format(locale.string.missingClose, {
                quote: firstChar
              }));
              break;
            }
            if (quotes.indexOf(firstChar) > -1) if (firstChar !== lastChar) {
              setError(i, format(locale.string.missingClose, {
                quote: firstChar
              }));
              break;
            }
            if ('string' === type) if (quotes.indexOf(firstChar) === -1 && quotes.indexOf(lastChar) === -1) {
              setError(i, format(locale.string.mustBeWrappedByQuotes));
              break;
            }
            if ('key' === type) if (followedBySymbol(i, ['}', ']'])) {
              setError(i, format(locale.invalidToken.termSequence.permitted, {
                firstTerm: locale.types.key,
                secondTerm: locale.symbols.colon
              }));
            }
            if (quotes.indexOf(firstChar) === -1 && quotes.indexOf(lastChar) === -1) for (var h = 0; h < string.length; h++) {
              if (error) break;
              const c = string.charAt(h);

              if (alphanumeric.indexOf(c) === -1) {
                setError(i, format(locale.string.nonAlphanumeric, {
                  token: c
                }));
                break;
              }
            }
            if (firstChar === "'") string = '"' + string.slice(1, -1) + '"';else if (firstChar !== '"') string = '"' + string + '"';
            if ('key' === type) if ('key' === typeFollowed(i)) {
              if (i > 0) if (!isNaN(buffer.tokens_merge[i - 1])) {
                buffer.tokens_merge[i - 1] += buffer.tokens_merge[i];
                setError(i, format(locale.key.numberAndLetterMissingQuotes));
                break;
              }
              setError(i, format(locale.key.spaceMissingQuotes));
              break;
            }
            if ('key' === type) if (!followsSymbol(i, ['{', ','])) {
              setError(i, format(locale.invalidToken.tokenSequence.permitted, {
                firstToken: type,
                secondToken: ["{", ","]
              }));
              break;
            }
            if ('string' === type) if (!followsSymbol(i, ['[', ':', ','])) {
              setError(i, format(locale.invalidToken.tokenSequence.permitted, {
                firstToken: type,
                secondToken: ["[", ":", ","]
              }));
              break;
            }
            if ('key' === type) if (buffer2.isValue) {
              setError(i, format(locale.string.unexpectedKey));
              break;
            }
            if ('string' === type) if (!buffer2.isValue) {
              setError(i, format(locale.key.unexpectedString));
              break;
            }
            buffer.json += string;
            break;

          case 'number':
          case 'primitive':
            if (followsSymbol(i, ['{'])) {
              buffer.tokens_merge[i].type = 'key';
              type = buffer.tokens_merge[i].type;
              string = '"' + string + '"';
            } else if (typeFollowed(i) === 'key') {
              buffer.tokens_merge[i].type = 'key';
              type = buffer.tokens_merge[i].type;
            } else if (!followsSymbol(i, ['[', ':', ','])) {
              setError(i, format(locale.invalidToken.tokenSequence.permitted, {
                firstToken: type,
                secondToken: ["[", ":", ","]
              }));
              break;
            }

            if (type !== 'key') if (!buffer2.isValue) {
              buffer.tokens_merge[i].type = 'key';
              type = buffer.tokens_merge[i].type;
              string = '"' + string + '"';
            }
            if (type === 'primitive') if (string === 'undefined') setError(i, format(locale.invalidToken.useInstead, {
              badToken: "undefined",
              goodToken: "null"
            }));
            buffer.json += string;
            break;
        }
      }

      let noEscapedSingleQuote = '';

      for (var i = 0; i < buffer.json.length; i++) {
        let current = buffer.json.charAt(i),
            next = '';

        if (i + 1 < buffer.json.length) {
          next = buffer.json.charAt(i + 1);

          if (current === '\\' && next === "'") {
            noEscapedSingleQuote += next;
            i++;
            continue;
          }
        }

        noEscapedSingleQuote += current;
      }

      buffer.json = noEscapedSingleQuote;

      if (!error) {
        const maxIterations = Math.ceil(bracketList.length / 2);
        let round = 0,
            delta = false;

        function removePair(index) {
          bracketList.splice(index + 1, 1);
          bracketList.splice(index, 1);
          if (!delta) delta = true;
        }

        while (bracketList.length > 0) {
          delta = false;

          for (var tokenCount = 0; tokenCount < bracketList.length - 1; tokenCount++) {
            const pair = bracketList[tokenCount].string + bracketList[tokenCount + 1].string;
            if (['[]', '{}'].indexOf(pair) > -1) removePair(tokenCount);
          }

          round++;
          if (!delta) break;
          if (round >= maxIterations) break;
        }

        if (bracketList.length > 0) {
          const _tokenString = bracketList[0].string,
                _tokenPosition = bracketList[0].i,
                _closingBracketType = _tokenString === '[' ? ']' : '}';

          line = bracketList[0].line;
          setError(_tokenPosition, format(locale.brace[_closingBracketType === ']' ? 'square' : 'curly'].missingClose));
        }
      }

      if (!error) if ([undefined, ''].indexOf(buffer.json) === -1) try {
        buffer.jsObject = JSON.parse(buffer.json);
      } catch (err) {
        const errorMessage = err.message,
              subsMark = errorMessage.indexOf('position');
        if (subsMark === -1) throw new Error('Error parsing failed');
        const errPositionStr = errorMessage.substring(subsMark + 9, errorMessage.length),
              errPosition = parseInt(errPositionStr);
        let charTotal = 0,
            tokenIndex = 0,
            token = false,
            _line = 1,
            exitWhile = false;

        while (charTotal < errPosition && !exitWhile) {
          token = buffer.tokens_merge[tokenIndex];
          if ('linebreak' === token.type) _line++;
          if (['space', 'linebreak'].indexOf(token.type) === -1) charTotal += token.string.length;
          if (charTotal >= errPosition) break;
          tokenIndex++;
          if (!buffer.tokens_merge[tokenIndex + 1]) exitWhile = true;
        }

        line = _line;
        let backslashCount = 0;

        for (let i = 0; i < token.string.length; i++) {
          const char = token.string.charAt(i);
          if (char === '\\') backslashCount = backslashCount > 0 ? backslashCount + 1 : 1;else {
            if (backslashCount % 2 !== 0 || backslashCount === 0) if ('\'"bfnrt'.indexOf(char) === -1) {
              setError(tokenIndex, format(locale.invalidToken.unexpected, {
                token: '\\'
              }));
            }
            backslashCount = 0;
          }
        }

        if (!error) setError(tokenIndex, format(locale.invalidToken.unexpected, {
          token: token.string
        }));
      }
      let _line = 1,
          _depth = 0;

      function newIndent() {
        var space = [];

        for (var i = 0; i < _depth * 2; i++) space.push('&nbsp;');

        return space.join('');
      }

      function newLineBreak(byPass = false) {
        _line++;

        if (_depth > 0 || byPass) {
          return '<br>';
        }

        return '';
      }

      function newLineBreakAndIndent(byPass = false) {
        return newLineBreak(byPass) + newIndent();
      }

      ;
      if (!error) for (var i = 0; i < buffer.tokens_merge.length; i++) {
        const token = buffer.tokens_merge[i],
              string = token.string,
              type = token.type;

        switch (type) {
          case 'space':
          case 'linebreak':
            break;

          case 'string':
          case 'number':
          case 'primitive':
          case 'error':
            buffer.markup += (followsSymbol(i, [',', '[']) ? newLineBreakAndIndent() : '') + newSpan(i, token, _depth);
            break;

          case 'key':
            buffer.markup += newLineBreakAndIndent() + newSpan(i, token, _depth);
            break;

          case 'colon':
            buffer.markup += newSpan(i, token, _depth) + '&nbsp;';
            break;

          case 'symbol':
            switch (string) {
              case '[':
              case '{':
                buffer.markup += (!followsSymbol(i, [':']) ? newLineBreakAndIndent() : '') + newSpan(i, token, _depth);
                _depth++;
                break;

              case ']':
              case '}':
                _depth--;

                const islastToken = i === buffer.tokens_merge.length - 1,
                      _adjustment = i > 0 ? ['[', '{'].indexOf(buffer.tokens_merge[i - 1].string) > -1 ? '' : newLineBreakAndIndent(islastToken) : '';

                buffer.markup += _adjustment + newSpan(i, token, _depth);
                break;

              case ',':
                buffer.markup += newSpan(i, token, _depth);
                break;
            }

            break;
        }
      }

      if (error) {
        let _line_fallback = 1;

        function countCarrigeReturn(string) {
          let count = 0;

          for (var i = 0; i < string.length; i++) {
            if (['\n', '\r'].indexOf(string[i]) > -1) count++;
          }

          return count;
        }

        _line = 1;

        for (var i = 0; i < buffer.tokens_merge.length; i++) {
          const token = buffer.tokens_merge[i],
                type = token.type,
                string = token.string;
          if (type === 'linebreak') _line++;
          buffer.markup += newSpan(i, token, _depth);
          _line_fallback += countCarrigeReturn(string);
        }

        _line++;
        _line_fallback++;
        if (_line < _line_fallback) _line = _line_fallback;
      }

      for (var i = 0; i < buffer.tokens_merge.length; i++) {
        let token = buffer.tokens_merge[i];
        buffer.indented += token.string;
        if (['space', 'linebreak'].indexOf(token.type) === -1) buffer.tokens_plainText += token.string;
      }

      if (error) {
        function isFunction(functionToCheck) {
          return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
        }

        if ('modifyErrorText' in this.props) if (isFunction(this.props.modifyErrorText)) error.reason = this.props.modifyErrorText(error.reason);
      }

      return {
        tokens: buffer.tokens_merge,
        noSpaces: buffer.tokens_plainText,
        indented: buffer.indented,
        json: buffer.json,
        jsObject: buffer.jsObject,
        markup: buffer.markup,
        lines: _line,
        error: error
      };
    }

    ;
    /**
     *     JS OBJECTS || PLACEHOLDER
     */

    if (!('nodeType' in something)) {
      let buffer = {
        inputText: JSON.stringify(something),
        position: 0,
        currentChar: '',
        tokenPrimary: '',
        tokenSecondary: '',
        brackets: [],
        isValue: false,
        stringOpen: false,
        stringStart: 0,
        tokens: []
      };

      function escape_character() {
        if (buffer.currentChar !== '\\') return false;
        buffer.inputText = deleteCharAt(buffer.inputText, buffer.position);
        return true;
      }

      function deleteCharAt(string, position) {
        return string.slice(0, position) + string.slice(position + 1);
      }

      function determine_string() {
        if ('\'"'.indexOf(buffer.currentChar) === -1) return false;

        if (!buffer.stringOpen) {
          add_tokenSecondary();
          buffer.stringStart = buffer.position;
          buffer.stringOpen = buffer.currentChar;
          return true;
        }

        if (buffer.stringOpen === buffer.currentChar) {
          add_tokenSecondary();
          const stringToken = buffer.inputText.substring(buffer.stringStart, buffer.position + 1);
          add_tokenPrimary(stringToken);
          buffer.stringOpen = false;
          return true;
        }

        return false;
      }

      function determine_value() {
        if (':,{}[]'.indexOf(buffer.currentChar) === -1) return false;
        if (buffer.stringOpen) return false;
        add_tokenSecondary();
        add_tokenPrimary(buffer.currentChar);

        switch (buffer.currentChar) {
          case ':':
            buffer.isValue = true;
            return true;
            break;

          case '{':
          case '[':
            buffer.brackets.push(buffer.currentChar);
            break;

          case '}':
          case ']':
            buffer.brackets.pop();
            break;
        }

        if (buffer.currentChar !== ':') buffer.isValue = buffer.brackets[buffer.brackets.length - 1] === '[';
        return true;
      }

      function add_tokenSecondary() {
        if (buffer.tokenSecondary.length === 0) return false;
        buffer.tokens.push(buffer.tokenSecondary);
        buffer.tokenSecondary = '';
        return true;
      }

      function add_tokenPrimary(value) {
        if (value.length === 0) return false;
        buffer.tokens.push(value);
        return true;
      }

      for (var i = 0; i < buffer.inputText.length; i++) {
        buffer.position = i;
        buffer.currentChar = buffer.inputText.charAt(buffer.position);
        const a = determine_value(),
              b = determine_string(),
              c = escape_character();
        if (!a && !b && !c) if (!buffer.stringOpen) buffer.tokenSecondary += buffer.currentChar;
      }

      let buffer2 = {
        brackets: [],
        isValue: false,
        tokens: []
      };
      buffer2.tokens = buffer.tokens.map(token => {
        let type = '',
            string = '',
            value = '';

        switch (token) {
          case ',':
            type = 'symbol';
            string = token;
            value = token;
            buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
            break;

          case ':':
            type = 'symbol';
            string = token;
            value = token;
            buffer2.isValue = true;
            break;

          case '{':
          case '[':
            type = 'symbol';
            string = token;
            value = token;
            buffer2.brackets.push(token);
            buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
            break;

          case '}':
          case ']':
            type = 'symbol';
            string = token;
            value = token;
            buffer2.brackets.pop();
            buffer2.isValue = buffer2.brackets[buffer2.brackets.length - 1] === '[';
            break;

          case 'undefined':
            type = 'primitive';
            string = token;
            value = undefined;
            break;

          case 'null':
            type = 'primitive';
            string = token;
            value = null;
            break;

          case 'false':
            type = 'primitive';
            string = token;
            value = false;
            break;

          case 'true':
            type = 'primitive';
            string = token;
            value = true;
            break;

          default:
            const C = token.charAt(0);

            function stripQuotesFromKey(text) {
              if (text.length === 0) return text;
              if (['""', "''"].indexOf(text) > -1) return "''";
              let wrappedInQuotes = false;

              for (var i = 0; i < 2; i++) {
                if ([text.charAt(0), text.charAt(text.length - 1)].indexOf(['"', "'"][i]) > -1) {
                  wrappedInQuotes = true;
                  break;
                }
              }

              if (wrappedInQuotes && text.length >= 2) text = text.slice(1, -1);

              const nonAlphaNumeric = text.replace(/\w/g, ''),
                    alphaNumeric = text.replace(/\W+/g, ''),
                    mayRemoveQuotes = ((nonAlphaNumeric, text) => {
                let numberAndLetter = false;

                for (var i = 0; i < text.length; i++) {
                  if (i === 0) if (isNaN(text.charAt(i))) break;

                  if (isNaN(text.charAt(i))) {
                    numberAndLetter = true;
                    break;
                  }
                }

                return !(nonAlphaNumeric.length > 0 || numberAndLetter);
              })(nonAlphaNumeric, text),
                    hasQuotes = (string => {
                for (var i = 0; i < string.length; i++) {
                  if (["'", '"'].indexOf(string.charAt(i)) > -1) return true;
                }

                return false;
              })(nonAlphaNumeric);

              if (hasQuotes) {
                let newText = '';
                const charList = text.split('');

                for (var ii = 0; ii < charList.length; ii++) {
                  let char = charList[ii];
                  if (["'", '"'].indexOf(char) > -1) char = '\\' + char;
                  newText += char;
                }

                text = newText;
              }

              if (!mayRemoveQuotes) return "'" + text + "'";else return text;
            }

            if ('\'"'.indexOf(C) > -1) {
              if (buffer2.isValue) type = 'string';else type = 'key';
              if (type === 'key') string = stripQuotesFromKey(token);

              if (type === 'string') {
                string = '';
                const charList2 = token.slice(1, -1).split('');

                for (var ii = 0; ii < charList2.length; ii++) {
                  let char = charList2[ii];
                  if ('\'\"'.indexOf(char) > -1) char = '\\' + char;
                  string += char;
                }

                string = "'" + string + "'";
              }

              value = string;
              break;
            }

            if (!isNaN(token)) {
              type = 'number';
              string = token;
              value = Number(token);
              break;
            }

            if (token.length > 0) if (!buffer2.isValue) {
              type = 'key';
              string = token;
              if (string.indexOf(' ') > -1) string = "'" + string + "'";
              value = string;
              break;
            }
        }

        return {
          type: type,
          string: string,
          value: value,
          depth: buffer2.brackets.length
        };
      });
      let clean = '';

      for (var i = 0; i < buffer2.tokens.length; i++) {
        let token = buffer2.tokens[i];
        clean += token.string;
      }

      function indent(number) {
        var space = [];

        for (var i = 0; i < number * 2; i++) space.push(' ');

        return (number > 0 ? '\n' : '') + space.join('');
      }

      ;
      let indentation = '';

      for (var i = 0; i < buffer2.tokens.length; i++) {
        let token = buffer2.tokens[i];

        switch (token.string) {
          case '[':
          case '{':
            const nextToken = i < buffer2.tokens.length - 1 - 1 ? buffer2.tokens[i + 1] : '';
            if ('}]'.indexOf(nextToken.string) === -1) indentation += token.string + indent(token.depth);else indentation += token.string;
            break;

          case ']':
          case '}':
            const prevToken = i > 0 ? buffer2.tokens[i - 1] : '';
            if ('[{'.indexOf(prevToken.string) === -1) indentation += indent(token.depth) + token.string;else indentation += token.string;
            break;

          case ':':
            indentation += token.string + ' ';
            break;

          case ',':
            indentation += token.string + indent(token.depth);
            break;

          default:
            indentation += token.string;
            break;
        }
      }

      let lines = 1;

      function indentII(number) {
        var space = [];
        if (number > 0) lines++;

        for (var i = 0; i < number * 2; i++) space.push('&nbsp;');

        return (number > 0 ? '<br>' : '') + space.join('');
      }

      ;
      let markup = '';
      const lastIndex = buffer2.tokens.length - 1;

      for (var i = 0; i < buffer2.tokens.length; i++) {
        let token = buffer2.tokens[i];
        let span = newSpan(i, token, token.depth);

        switch (token.string) {
          case '{':
          case '[':
            const nextToken = i < buffer2.tokens.length - 1 - 1 ? buffer2.tokens[i + 1] : '';
            if ('}]'.indexOf(nextToken.string) === -1) markup += span + indentII(token.depth);else markup += span;
            break;

          case '}':
          case ']':
            const prevToken = i > 0 ? buffer2.tokens[i - 1] : '';
            if ('[{'.indexOf(prevToken.string) === -1) markup += indentII(token.depth) + (lastIndex === i ? '<br>' : '') + span;else markup += span;
            break;

          case ':':
            markup += span + ' ';
            break;

          case ',':
            markup += span + indentII(token.depth);
            break;

          default:
            markup += span;
            break;
        }
      }

      lines += 2;
      return {
        tokens: buffer2.tokens,
        noSpaces: clean,
        indented: indentation,
        json: JSON.stringify(something),
        jsObject: something,
        markup: markup,
        lines: lines
      };
    }
  }

}

export default JSONInput;