From fdb7d5d704cb10a4eeb55fbe5866196cbc06ca9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Tue, 25 Mar 2025 16:11:17 +0000 Subject: [PATCH 01/10] Initial setup of pages. Tool definition --- src/pages/tools/json/index.ts | 4 +- src/pages/tools/json/json-to-xml/index.tsx | 128 ++++++++++++++++++++ src/pages/tools/json/json-to-xml/meta.ts | 12 ++ src/pages/tools/json/json-to-xml/service.ts | 0 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/pages/tools/json/json-to-xml/index.tsx create mode 100644 src/pages/tools/json/json-to-xml/meta.ts create mode 100644 src/pages/tools/json/json-to-xml/service.ts diff --git a/src/pages/tools/json/index.ts b/src/pages/tools/json/index.ts index e1f0c67..6484d1b 100644 --- a/src/pages/tools/json/index.ts +++ b/src/pages/tools/json/index.ts @@ -2,10 +2,12 @@ import { tool as jsonPrettify } from './prettify/meta'; import { tool as jsonMinify } from './minify/meta'; import { tool as jsonStringify } from './stringify/meta'; import { tool as validateJson } from './validateJson/meta'; +import { tool as jsonToXml } from './json-to-xml/meta'; export const jsonTools = [ validateJson, jsonPrettify, jsonMinify, - jsonStringify + jsonStringify, + jsonToXml ]; diff --git a/src/pages/tools/json/json-to-xml/index.tsx b/src/pages/tools/json/json-to-xml/index.tsx new file mode 100644 index 0000000..3439e54 --- /dev/null +++ b/src/pages/tools/json/json-to-xml/index.tsx @@ -0,0 +1,128 @@ +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +// import { convertJsonToXml } from './service'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { ToolComponentProps } from '@tools/defineTool'; +import { Box } from '@mui/material'; +import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; + +type InitialValuesType = { + delimiter: string; + quote: string; + comment: string; + useHeaders: boolean; + skipEmptyLines: boolean; +}; + +const initialValues: InitialValuesType = { + delimiter: ',', + quote: '"', + comment: '#', + useHeaders: true, + skipEmptyLines: true +}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Basic CSV to XML', + description: 'Convert a simple CSV file into an XML format.', + sampleText: 'name,age,city\nJohn,30,New York\nAlice,25,London', + sampleResult: ` + + John + 30 + New York + + + Alice + 25 + London + +`, + sampleOptions: { + ...initialValues, + useHeaders: true + } + } +]; + +export default function JsonToXml({ title }: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (values: InitialValuesType, input: string) => { + if (input) { + try { + //const xmlResult = convertJsonToXml(input, values); + //setResult(xmlResult); + } catch (error) { + setResult( + `Error: ${ + error instanceof Error ? error.message : 'Invalid Json format' + }` + ); + } + } + }; + + return ( + + } + resultComponent={} + getGroups={({ values, updateField }) => [ + { + title: 'Input Json Format', + component: ( + + updateField('delimiter', val)} + /> + updateField('quote', val)} + value={values.quote} + /> + updateField('comment', val)} + /> + + ) + }, + { + title: 'Conversion Options', + component: ( + + updateField('useHeaders', value)} + title="Use Headers" + description="First row is treated as column headers" + /> + updateField('skipEmptyLines', value)} + title="Skip Empty Lines" + description="Don't process empty lines in the CSV" + /> + + ) + } + ]} + /> + ); +} diff --git a/src/pages/tools/json/json-to-xml/meta.ts b/src/pages/tools/json/json-to-xml/meta.ts new file mode 100644 index 0000000..a0f5f63 --- /dev/null +++ b/src/pages/tools/json/json-to-xml/meta.ts @@ -0,0 +1,12 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('json', { + name: 'Convert JSON to XML', + path: 'json-to-xml', + icon: 'mdi-light:xml', + description: 'Convert JSON files to XML format with customizable options.', + shortDescription: 'Convert JSON data to XML format', + keywords: ['json', 'xml', 'convert', 'transform', 'parse'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts new file mode 100644 index 0000000..e69de29 From ff628c0ef12ddfbef241feea810fb463540b60b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Tue, 25 Mar 2025 16:42:21 +0000 Subject: [PATCH 02/10] Added tool options --- src/pages/tools/json/json-to-xml/index.tsx | 110 +++++++++++---------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/src/pages/tools/json/json-to-xml/index.tsx b/src/pages/tools/json/json-to-xml/index.tsx index 3439e54..17be45b 100644 --- a/src/pages/tools/json/json-to-xml/index.tsx +++ b/src/pages/tools/json/json-to-xml/index.tsx @@ -5,46 +5,55 @@ import ToolTextResult from '@components/result/ToolTextResult'; // import { convertJsonToXml } from './service'; import { CardExampleType } from '@components/examples/ToolExamples'; import { ToolComponentProps } from '@tools/defineTool'; -import { Box } from '@mui/material'; +import { Box, Radio } from '@mui/material'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import RadioWithTextField from '@components/options/RadioWithTextField'; +import SimpleRadio from '@components/options/SimpleRadio'; type InitialValuesType = { - delimiter: string; - quote: string; - comment: string; - useHeaders: boolean; - skipEmptyLines: boolean; + indentationType: 'space' | 'tab' | 'none'; + addMetaTag: boolean; }; const initialValues: InitialValuesType = { - delimiter: ',', - quote: '"', - comment: '#', - useHeaders: true, - skipEmptyLines: true + indentationType: 'space', + addMetaTag: false }; const exampleCards: CardExampleType[] = [ { - title: 'Basic CSV to XML', - description: 'Convert a simple CSV file into an XML format.', - sampleText: 'name,age,city\nJohn,30,New York\nAlice,25,London', + title: 'Basic JSON to XML', + description: 'Convert a simple JSON object into an XML format.', + sampleText: ` +{ + "users": [ + { + "name": "John", + "age": 30, + "city": "New York" + }, + { + "name": "Alice", + "age": 25, + "city": "London" + } + ] +}`, sampleResult: ` - - John - 30 - New York - - - Alice - 25 - London - +\t +\t\tJohn +\t\t30 +\t\tNew York +\t +\t +\t\tAlice +\t\t25 +\t\tLondon +\t `, sampleOptions: { - ...initialValues, - useHeaders: true + ...initialValues } } ]; @@ -82,42 +91,43 @@ export default function JsonToXml({ title }: ToolComponentProps) { resultComponent={} getGroups={({ values, updateField }) => [ { - title: 'Input Json Format', + title: 'Output XML Indentation', component: ( - updateField('delimiter', val)} + updateField('indentationType', 'space')} /> - updateField('quote', val)} - value={values.quote} + updateField('indentationType', 'tab')} /> - updateField('comment', val)} + updateField('indentationType', 'none')} /> ) }, { - title: 'Conversion Options', + title: 'XML Meta Information', component: ( updateField('useHeaders', value)} - title="Use Headers" - description="First row is treated as column headers" - /> - updateField('skipEmptyLines', value)} - title="Skip Empty Lines" - description="Don't process empty lines in the CSV" + checked={values.addMetaTag} + onChange={(value) => updateField('addMetaTag', value)} + title="Add an XML Meta Tag" + description="Add a meta tag at the beginning of the XML output." /> ) From b13ae88475f3f68abc62d6dd669189e759a663ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Tue, 25 Mar 2025 19:07:26 +0000 Subject: [PATCH 03/10] Added conversion from json to xml --- src/pages/tools/json/json-to-xml/index.tsx | 6 +- src/pages/tools/json/json-to-xml/service.ts | 81 +++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/pages/tools/json/json-to-xml/index.tsx b/src/pages/tools/json/json-to-xml/index.tsx index 17be45b..68da35b 100644 --- a/src/pages/tools/json/json-to-xml/index.tsx +++ b/src/pages/tools/json/json-to-xml/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import ToolContent from '@components/ToolContent'; import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextResult from '@components/result/ToolTextResult'; -// import { convertJsonToXml } from './service'; +import { convertJsonToXml } from './service'; import { CardExampleType } from '@components/examples/ToolExamples'; import { ToolComponentProps } from '@tools/defineTool'; import { Box, Radio } from '@mui/material'; @@ -65,8 +65,8 @@ export default function JsonToXml({ title }: ToolComponentProps) { const compute = (values: InitialValuesType, input: string) => { if (input) { try { - //const xmlResult = convertJsonToXml(input, values); - //setResult(xmlResult); + const xmlResult = convertJsonToXml(input, values); + setResult(xmlResult); } catch (error) { setResult( `Error: ${ diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index e69de29..ae9671e 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -0,0 +1,81 @@ +type JsonToXmlOptions = { + indentationType: 'space' | 'tab' | 'none'; + addMetaTag: boolean; +}; + +export const convertJsonToXml = ( + json: string, + options: JsonToXmlOptions +): string => { + const obj = JSON.parse(json); + return convertObjectToXml(obj, options); +}; + +const getIndentation = (options: JsonToXmlOptions, depth: number): string => { + switch (options.indentationType) { + case 'space': + return ' '.repeat(depth + 1); + case 'tab': + return '\t'.repeat(depth + 1); + case 'none': + default: + return ''; + } +}; + +const convertObjectToXml = ( + obj: any, + options: JsonToXmlOptions, + depth: number = 0 +): string => { + let xml = ''; + + const newline = options.indentationType === 'none' ? '' : '\n'; + + if (depth === 0) { + if (options.addMetaTag) { + xml += '' + newline; + } + xml += '' + newline; + } + + for (const key in obj) { + const value = obj[key]; + + const keyString = isNaN(Number(key)) ? key : `row-${key}`; + + if (Array.isArray(value)) { + value.forEach((item) => { + xml += `${getIndentation(options, depth)}<${keyString}>`; + xml += + typeof item === 'object' && item !== null + ? `${newline}${convertObjectToXml( + item, + options, + depth + 1 + )}${getIndentation(options, depth)}` + : `${escapeXml(String(item))}`; + xml += `${newline}`; + }); + } else if (typeof value === 'object' && value !== null) { + xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; + xml += convertObjectToXml(value, options, depth + 1); + xml += `${getIndentation(options, depth)}${newline}`; + } else { + xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml( + String(value) + )}${newline}`; + } + } + + return depth === 0 ? `${xml}` : xml; +}; + +const escapeXml = (str: string): string => { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +}; From 52666b2780e621f1c289cfd86e4249244298ffc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Tue, 25 Mar 2025 19:23:29 +0000 Subject: [PATCH 04/10] Removed unecessary dependencies --- src/pages/tools/json/json-to-xml/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/tools/json/json-to-xml/index.tsx b/src/pages/tools/json/json-to-xml/index.tsx index 68da35b..78cec17 100644 --- a/src/pages/tools/json/json-to-xml/index.tsx +++ b/src/pages/tools/json/json-to-xml/index.tsx @@ -1,14 +1,12 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import ToolContent from '@components/ToolContent'; import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextResult from '@components/result/ToolTextResult'; import { convertJsonToXml } from './service'; import { CardExampleType } from '@components/examples/ToolExamples'; import { ToolComponentProps } from '@tools/defineTool'; -import { Box, Radio } from '@mui/material'; +import { Box } from '@mui/material'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; -import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; -import RadioWithTextField from '@components/options/RadioWithTextField'; import SimpleRadio from '@components/options/SimpleRadio'; type InitialValuesType = { From 63884cd5ea5013ef7842bb0fa5e859dabb569272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 11:09:20 +0000 Subject: [PATCH 05/10] Added support for null values: empty xml tag --- src/pages/tools/json/json-to-xml/service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index ae9671e..33eb8e1 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -57,7 +57,12 @@ const convertObjectToXml = ( : `${escapeXml(String(item))}`; xml += `${newline}`; }); - } else if (typeof value === 'object' && value !== null) { + } else if (value === null) { + xml += `${getIndentation( + options, + depth + )}<${keyString}>${newline}`; + } else if (typeof value === 'object') { xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; xml += convertObjectToXml(value, options, depth + 1); xml += `${getIndentation(options, depth)}${newline}`; From 52134e82a35c40d27ca3b9b83689e241162bea5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 11:22:57 +0000 Subject: [PATCH 06/10] Refactor: removed redundancy on array conversion --- src/pages/tools/json/json-to-xml/service.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index 33eb8e1..2d6d255 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -47,14 +47,7 @@ const convertObjectToXml = ( if (Array.isArray(value)) { value.forEach((item) => { xml += `${getIndentation(options, depth)}<${keyString}>`; - xml += - typeof item === 'object' && item !== null - ? `${newline}${convertObjectToXml( - item, - options, - depth + 1 - )}${getIndentation(options, depth)}` - : `${escapeXml(String(item))}`; + xml += convertObjectToXml(item, options, depth + 1); xml += `${newline}`; }); } else if (value === null) { @@ -66,6 +59,8 @@ const convertObjectToXml = ( xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; xml += convertObjectToXml(value, options, depth + 1); xml += `${getIndentation(options, depth)}${newline}`; + + // All other types are tre } else { xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml( String(value) From 1fbba9b35956d791ee51a48a6b539da325af52ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 11:42:59 +0000 Subject: [PATCH 07/10] Fix: line break in arrays --- src/pages/tools/json/json-to-xml/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index 2d6d255..f5e11df 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -46,7 +46,7 @@ const convertObjectToXml = ( if (Array.isArray(value)) { value.forEach((item) => { - xml += `${getIndentation(options, depth)}<${keyString}>`; + xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; xml += convertObjectToXml(item, options, depth + 1); xml += `${newline}`; }); From 44f0856ad481c7357df8c2feb0de9b448f67eae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 12:00:26 +0000 Subject: [PATCH 08/10] Fixed comment --- src/pages/tools/json/json-to-xml/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index f5e11df..24b52e7 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -60,7 +60,7 @@ const convertObjectToXml = ( xml += convertObjectToXml(value, options, depth + 1); xml += `${getIndentation(options, depth)}${newline}`; - // All other types are tre + // All other types are treated the same way } else { xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml( String(value) From c9f94f5f61720716e5083b9be5029ef3618486b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 12:41:18 +0000 Subject: [PATCH 09/10] Fix: indentation fix --- src/pages/tools/json/json-to-xml/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index 24b52e7..4525583 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -48,7 +48,7 @@ const convertObjectToXml = ( value.forEach((item) => { xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; xml += convertObjectToXml(item, options, depth + 1); - xml += `${newline}`; + xml += `${getIndentation(options, depth)}${newline}`; }); } else if (value === null) { xml += `${getIndentation( From b61ac4d9692d23c632311e1e5569c1d2181188be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Jesus?= Date: Wed, 26 Mar 2025 15:49:56 +0000 Subject: [PATCH 10/10] Refactor: code is easier to understand and more recursive-friendly --- src/pages/tools/json/json-to-xml/service.ts | 48 ++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/pages/tools/json/json-to-xml/service.ts b/src/pages/tools/json/json-to-xml/service.ts index 4525583..bbc05c1 100644 --- a/src/pages/tools/json/json-to-xml/service.ts +++ b/src/pages/tools/json/json-to-xml/service.ts @@ -41,31 +41,49 @@ const convertObjectToXml = ( for (const key in obj) { const value = obj[key]; - const keyString = isNaN(Number(key)) ? key : `row-${key}`; - if (Array.isArray(value)) { - value.forEach((item) => { - xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; - xml += convertObjectToXml(item, options, depth + 1); - xml += `${getIndentation(options, depth)}${newline}`; - }); - } else if (value === null) { + // Handle null values + if (value === null) { xml += `${getIndentation( options, depth )}<${keyString}>${newline}`; - } else if (typeof value === 'object') { + continue; + } + + // Handle arrays + if (Array.isArray(value)) { + value.forEach((item) => { + xml += `${getIndentation(options, depth)}<${keyString}>`; + if (item === null) { + xml += `${newline}`; + } else if (typeof item === 'object') { + xml += `${newline}${convertObjectToXml( + item, + options, + depth + 1 + )}${getIndentation(options, depth)}`; + xml += `${newline}`; + } else { + xml += `${escapeXml(String(item))}${newline}`; + } + }); + continue; + } + + // Handle objects + if (typeof value === 'object') { xml += `${getIndentation(options, depth)}<${keyString}>${newline}`; xml += convertObjectToXml(value, options, depth + 1); xml += `${getIndentation(options, depth)}${newline}`; - - // All other types are treated the same way - } else { - xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml( - String(value) - )}${newline}`; + continue; } + + // Handle primitive values (string, number, boolean, etc.) + xml += `${getIndentation(options, depth)}<${keyString}>${escapeXml( + String(value) + )}${newline}`; } return depth === 0 ? `${xml}` : xml;