fix serialize and json tool to handle boolean type and number type field, and fix the nested rows in array and object type
This commit is contained in:
@@ -12,6 +12,7 @@ const DiffTool = () => {
|
||||
const [rightText, setRightText] = useState('');
|
||||
const [diffMode, setDiffMode] = useState('unified'); // 'unified' or 'split'
|
||||
|
||||
|
||||
// Generate unified diff format for react-diff-view
|
||||
const diffText = useMemo(() => {
|
||||
if (!leftText && !rightText) return null;
|
||||
@@ -231,6 +232,7 @@ const user = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Diff Result */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
|
||||
@@ -12,6 +12,7 @@ const SerializeTool = () => {
|
||||
const [editorMode, setEditorMode] = useState('text'); // 'text' or 'visual'
|
||||
const [structuredData, setStructuredData] = useState({});
|
||||
|
||||
|
||||
// Simple PHP serialize implementation for common data types
|
||||
const phpSerialize = (data) => {
|
||||
if (data === null) return 'N;';
|
||||
@@ -45,7 +46,8 @@ const SerializeTool = () => {
|
||||
// Simple PHP unserialize implementation
|
||||
const phpUnserialize = (str) => {
|
||||
let index = 0;
|
||||
|
||||
|
||||
|
||||
const parseValue = () => {
|
||||
if (index >= str.length) {
|
||||
throw new Error('Unexpected end of string');
|
||||
@@ -110,20 +112,60 @@ const SerializeTool = () => {
|
||||
}
|
||||
index++; // Skip opening '"'
|
||||
|
||||
const length = parseInt(lenStr);
|
||||
if (isNaN(length) || length < 0) {
|
||||
const byteLength = parseInt(lenStr);
|
||||
if (isNaN(byteLength) || byteLength < 0) {
|
||||
throw new Error(`Invalid string length: ${lenStr}`);
|
||||
}
|
||||
|
||||
// Extract string content
|
||||
const stringVal = str.substring(index, index + length);
|
||||
index += length;
|
||||
// Handle empty strings
|
||||
if (byteLength === 0) {
|
||||
// Expect closing quote and semicolon immediately
|
||||
if (index + 1 >= str.length || str[index] !== '"' || str[index + 1] !== ';') {
|
||||
throw new Error(`Expected '";' after empty string at position ${index}`);
|
||||
}
|
||||
index += 2; // Skip closing '";'
|
||||
return '';
|
||||
}
|
||||
|
||||
// Expect closing quote and semicolon
|
||||
if (index + 1 >= str.length || str[index] !== '"' || str[index + 1] !== ';') {
|
||||
// Extract string by slicing exact UTF-8 byte length
|
||||
const startIndex = index;
|
||||
const remaining = str.slice(index);
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(remaining);
|
||||
|
||||
if (bytes.length < byteLength) {
|
||||
throw new Error(`String byte length mismatch: expected ${byteLength}, got ${bytes.length} (remaining) at position ${startIndex}`);
|
||||
}
|
||||
|
||||
// Take exactly `byteLength` bytes and decode back to a JS string.
|
||||
// If the slice ends mid-codepoint, TextDecoder with {fatal:true} will throw.
|
||||
let stringVal = '';
|
||||
try {
|
||||
const slice = bytes.slice(0, byteLength);
|
||||
const decoder = new TextDecoder('utf-8', { fatal: true });
|
||||
stringVal = decoder.decode(slice);
|
||||
} catch (e) {
|
||||
throw new Error(`Declared byte length splits a UTF-8 code point at position ${startIndex}`);
|
||||
}
|
||||
|
||||
// Advance `index` by the number of UTF-16 code units consumed by `stringVal`.
|
||||
index += stringVal.length;
|
||||
|
||||
// Verify the re-encoded byte length matches exactly
|
||||
if (new TextEncoder().encode(stringVal).length !== byteLength) {
|
||||
throw new Error(`String byte length mismatch: expected ${byteLength}, got ${new TextEncoder().encode(stringVal).length} at position ${startIndex}`);
|
||||
}
|
||||
|
||||
// Expect closing quote and semicolon normally. Some producers incorrectly include the closing quote in the declared byte length.
|
||||
if (index + 1 < str.length && str[index] === '"' && str[index + 1] === ';') {
|
||||
index += 2; // standard '";' terminator
|
||||
} else if (index < str.length && str[index] === ';' && str[index - 1] === '"') {
|
||||
// Len included the closing '"' in the byteCount; accept ';' directly.
|
||||
// This is a compatibility path for non-standard serialized inputs observed in the wild.
|
||||
index += 1; // consume ';'
|
||||
} else {
|
||||
throw new Error(`Expected '";' after string at position ${index}`);
|
||||
}
|
||||
index += 2; // Skip closing '";'
|
||||
|
||||
return stringVal;
|
||||
|
||||
@@ -186,12 +228,10 @@ const SerializeTool = () => {
|
||||
|
||||
try {
|
||||
const result = parseValue();
|
||||
|
||||
// Check if there's unexpected trailing data
|
||||
if (index < str.length) {
|
||||
console.warn(`Warning: Trailing data after parsing: "${str.substring(index)}"`);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new Error(`Parse error at position ${index}: ${error.message}`);
|
||||
@@ -355,7 +395,7 @@ const SerializeTool = () => {
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Input/Output Grid */}
|
||||
<div className={`grid gap-6 ${
|
||||
mode === 'serialize' && editorMode === 'visual'
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Search, Copy, Download } from 'lucide-react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/material.css';
|
||||
import 'codemirror/mode/xml/xml';
|
||||
import 'codemirror/mode/css/css';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/addon/search/search';
|
||||
import 'codemirror/addon/search/searchcursor';
|
||||
import 'codemirror/addon/dialog/dialog';
|
||||
import 'codemirror/addon/dialog/dialog.css';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css as cssLang } from '@codemirror/lang-css';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { EditorView, keymap } from '@codemirror/view';
|
||||
import { searchKeymap, openSearchPanel } from '@codemirror/search';
|
||||
|
||||
const CodeInputs = ({
|
||||
htmlInput,
|
||||
@@ -21,14 +17,15 @@ const CodeInputs = ({
|
||||
isFullscreen
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState('html');
|
||||
const htmlEditorRef = useRef(null);
|
||||
const cssEditorRef = useRef(null);
|
||||
const jsEditorRef = useRef(null);
|
||||
const htmlViewRef = useRef(null);
|
||||
const cssViewRef = useRef(null);
|
||||
const jsViewRef = useRef(null);
|
||||
|
||||
// Handle search functionality
|
||||
const handleSearch = (editorRef) => {
|
||||
if (editorRef.current && editorRef.current.editor) {
|
||||
editorRef.current.editor.execCommand('find');
|
||||
const handleSearch = (viewRef) => {
|
||||
const view = viewRef.current;
|
||||
if (view) {
|
||||
openSearchPanel(view);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,10 +54,10 @@ const CodeInputs = ({
|
||||
// Get current editor ref based on active tab
|
||||
const getCurrentEditorRef = () => {
|
||||
switch (activeTab) {
|
||||
case 'html': return htmlEditorRef;
|
||||
case 'css': return cssEditorRef;
|
||||
case 'js': return jsEditorRef;
|
||||
default: return htmlEditorRef;
|
||||
case 'html': return htmlViewRef;
|
||||
case 'css': return cssViewRef;
|
||||
case 'js': return jsViewRef;
|
||||
default: return htmlViewRef;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,69 +136,36 @@ const CodeInputs = ({
|
||||
<div className="flex-1">
|
||||
{activeTab === 'html' && (
|
||||
<CodeMirror
|
||||
ref={htmlEditorRef}
|
||||
value={htmlInput}
|
||||
onBeforeChange={(editor, data, value) => setHtmlInput(value)}
|
||||
options={{
|
||||
mode: 'xml',
|
||||
theme: 'material',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseTags: true,
|
||||
matchBrackets: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
extraKeys: {
|
||||
'Ctrl-F': 'findPersistent',
|
||||
'Cmd-F': 'findPersistent'
|
||||
}
|
||||
}}
|
||||
height={isFullscreen ? 'calc(100vh - 210px)' : '380px'}
|
||||
extensions={[html(), keymap.of(searchKeymap), EditorView.lineWrapping]}
|
||||
onChange={(value) => setHtmlInput(value)}
|
||||
onUpdate={(vu) => { if (!htmlViewRef.current) htmlViewRef.current = vu.view; }}
|
||||
basicSetup={{ lineNumbers: true }}
|
||||
className="h-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'css' && (
|
||||
<CodeMirror
|
||||
ref={cssEditorRef}
|
||||
value={cssInput}
|
||||
onBeforeChange={(editor, data, value) => setCssInput(value)}
|
||||
options={{
|
||||
mode: 'css',
|
||||
theme: 'material',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
extraKeys: {
|
||||
'Ctrl-F': 'findPersistent',
|
||||
'Cmd-F': 'findPersistent'
|
||||
}
|
||||
}}
|
||||
height={isFullscreen ? 'calc(100vh - 210px)' : '380px'}
|
||||
extensions={[cssLang(), keymap.of(searchKeymap), EditorView.lineWrapping]}
|
||||
onChange={(value) => setCssInput(value)}
|
||||
onUpdate={(vu) => { if (!cssViewRef.current) cssViewRef.current = vu.view; }}
|
||||
basicSetup={{ lineNumbers: true }}
|
||||
className="h-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'js' && (
|
||||
<CodeMirror
|
||||
ref={jsEditorRef}
|
||||
value={jsInput}
|
||||
onBeforeChange={(editor, data, value) => setJsInput(value)}
|
||||
options={{
|
||||
mode: 'javascript',
|
||||
theme: 'material',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
extraKeys: {
|
||||
'Ctrl-F': 'findPersistent',
|
||||
'Cmd-F': 'findPersistent'
|
||||
}
|
||||
}}
|
||||
height={isFullscreen ? 'calc(100vh - 210px)' : '380px'}
|
||||
extensions={[javascript({ jsx: true }), keymap.of(searchKeymap), EditorView.lineWrapping]}
|
||||
onChange={(value) => setJsInput(value)}
|
||||
onUpdate={(vu) => { if (!jsViewRef.current) jsViewRef.current = vu.view; }}
|
||||
basicSetup={{ lineNumbers: true }}
|
||||
className="h-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user