feat(xml): add XML tools for validation, beautification, and viewing

This commit is contained in:
AshAnand34
2025-07-08 12:56:31 -07:00
parent d47fc0812d
commit 6b2070b39f
20 changed files with 372 additions and 3 deletions

31
package-lock.json generated
View File

@@ -29,6 +29,7 @@
"cron-validator": "^1.3.1", "cron-validator": "^1.3.1",
"cronstrue": "^3.0.0", "cronstrue": "^3.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"fast-xml-parser": "^5.2.5",
"formik": "^2.4.6", "formik": "^2.4.6",
"jimp": "^0.22.12", "jimp": "^0.22.12",
"js-quantities": "^1.8.0", "js-quantities": "^1.8.0",
@@ -5893,6 +5894,24 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "dev": true
}, },
"node_modules/fast-xml-parser": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
"integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"strnum": "^2.1.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -10502,6 +10521,18 @@
"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
"dev": true "dev": true
}, },
"node_modules/strnum": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz",
"integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/strtok3": { "node_modules/strtok3": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",

View File

@@ -46,6 +46,7 @@
"cron-validator": "^1.3.1", "cron-validator": "^1.3.1",
"cronstrue": "^3.0.0", "cronstrue": "^3.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"fast-xml-parser": "^5.2.5",
"formik": "^2.4.6", "formik": "^2.4.6",
"jimp": "^0.22.12", "jimp": "^0.22.12",
"js-quantities": "^1.8.0", "js-quantities": "^1.8.0",

View File

@@ -0,0 +1,4 @@
import { tool as xmlXmlValidator } from './xml-validator/meta';
import { tool as xmlXmlBeautifier } from './xml-beautifier/meta';
import { tool as xmlXmlViewer } from './xml-viewer/meta';
export const xmlTools = [xmlXmlViewer, xmlXmlBeautifier, xmlXmlValidator];

View File

@@ -0,0 +1,56 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { CardExampleType } from '@components/examples/ToolExamples';
import { beautifyXml } from './service';
import { InitialValuesType } from './types';
const initialValues: InitialValuesType = {};
const exampleCards: CardExampleType<InitialValuesType>[] = [
{
title: 'Beautify XML',
description: 'Beautify a compact XML string for readability.',
sampleText: '<root><item>1</item><item>2</item></root>',
sampleResult: `<root>\n <item>1</item>\n <item>2</item>\n</root>`,
sampleOptions: {}
}
];
const getGroups = () => [];
export default function XmlBeautifier({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (_values: InitialValuesType, input: string) => {
setResult(beautifyXml(input, {}));
};
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolTextInput
value={input}
onChange={setInput}
placeholder="Paste or import XML here..."
/>
}
resultComponent={<ToolTextResult value={result} extension="xml" />}
initialValues={initialValues}
exampleCards={exampleCards}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
/>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('xml', {
name: 'XML Beautifier',
path: 'xml-beautifier',
icon: 'mdi:format-align-left',
description:
'Beautify and reformat XML for improved readability and structure.',
shortDescription: 'Beautify XML for readability.',
keywords: ['xml', 'beautify', 'format', 'pretty', 'indent'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,16 @@
import { InitialValuesType } from './types';
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
export function beautifyXml(
input: string,
_options: InitialValuesType
): string {
try {
const parser = new XMLParser();
const obj = parser.parse(input);
const builder = new XMLBuilder({ format: true, indentBy: ' ' });
return builder.build(obj);
} catch (e: any) {
return `Invalid XML: ${e.message}`;
}
}

View File

@@ -0,0 +1,3 @@
export type InitialValuesType = {
// splitSeparator: string;
};

View File

@@ -0,0 +1,18 @@
import { expect, describe, it } from 'vitest';
import { beautifyXml } from './service';
describe('xml-beautifier', () => {
it('beautifies valid XML', () => {
const input = '<root><a>1</a><b>2</b></root>';
const result = beautifyXml(input, {});
expect(result).toContain('<root>');
expect(result).toContain(' <a>1</a>');
expect(result).toContain(' <b>2</b>');
});
it('returns error for invalid XML', () => {
const input = '<root><a>1</b></root>';
const result = beautifyXml(input, {});
expect(result).toMatch(/Invalid XML/i);
});
});

View File

@@ -0,0 +1,63 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { CardExampleType } from '@components/examples/ToolExamples';
import { validateXml } from './service';
import { InitialValuesType } from './types';
const initialValues: InitialValuesType = {};
const getGroups = () => [];
const exampleCards: CardExampleType<InitialValuesType>[] = [
{
title: 'Validate XML',
description: 'Check if an XML string is well-formed.',
sampleText: '<root><item>1</item><item>2</item></root>',
sampleResult: 'Valid XML',
sampleOptions: {}
},
{
title: 'Invalid XML',
description: 'Example of malformed XML.',
sampleText: '<root><item>1</item><item>2</root>',
sampleResult: 'Invalid XML: ...',
sampleOptions: {}
}
];
export default function XmlValidator({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (_values: InitialValuesType, input: string) => {
setResult(validateXml(input, {}));
};
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolTextInput
value={input}
onChange={setInput}
placeholder="Paste or import XML here..."
/>
}
resultComponent={<ToolTextResult value={result} extension="txt" />}
initialValues={initialValues}
exampleCards={exampleCards}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
/>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('xml', {
name: 'XML Validator',
path: 'xml-validator',
icon: 'mdi:check-decagram',
description:
'Validate XML files or strings to ensure they are well-formed and error-free.',
shortDescription: 'Validate XML for errors.',
keywords: ['xml', 'validate', 'check', 'syntax', 'error'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,16 @@
import { InitialValuesType } from './types';
import { XMLValidator } from 'fast-xml-parser';
export function validateXml(
input: string,
_options: InitialValuesType
): string {
const result = XMLValidator.validate(input);
if (result === true) {
return 'Valid XML';
} else if (typeof result === 'object' && result.err) {
return `Invalid XML: ${result.err.msg} (line ${result.err.line}, col ${result.err.col})`;
} else {
return 'Invalid XML: Unknown error';
}
}

View File

@@ -0,0 +1,3 @@
export type InitialValuesType = {
// splitSeparator: string;
};

View File

@@ -0,0 +1,16 @@
import { expect, describe, it } from 'vitest';
import { validateXml } from './service';
describe('xml-validator', () => {
it('returns Valid XML for well-formed XML', () => {
const input = '<root><a>1</a><b>2</b></root>';
const result = validateXml(input, {});
expect(result).toBe('Valid XML');
});
it('returns error for invalid XML', () => {
const input = '<root><a>1</b></root>';
const result = validateXml(input, {});
expect(result).toMatch(/Invalid XML/i);
});
});

View File

@@ -0,0 +1,56 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { CardExampleType } from '@components/examples/ToolExamples';
import { prettyPrintXml } from './service';
import { InitialValuesType } from './types';
const initialValues: InitialValuesType = {};
const exampleCards: CardExampleType<InitialValuesType>[] = [
{
title: 'Pretty Print XML',
description: 'View and pretty-print a compact XML string.',
sampleText: '<root><item>1</item><item>2</item></root>',
sampleResult: `<root>\n <item>1</item>\n <item>2</item>\n</root>`,
sampleOptions: {}
}
];
const getGroups = () => [];
export default function XmlViewer({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (_values: InitialValuesType, input: string) => {
setResult(prettyPrintXml(input, {}));
};
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolTextInput
value={input}
onChange={setInput}
placeholder="Paste or import XML here..."
/>
}
resultComponent={<ToolTextResult value={result} extension="xml" />}
initialValues={initialValues}
exampleCards={exampleCards}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
/>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('xml', {
name: 'XML Viewer',
path: 'xml-viewer',
icon: 'mdi:eye-outline',
description:
'View and pretty-print XML files or strings for easier reading and debugging.',
shortDescription: 'Pretty-print and view XML.',
keywords: ['xml', 'viewer', 'pretty print', 'format', 'inspect'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,16 @@
import { InitialValuesType } from './types';
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
export function prettyPrintXml(
input: string,
_options: InitialValuesType
): string {
try {
const parser = new XMLParser();
const obj = parser.parse(input);
const builder = new XMLBuilder({ format: true, indentBy: ' ' });
return builder.build(obj);
} catch (e: any) {
return `Invalid XML: ${e.message}`;
}
}

View File

@@ -0,0 +1,3 @@
export type InitialValuesType = {
// splitSeparator: string;
};

View File

@@ -0,0 +1,18 @@
import { expect, describe, it } from 'vitest';
import { prettyPrintXml } from './service';
describe('xml-viewer', () => {
it('pretty prints valid XML', () => {
const input = '<root><a>1</a><b>2</b></root>';
const result = prettyPrintXml(input, {});
expect(result).toContain('<root>');
expect(result).toContain(' <a>1</a>');
expect(result).toContain(' <b>2</b>');
});
it('returns error for invalid XML', () => {
const input = '<root><a>1</b></root>';
const result = prettyPrintXml(input, {});
expect(result).toMatch(/Invalid XML/i);
});
});

View File

@@ -24,7 +24,8 @@ export type ToolCategory =
| 'time' | 'time'
| 'csv' | 'csv'
| 'pdf' | 'pdf'
| 'image-generic'; | 'image-generic'
| 'xml';
export interface DefinedTool { export interface DefinedTool {
type: ToolCategory; type: ToolCategory;

View File

@@ -11,6 +11,7 @@ import { csvTools } from '../pages/tools/csv';
import { timeTools } from '../pages/tools/time'; import { timeTools } from '../pages/tools/time';
import { IconifyIcon } from '@iconify/react'; import { IconifyIcon } from '@iconify/react';
import { pdfTools } from '../pages/tools/pdf'; import { pdfTools } from '../pages/tools/pdf';
import { xmlTools } from 'pages/tools/xml';
const toolCategoriesOrder: ToolCategory[] = [ const toolCategoriesOrder: ToolCategory[] = [
'image-generic', 'image-generic',
@@ -23,7 +24,8 @@ const toolCategoriesOrder: ToolCategory[] = [
'number', 'number',
'png', 'png',
'time', 'time',
'gif' 'gif',
'xml'
]; ];
export const tools: DefinedTool[] = [ export const tools: DefinedTool[] = [
...imageTools, ...imageTools,
@@ -34,7 +36,8 @@ export const tools: DefinedTool[] = [
...csvTools, ...csvTools,
...videoTools, ...videoTools,
...numberTools, ...numberTools,
...timeTools ...timeTools,
...xmlTools
]; ];
const categoriesConfig: { const categoriesConfig: {
type: ToolCategory; type: ToolCategory;
@@ -115,6 +118,12 @@ const categoriesConfig: {
icon: 'material-symbols-light:image-outline-rounded', icon: 'material-symbols-light:image-outline-rounded',
value: value:
'Tools for working with pictures compress, resize, crop, convert to JPG, rotate, remove background and much more.' 'Tools for working with pictures compress, resize, crop, convert to JPG, rotate, remove background and much more.'
},
{
type: 'xml',
icon: 'mdi-light:xml',
value:
'Tools for working with XML data structures - viewer, beautifier, validator and much more'
} }
]; ];
// use for changelogs // use for changelogs