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

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);
});
});