refactor parser

This commit is contained in:
AykutSarac 2022-12-31 15:24:27 +03:00
parent e488ff524c
commit 5914ae94eb
7 changed files with 400 additions and 358 deletions

View File

@ -3,7 +3,7 @@ import { CanvasDirection } from "reaflow";
import { Graph } from "src/components/Graph";
import { getChildrenEdges } from "src/utils/getChildrenEdges";
import { getOutgoers } from "src/utils/getOutgoers";
import { parser } from "src/utils/jsonParser";
import { parser } from "src/utils/core/jsonParser";
import create from "zustand";
import useJson from "./useJson";

View File

@ -0,0 +1,11 @@
import { Graph } from "./jsonParser";
export const addEdgeToGraph = (graph: Graph, from: string, to: string) => {
graph.edges = graph.edges.concat([
{
id: `e${from}-${to}`,
from: from,
to: to,
},
]);
};

View File

@ -0,0 +1,27 @@
import { Graph } from "./jsonParser";
export const addNodeToGraph = (
graph: Graph,
text: any,
width: number,
height: number,
parent: "string" | "number" | "boolean" | "object" | "array" | "null" | false,
isEmpty?: boolean
) => {
let actualId = String(graph.nodes.length + 1);
graph.nodes = graph.nodes.concat([
{
id: actualId,
text: text,
width: width,
height: height,
data: {
parent: parent === "array" || parent === "object" ? parent : false,
childrenCount: parent ? 1 : 0,
isEmpty: isEmpty,
},
},
]);
return actualId;
};

View File

@ -0,0 +1,51 @@
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
export const calculateNodeSize = (text: string | [string, string][], isParent = false) => {
// Get the current state of the application
const isFolded = useGraph.getState().foldNodes;
const isImagePreview = useStored.getState().imagePreview;
// Initialize variables
let lineCounts = 1;
let lineLengths: number[] = [];
// Calculate the number of lines and the length of each line
if (typeof text === "string") {
lineLengths.push(text.length);
} else {
lineCounts = text.map(([k, v]) => {
const length = `${k}: ${v}`.length;
const line = length > 150 ? 150 : length;
lineLengths.push(line);
return `${k}: ${v}`;
}).length;
}
// Get the longest line
const longestLine = Math.max(...lineLengths);
// Calculate the width of the node
const getWidth = () => {
if (text.length === 0) return 35;
if (Array.isArray(text) && !text.length) return 40;
if (!isFolded) return 35 + longestLine * 7.8 + (isParent ? 60 : 0);
if (isParent && isFolded) return 170;
return 200;
};
// Calculate the height of the node
const getHeight = () => {
if (lineCounts * 17.8 < 30) return 40;
return (lineCounts + 1) * 18;
};
// Check if text matches URL pattern
const isImage =
!Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && isImagePreview;
return {
width: isImage ? 80 : getWidth(),
height: isImage ? 80 : getHeight(),
};
};

View File

@ -0,0 +1,84 @@
import { parseTree } from "jsonc-parser";
import { addEdgeToGraph } from "./addEdgeToGraph";
import { addNodeToGraph } from "./addNodeToGraph";
import { calculateNodeSize } from "./calculateNodeSize";
import { traverse } from "./traverse";
export type Graph = {
nodes: NodeData[];
edges: EdgeData[];
};
export type States = {
parentName: string;
bracketOpen: { id: string; type: string }[];
objectsFromArray: number[];
objectsFromArrayId: number;
notHaveParent: string[];
brothersNode: [string, string][];
brothersParentId: string | undefined;
brotherKey: string;
brothersNodeProps: {
id: string;
parentId: string | undefined;
objectsFromArrayId: number | undefined;
}[];
};
export const parser = (jsonStr: string) => {
try {
let json = parseTree(jsonStr);
const graph: Graph = {
nodes: [],
edges: [],
};
const states: States = {
parentName: "",
bracketOpen: [],
objectsFromArray: [],
objectsFromArrayId: 0,
notHaveParent: [],
brothersNode: [],
brothersParentId: undefined,
brotherKey: "",
brothersNodeProps: [],
};
if (json) {
traverse(graph, states, json);
if (states.notHaveParent.length > 1) {
if (json.type !== "array") {
const text = "";
const { width, height } = calculateNodeSize(text, false);
const emptyId = addNodeToGraph(graph, text, width, height, false, true);
states.notHaveParent.forEach(children => {
addEdgeToGraph(graph, emptyId, children);
});
}
}
if (graph.nodes.length === 0) {
if (json.type === "array") {
const text = "[]";
const { width, height } = calculateNodeSize(text, false);
addNodeToGraph(graph, text, width, height, false);
} else {
const text = "{}";
const { width, height } = calculateNodeSize(text, false);
addNodeToGraph(graph, text, width, height, false);
}
}
}
return graph;
} catch (error) {
console.error(error);
return {
nodes: [],
edges: [],
};
}
};

226
src/utils/core/traverse.ts Normal file
View File

@ -0,0 +1,226 @@
import { Node, NodeType } from "jsonc-parser";
import { addEdgeToGraph } from "./addEdgeToGraph";
import { addNodeToGraph } from "./addNodeToGraph";
import { calculateNodeSize } from "./calculateNodeSize";
import { Graph, States } from "./jsonParser";
const isPrimitiveOrNullType = (type?: NodeType) => {
return type === "boolean" || type === "string" || type === "number" || type === "null";
};
const alignChildren = (a: Node, b: Node) => {
if (
isPrimitiveOrNullType(a?.children?.[1]?.type) &&
!isPrimitiveOrNullType(b?.children?.[1]?.type)
) {
return -1;
}
return 0;
};
export const traverse = (
graph: Graph,
states: States,
objectToTraverse: Node,
parentType?: string,
myParentId?: string,
nextType?: string
) => {
let { type, children, value } = objectToTraverse;
if (!children) {
if (value !== undefined) {
if (parentType === "property" && nextType !== "object" && nextType !== "array") {
states.brothersParentId = myParentId;
if (nextType === undefined) {
// add key and value to brothers node
states.brothersNode = [...states.brothersNode, [states.brotherKey, value]];
} else {
states.brotherKey = value;
}
} else if (parentType === "array") {
const { width, height } = calculateNodeSize(String(value), false);
const nodeFromArrayId = addNodeToGraph(graph, String(value), width, height, false);
if (myParentId) {
addEdgeToGraph(graph, myParentId, nodeFromArrayId);
}
}
if (nextType && parentType !== "array") {
if (nextType === "object" || nextType === "array") {
states.parentName = value;
}
}
}
} else if (children) {
let parentId: string | undefined;
if (type !== "property" && states.parentName !== "") {
// add last brothers node and add parent node
if (states.brothersNode.length > 0) {
// add or concat brothers node of same parent
let findBrothersNode = states.brothersNodeProps.find(
e =>
e.parentId === states.brothersParentId &&
e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...graph.nodes];
let findNode = graph.nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(states.brothersNode);
const { width, height } = calculateNodeSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
graph.nodes = [...ModifyNodes];
states.brothersNode = [];
}
} else {
const { width, height } = calculateNodeSize(states.brothersNode, false);
const brothersNodeId = addNodeToGraph(graph, states.brothersNode, width, height, false);
states.brothersNode = [];
if (states.brothersParentId) {
addEdgeToGraph(graph, states.brothersParentId, brothersNodeId);
} else {
states.notHaveParent = [...states.notHaveParent, brothersNodeId];
}
states.brothersNodeProps = [
...states.brothersNodeProps,
{
id: brothersNodeId,
parentId: states.brothersParentId,
objectsFromArrayId: states.objectsFromArray[states.objectsFromArray.length - 1],
},
];
}
}
// add parent node
const { width, height } = calculateNodeSize(states.parentName, true);
parentId = addNodeToGraph(graph, states.parentName, width, height, type);
states.bracketOpen = [...states.bracketOpen, { id: parentId, type: type }];
states.parentName = "";
// add edges from parent node
let brothersProps = states.brothersNodeProps.filter(
e =>
e.parentId === myParentId &&
e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1]
);
if (
(brothersProps.length > 0 &&
states.bracketOpen[states.bracketOpen.length - 2] &&
states.bracketOpen[states.bracketOpen.length - 2].type !== "object") ||
(brothersProps.length > 0 && states.bracketOpen.length === 1)
) {
addEdgeToGraph(graph, brothersProps[brothersProps.length - 1].id, parentId);
} else if (myParentId) {
addEdgeToGraph(graph, myParentId, parentId);
} else {
states.notHaveParent = [...states.notHaveParent, parentId];
}
} else if (parentType === "array") {
states.objectsFromArray = [...states.objectsFromArray, states.objectsFromArrayId++];
}
(type === "object" ? children.sort(alignChildren) : children).forEach(
(branch, index, array) => {
if (array[index + 1]) {
traverse(
graph,
states,
branch,
type,
states.bracketOpen[states.bracketOpen.length - 1]
? states.bracketOpen[states.bracketOpen.length - 1].id
: undefined,
array[index + 1].type
);
} else {
traverse(
graph,
states,
branch,
type,
states.bracketOpen[states.bracketOpen.length - 1]
? states.bracketOpen[states.bracketOpen.length - 1].id
: undefined
);
}
}
);
if (type !== "property") {
// when children end
// add or concat brothers node when it is the last parent node
if (states.brothersNode.length > 0) {
let findBrothersNode = states.brothersNodeProps.find(
e =>
e.parentId === states.brothersParentId &&
e.objectsFromArrayId === states.objectsFromArray[states.objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...graph.nodes];
let findNode = graph.nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(states.brothersNode);
const { width, height } = calculateNodeSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
graph.nodes = [...ModifyNodes];
states.brothersNode = [];
}
} else {
const { width, height } = calculateNodeSize(states.brothersNode, false);
const brothersNodeId = addNodeToGraph(graph, states.brothersNode, width, height, false);
states.brothersNode = [];
if (states.brothersParentId) {
addEdgeToGraph(graph, states.brothersParentId, brothersNodeId);
} else {
states.notHaveParent = [...states.notHaveParent, brothersNodeId];
}
states.brothersNodeProps = [
...states.brothersNodeProps,
{
id: brothersNodeId,
parentId: states.brothersParentId,
objectsFromArrayId: states.objectsFromArray[states.objectsFromArray.length - 1],
},
];
}
}
// close brackets
if (parentType !== "array") {
if (states.bracketOpen.length > 0) {
let newBracketOpen = [...states.bracketOpen];
newBracketOpen.splice(newBracketOpen.length - 1);
states.bracketOpen = [...newBracketOpen];
}
} else if (parentType === "array") {
if (states.objectsFromArray.length > 0) {
let newobjectsFromArray = [...states.objectsFromArray];
newobjectsFromArray.splice(newobjectsFromArray.length - 1);
states.objectsFromArray = [...newobjectsFromArray];
}
}
if (parentId) {
let myChildrens = graph.edges.filter(e => e.from === parentId);
let myIndex = graph.nodes.findIndex(e => e.id === parentId);
let ModifyNodes = [...graph.nodes];
if (ModifyNodes[myIndex]) {
ModifyNodes[myIndex].data.childrenCount = myChildrens.length;
graph.nodes = [...ModifyNodes];
}
}
}
}
};

View File

@ -1,357 +0,0 @@
import { Node, NodeType, parseTree } from "jsonc-parser";
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
const calculateSize = (text: string | [string, string][], isParent = false) => {
const isFolded = useGraph.getState().foldNodes;
const isImagePreview = useStored.getState().imagePreview;
let lineCounts = 1;
let lineLengths: number[] = [];
if (typeof text === "string") {
lineLengths.push(text.length);
} else {
lineCounts = text.map(([k, v]) => {
const length = `${k}: ${v}`.length;
const line = length > 150 ? 150 : length;
lineLengths.push(line);
return `${k}: ${v}`;
}).length;
}
const longestLine = Math.max(...lineLengths);
const getWidth = () => {
if (text.length === 0) return 35;
if (Array.isArray(text) && !text.length) return 40;
if (!isFolded) return 35 + longestLine * 7.8 + (isParent ? 60 : 0);
if (isParent && isFolded) return 170;
return 200;
};
const getHeight = () => {
if (lineCounts * 17.8 < 30) return 40;
return (lineCounts + 1) * 18;
};
const isImage =
!Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && isImagePreview;
return {
width: isImage ? 80 : getWidth(),
height: isImage ? 80 : getHeight(),
};
};
export const parser = (jsonStr: string) => {
try {
let json = parseTree(jsonStr);
let nodes: NodeData[] = [];
let edges: EdgeData[] = [];
const addNodes = (
text: any,
width: number,
height: number,
parent: "string" | "number" | "boolean" | "object" | "array" | "null" | false,
isEmpty?: boolean
) => {
let actualId = String(nodes.length + 1);
nodes = [
...nodes,
{
id: actualId,
text: text,
width: width,
height: height,
data: {
parent: parent === "array" || parent === "object" ? parent : false,
childrenCount: parent ? 1 : 0,
isEmpty: isEmpty,
},
},
];
return actualId;
};
const addEdges = (from: string, to: string) => {
edges = [
...edges,
{
id: `e${from}-${to}`,
from: from,
to: to,
},
];
};
const isPrimitiveOrNullType = (type?: NodeType) => {
return (
type === "boolean" ||
type === "string" ||
type === "number" ||
type === "null"
);
};
const alignChildren = (a: Node, b: Node) => {
if (
isPrimitiveOrNullType(a?.children?.[1]?.type) &&
!isPrimitiveOrNullType(b?.children?.[1]?.type)
) {
return -1;
}
return 0;
};
let parentName: string = "";
let bracketOpen: { id: string; type: string }[] = [];
let objectsFromArray: number[] = [];
let objectsFromArrayId = 0;
let notHaveParent: string[] = [];
let brothersNode: [string, string][] = [];
let brothersParentId: string | undefined = "";
let brotherKey: string = "";
let brothersNodeProps: {
id: string;
parentId: string | undefined;
objectsFromArrayId: number | undefined;
}[] = [];
const traverse = (
objectToTraverse: Node,
parentType?: string,
myParentId?: string,
nextType?: string
) => {
let { type, children, value } = objectToTraverse;
if (!children) {
if (value !== undefined) {
if (parentType === "property" && nextType !== "object" && nextType !== "array") {
brothersParentId = myParentId;
if (nextType === undefined) {
// add key and value to brothers node
brothersNode = [...brothersNode, [brotherKey, value]];
} else {
brotherKey = value;
}
} else if (parentType === "array") {
const { width, height } = calculateSize(String(value), false);
const nodeFromArrayId = addNodes(String(value), width, height, false);
if (myParentId) {
addEdges(myParentId, nodeFromArrayId);
}
}
if (nextType && parentType !== "array") {
if (nextType === "object" || nextType === "array") {
parentName = value;
}
}
}
} else if (children) {
let parentId: string | undefined;
if (type !== "property" && parentName !== "") {
// add last brothers node and add parent node
if (brothersNode.length > 0) {
// add or concat brothers node of same parent
let findBrothersNode = brothersNodeProps.find(
e =>
e.parentId === brothersParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...nodes];
let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
nodes = [...ModifyNodes];
brothersNode = [];
}
} else {
const { width, height } = calculateSize(brothersNode, false);
const brothersNodeId = addNodes(brothersNode, width, height, false);
brothersNode = [];
if (brothersParentId) {
addEdges(brothersParentId, brothersNodeId);
} else {
notHaveParent = [...notHaveParent, brothersNodeId];
}
brothersNodeProps = [
...brothersNodeProps,
{
id: brothersNodeId,
parentId: brothersParentId,
objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
},
];
}
}
// add parent node
const { width, height } = calculateSize(parentName, true);
parentId = addNodes(parentName, width, height, type);
bracketOpen = [...bracketOpen, { id: parentId, type: type }];
parentName = "";
// add edges from parent node
let brothersProps = brothersNodeProps.filter(
e =>
e.parentId === myParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (
(brothersProps.length > 0 &&
bracketOpen[bracketOpen.length - 2] &&
bracketOpen[bracketOpen.length - 2].type !== "object") ||
(brothersProps.length > 0 && bracketOpen.length === 1)
) {
addEdges(brothersProps[brothersProps.length - 1].id, parentId);
} else if (myParentId) {
addEdges(myParentId, parentId);
} else {
notHaveParent = [...notHaveParent, parentId];
}
} else if (parentType === "array") {
objectsFromArray = [...objectsFromArray, objectsFromArrayId++];
}
(type === "object" ? children.sort(alignChildren) : children).forEach(
(branch, index, array) => {
if (array[index + 1]) {
traverse(
branch,
type,
bracketOpen[bracketOpen.length - 1]
? bracketOpen[bracketOpen.length - 1].id
: undefined,
array[index + 1].type
);
} else {
traverse(
branch,
type,
bracketOpen[bracketOpen.length - 1]
? bracketOpen[bracketOpen.length - 1].id
: undefined
);
}
}
);
if (type !== "property") {
// when children end
// add or concat brothers node when it is the last parent node
if (brothersNode.length > 0) {
let findBrothersNode = brothersNodeProps.find(
e =>
e.parentId === brothersParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...nodes];
let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
nodes = [...ModifyNodes];
brothersNode = [];
}
} else {
const { width, height } = calculateSize(brothersNode, false);
const brothersNodeId = addNodes(brothersNode, width, height, false);
brothersNode = [];
if (brothersParentId) {
addEdges(brothersParentId, brothersNodeId);
} else {
notHaveParent = [...notHaveParent, brothersNodeId];
}
brothersNodeProps = [
...brothersNodeProps,
{
id: brothersNodeId,
parentId: brothersParentId,
objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
},
];
}
}
// close brackets
if (parentType !== "array") {
if (bracketOpen.length > 0) {
let newBracketOpen = [...bracketOpen];
newBracketOpen.splice(newBracketOpen.length - 1);
bracketOpen = [...newBracketOpen];
}
} else if (parentType === "array") {
if (objectsFromArray.length > 0) {
let newobjectsFromArray = [...objectsFromArray];
newobjectsFromArray.splice(newobjectsFromArray.length - 1);
objectsFromArray = [...newobjectsFromArray];
}
}
if (parentId) {
let myChildrens = edges.filter(e => e.from === parentId);
let myIndex = nodes.findIndex(e => e.id === parentId);
let ModifyNodes = [...nodes];
if (ModifyNodes[myIndex]) {
ModifyNodes[myIndex].data.childrenCount = myChildrens.length;
nodes = [...ModifyNodes];
}
}
}
}
};
if (json) {
traverse(json);
if (notHaveParent.length > 1) {
if (json.type !== "array") {
const text = "";
const { width, height } = calculateSize(text, false);
const emptyId = addNodes(text, width, height, false, true);
notHaveParent.forEach(children => {
addEdges(emptyId, children);
});
}
}
if (nodes.length === 0) {
if (json.type === "array") {
const text = "[]";
const { width, height } = calculateSize(text, false);
addNodes(text, width, height, false);
} else {
const text = "{}";
const { width, height } = calculateSize(text, false);
addNodes(text, width, height, false);
}
}
}
return { nodes, edges };
} catch (error) {
console.error(error);
return {
nodes: [],
edges: [],
};
}
};