UNPKG

react-json-editor-ajrm-ts-platform

Version:

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

1,035 lines (1,034 loc) 94.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); // @ts-nocheck const react_1 = tslib_1.__importStar(require("react")); const themes_1 = tslib_1.__importDefault(require("./themes")); const mitsuketa_1 = require("./mitsuketa"); const err_1 = tslib_1.__importDefault(require("./err")); const locale_1 = require("./locale"); const en_1 = tslib_1.__importDefault(require("./locale/en")); class JSONInput extends react_1.Component { constructor(props) { super(props); this.mappedKeys = new Set(); 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: 0, error: false, isLoading: false, }; if (!this.props.locale) { console.warn("[react-json-editor-ajrm - 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_1.default.dark_vscode_tribute; if ("theme" in this.props) if (typeof this.props.theme === "string") if (this.props.theme in themes_1.default) theme = themes_1.default[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.waitAfterKeyPress = "waitAfterKeyPress" in this.props ? this.props.waitAfterKeyPress : 1000; this.updateTime = false; this.resetConfiguration = "reset" in this.props ? this.props.reset : false; } render() { const id = this.props.id, markupText = this.state.markupText, error = this.props.error || this.state.error, colors = this.colors, style = this.style || {}, confirmGood = this.confirmGood, totalHeight = this.totalHeight, totalWidth = this.totalWidth, isLoading = this.state.isLoading, hasError = !!this.props.error || (error ? "token" in error : false); this.renderCount++; return (react_1.default.createElement("div", { id: id && id + "-outer-box", style: Object.assign({ display: "block", overflow: "none", height: totalHeight, width: totalWidth, margin: 0, boxSizing: "border-box", position: "relative" }, style.outerBox) }, confirmGood ? (react_1.default.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)", } }, isLoading ? react_1.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", width: "30px", height: "30px", viewBox: "0 0 30 30", version: "1.1" }, react_1.default.createElement("g", { id: "surface1" }, react_1.default.createElement("path", { d: "M 8.863281 15 C 8.863281 14.246094 8.253906 13.636719 7.5 13.636719 L 2.046875 13.636719 C 1.292969 13.636719 0.683594 14.246094 0.683594 15 C 0.683594 15.753906 1.292969 16.363281 2.046875 16.363281 L 7.5 16.363281 C 8.253906 16.363281 8.863281 15.753906 8.863281 15 Z M 8.863281 15 " }), react_1.default.createElement("path", { d: "M 27.953125 13.636719 L 25.226562 13.636719 C 24.472656 13.636719 23.863281 14.246094 23.863281 15 C 23.863281 15.753906 24.472656 16.363281 25.226562 16.363281 L 27.953125 16.363281 C 28.707031 16.363281 29.316406 15.753906 29.316406 15 C 29.316406 14.246094 28.707031 13.636719 27.953125 13.636719 Z M 27.953125 13.636719 " }), react_1.default.createElement("path", { d: "M 15.683594 8.183594 C 16.433594 8.183594 17.046875 7.570312 17.046875 6.816406 L 17.046875 1.363281 C 17.046875 0.609375 16.433594 0 15.683594 0 C 14.929688 0 14.316406 0.609375 14.316406 1.363281 L 14.316406 6.816406 C 14.316406 7.570312 14.929688 8.183594 15.683594 8.183594 Z M 15.683594 8.183594 " }), react_1.default.createElement("path", { d: "M 15.683594 21.816406 C 14.929688 21.816406 14.316406 22.429688 14.316406 23.183594 L 14.316406 28.636719 C 14.316406 29.390625 14.929688 30 15.683594 30 C 16.433594 30 17.046875 29.390625 17.046875 28.636719 L 17.046875 23.183594 C 17.046875 22.429688 16.433594 21.816406 15.683594 21.816406 Z M 15.683594 21.816406 " }), react_1.default.createElement("path", { d: "M 7.003906 4.394531 C 6.472656 3.859375 5.609375 3.859375 5.074219 4.394531 C 4.542969 4.925781 4.542969 5.789062 5.074219 6.320312 L 8.933594 10.179688 C 9.199219 10.445312 9.546875 10.578125 9.894531 10.578125 C 10.246094 10.578125 10.59375 10.445312 10.859375 10.179688 C 11.394531 9.644531 11.394531 8.78125 10.859375 8.25 Z M 7.003906 4.394531 " }), react_1.default.createElement("path", { d: "M 22.429688 19.820312 C 21.898438 19.289062 21.035156 19.289062 20.503906 19.820312 C 19.96875 20.355469 19.96875 21.21875 20.503906 21.75 L 24.359375 25.605469 C 24.625 25.871094 24.976562 26.007812 25.324219 26.007812 C 25.671875 26.007812 26.023438 25.871094 26.289062 25.605469 C 26.820312 25.074219 26.820312 24.210938 26.289062 23.679688 Z M 22.429688 19.820312 " }), react_1.default.createElement("path", { d: "M 8.933594 19.820312 L 5.074219 23.679688 C 4.542969 24.210938 4.542969 25.074219 5.074219 25.605469 C 5.339844 25.871094 5.691406 26.007812 6.039062 26.007812 C 6.386719 26.007812 6.738281 25.871094 7.003906 25.605469 L 10.859375 21.75 C 11.394531 21.21875 11.394531 20.355469 10.859375 19.820312 C 10.328125 19.289062 9.464844 19.289062 8.933594 19.820312 Z M 8.933594 19.820312 " }))) : react_1.default.createElement("svg", { height: "30px", width: "30px", viewBox: "0 0 100 100" }, react_1.default.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_1.default.createElement("div", { id: id && id + "-container", style: Object.assign({ display: "block", height: totalHeight, width: totalWidth, margin: 0, boxSizing: "border-box", overflow: "hidden", fontFamily: "Roboto, sans-serif" }, style.container), onClick: this.onClick }, react_1.default.createElement("div", { id: id && id + "-warning-box", style: Object.assign({ 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_1.default.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_1.default.createElement("div", { style: { position: "relative", top: 0, left: 0, height: "60px", width: "60px", margin: 0, pointerEvents: "none", }, onClick: this.onClick }, react_1.default.createElement("div", { style: { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", pointerEvents: "none", }, onClick: this.onClick }, react_1.default.createElement("svg", { height: "25px", width: "25px", viewBox: "0 0 100 100" }, react_1.default.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_1.default.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_1.default.createElement("div", { id: id && id + "-body", style: Object.assign({ 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_1.default.createElement("span", { id: id && id + "-labels", ref: (ref) => (this.refLabels = ref), style: Object.assign({ 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_1.default.createElement("span", { id: id, ref: (ref) => (this.refContent = ref), contentEditable: true, style: Object.assign({ 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, // @ts-ignore autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false }))))); } renderErrorMessage() { const locale = this.props.locale || en_1.default, error = this.props.error || this.state.error, style = this.style; if (!error) return void 0; return (react_1.default.createElement("p", { style: Object.assign({ 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) }, (0, locale_1.format)(locale.format, error))); } renderLabels() { const colors = this.colors, style = this.style, error = this.props.error || this.state.error, errorLine = error ? error.line : -1, lines = this.state.lines ? this.state.lines : 1; let labels = new Array(lines); for (let i = 0; i < lines - 1; i++) labels[i] = i + 1; return labels.map((number) => { const color = number !== errorLine ? colors.default : "red"; return (react_1.default.createElement("div", { key: number, style: Object.assign(Object.assign({}, 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 (let 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) { const jsObject = this.decodePlaceholder(data.jsObject); this.props.onChange({ plainText: data.indented, markupText: data.markup, json: data.json, jsObject, lines: data.lines, error: data.error, }); } let cursorPosition = this.getCursorPosition(data.error) + cursorOffset; this.updateTime = false; this.setState({ plainText: data.indented, markupText: data.markup, json: data.json, jsObject: data.jsObject, lines: data.lines, error: data.error, isLoading: false, }); if (updateCursorPosition) this.setCursorPosition(cursorPosition); } scheduledUpdate() { if ("onKeyPressUpdate" in this.props) if (this.props.onKeyPressUpdate === false) return; const { updateTime } = this; if (updateTime === false || 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; this.setState({ isLoading: true }); } 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) { var _a; if (this.props.viewOnly) { this.stopEvent(event); } else { event.preventDefault(); let text = (_a = event.clipboardData.getData("text/plain")) === null || _a === void 0 ? void 0 : _a.replace(/(\r\n|\n|\r|\t|\u200B)/gm, ''); 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; const container = this.refContent, data = this.tokenize(container); if ("onBlur" in this.props) { const jsObject = this.decodePlaceholder(data.jsObject); this.props.onBlur({ plainText: data.indented, markupText: data.markup, json: data.json, jsObject, lines: data.lines, error: data.error, }); } } onScroll(event) { this.refLabels.scrollTop = event.target.scrollTop; } componentDidUpdate() { if (!this.state.isLoading) { this.updateInternalProps(); this.showPlaceholder(); } } componentDidMount() { this.showPlaceholder(); } componentWillUnmount() { if (this.timer) clearInterval(this.timer); this.mappedKeys.clear(); } isArray(value) { return Array.isArray(value); } isObject(value) { return typeof value === 'object' && !this.isArray(value) && value !== null; } isString(value) { return typeof value === 'string' || value instanceof String; } encodePlaceholder(placeholder) { try { if (!this.isObject(placeholder)) return; for (const [key, value] of Object.entries(placeholder)) { const isArray = this.isArray(value); if (!this.isString(value) && !isArray) continue; if (isArray) { value.forEach((val, index) => { try { placeholder[key][index] = JSON.parse(val); this.mappedKeys.add({ key, type: "array" }); } catch (e) { placeholder[key][index] = val; } }); } else { try { placeholder[key] = JSON.parse(value); this.mappedKeys.add({ key }); } catch (e) { placeholder[key] = value; } } } } catch (e) { console.error(e); } } decodePlaceholder(placeholder) { if (!placeholder) return null; const decodedPlaceholder = structuredClone(placeholder); Array.from(this.mappedKeys).forEach(item => { const value = placeholder[item.key]; if (item.type === "array") { value.forEach((val, index) => { try { decodedPlaceholder[item.key][index] = JSON.stringify(val); } catch (e) { decodedPlaceholder[item.key][index] = val; } }); } else { try { decodedPlaceholder[item.key] = JSON.stringify(value); } catch (e) { console.error(e); } } }); return decodedPlaceholder; } showPlaceholder() { const placeholderDoesNotExist = !("placeholder" in this.props); if (placeholderDoesNotExist) return; const { placeholder } = this.props; const placeholderHasEmptyValues = [undefined, null].indexOf(placeholder) > -1; if (placeholderHasEmptyValues) return; this.encodePlaceholder(placeholder); const { prevPlaceholder, jsObject } = this.state; const { resetConfiguration } = this; const placeholderDataType = (0, mitsuketa_1.getType)(placeholder); const unexpectedDataType = ["object", "array"].indexOf(placeholderDataType) === -1; if (unexpectedDataType) err_1.default.throwError("showPlaceholder", "placeholder", "either an object or an array"); const samePlaceholderValues = (0, mitsuketa_1.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 = !(0, mitsuketa_1.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, isLoading: false, }); } tokenize(something) { var _a, _b; if (typeof something !== "object") return console.error("tokenize() expects object type properties only. Got '" + typeof something + "' type instead."); const locale = this.props.locale || en_1.default; 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 (let 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.textContent, 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 (let 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 (let 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 (let 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 (let 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 (let i = 0; i < nonAlphanumeric.length; i++) { const nonAlpha = nonAlphanumeric.charAt(i); if (string.indexOf(nonAlpha) > -1) return false; } } break; case "number": for (let 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 (let 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 (let 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 (let 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 (let 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 (let 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 (let 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);