diff --git a/components.d.ts b/components.d.ts
index 15cda0fd..0db2d095 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -161,6 +161,7 @@ declare module '@vue/runtime-core' {
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
+ XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default']
}
}
diff --git a/package.json b/package.json
index 26a3d5d3..277da558 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"uuid": "^8.3.2",
"vue": "^3.2.47",
"vue-router": "^4.1.6",
+ "xml-formatter": "^3.3.2",
"yaml": "^2.2.1"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 914a9e12..99488feb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -130,6 +130,9 @@ dependencies:
vue-router:
specifier: ^4.1.6
version: 4.1.6(vue@3.2.47)
+ xml-formatter:
+ specifier: ^3.3.2
+ version: 3.3.2
yaml:
specifier: ^2.2.1
version: 2.2.1
@@ -8883,11 +8886,23 @@ packages:
optional: true
dev: true
+ /xml-formatter@3.3.2:
+ resolution: {integrity: sha512-ld34F1b7+2UQGNkfsAV4MN3/b7cdUstyMj3XJhzKFasOPtMToVCkqmrNcmrRuSlPxgH1K9tXPkqr75gAT3ix2g==}
+ engines: {node: '>= 14'}
+ dependencies:
+ xml-parser-xo: 4.0.5
+ dev: false
+
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
dev: true
+ /xml-parser-xo@4.0.5:
+ resolution: {integrity: sha512-UWXOHMQ4ySxpUiU3S/9KzPOhninlL8SN1xFfWgX9WjgoZWoLKtEeJIEz4jhKtdFsoZBCYjg9rDEP3qfnpiHagQ==}
+ engines: {node: '>= 14'}
+ dev: false
+
/xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
dev: true
diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue
index 78f26d76..16e11512 100644
--- a/src/components/TextareaCopyable.vue
+++ b/src/components/TextareaCopyable.vue
@@ -25,6 +25,7 @@ const props = withDefaults(
hljs.registerLanguage('sql', sqlHljs);
hljs.registerLanguage('json', jsonHljs);
hljs.registerLanguage('html', xmlHljs);
+hljs.registerLanguage('xml', xmlHljs);
hljs.registerLanguage('yaml', yamlHljs);
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 211a8a81..f451d679 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -57,6 +57,7 @@ import { tool as urlEncoder } from './url-encoder';
import { tool as urlParser } from './url-parser';
import { tool as uuidGenerator } from './uuid-generator';
import { tool as macAddressLookup } from './mac-address-lookup';
+import { tool as xmlFormatter } from './xml-formatter';
export const toolsByCategory: ToolCategory[] = [
{
@@ -114,6 +115,7 @@ export const toolsByCategory: ToolCategory[] = [
sqlPrettify,
chmodCalculator,
dockerRunToDockerComposeConverter,
+ xmlFormatter,
],
},
{
diff --git a/src/tools/xml-formatter/index.ts b/src/tools/xml-formatter/index.ts
new file mode 100644
index 00000000..fe28d3ae
--- /dev/null
+++ b/src/tools/xml-formatter/index.ts
@@ -0,0 +1,12 @@
+import { Code } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'XML formatter',
+ path: '/xml-formatter',
+ description: 'Prettify your XML string to a human friendly readable format.',
+ keywords: ['xml', 'prettify', 'format'],
+ component: () => import('./xml-formatter.vue'),
+ icon: Code,
+ createdAt: new Date('2023-06-17'),
+});
diff --git a/src/tools/xml-formatter/xml-formatter.e2e.spec.ts b/src/tools/xml-formatter/xml-formatter.e2e.spec.ts
new file mode 100644
index 00000000..11fbbd8e
--- /dev/null
+++ b/src/tools/xml-formatter/xml-formatter.e2e.spec.ts
@@ -0,0 +1,23 @@
+import { expect, test } from '@playwright/test';
+
+test.describe('Tool - XML formatter', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/xml-formatter');
+ });
+
+ test('Has correct title', async ({ page }) => {
+ await expect(page).toHaveTitle('XML formatter - IT Tools');
+ });
+
+ test('XML is converted into a human readable format', async ({ page }) => {
+ await page.getByTestId('input').fill('bazbaz');
+
+ const formattedXml = await page.getByTestId('area-content').innerText();
+
+ expect(formattedXml.trim()).toEqual(`
+
+ baz
+ baz
+`.trim());
+ });
+});
diff --git a/src/tools/xml-formatter/xml-formatter.service.test.ts b/src/tools/xml-formatter/xml-formatter.service.test.ts
new file mode 100644
index 00000000..2b14558c
--- /dev/null
+++ b/src/tools/xml-formatter/xml-formatter.service.test.ts
@@ -0,0 +1,27 @@
+import { describe, expect, it } from 'vitest';
+import { formatXml } from './xml-formatter.service';
+
+describe('xml-formatter service', () => {
+ describe('formatXml', () => {
+ it('converts XML into a human readable format', () => {
+ const initString = 'foobar';
+
+ expect(formatXml(initString)).toMatchInlineSnapshot(`
+ "
+
+ foo
+
+
+ bar
+
+ "
+ `);
+ });
+
+ it('returns an empty string if the input is not valid XML', () => {
+ const initString = 'hello world';
+
+ expect(formatXml(initString)).toEqual('');
+ });
+ });
+});
diff --git a/src/tools/xml-formatter/xml-formatter.service.ts b/src/tools/xml-formatter/xml-formatter.service.ts
new file mode 100644
index 00000000..3441f0bb
--- /dev/null
+++ b/src/tools/xml-formatter/xml-formatter.service.ts
@@ -0,0 +1,28 @@
+import xmlFormat, { type XMLFormatterOptions } from 'xml-formatter';
+import { withDefaultOnError } from '@/utils/defaults';
+
+export { formatXml, isValidXML };
+
+function cleanRawXml(rawXml: string): string {
+ return rawXml.trim();
+}
+
+function formatXml(rawXml: string, options?: XMLFormatterOptions): string {
+ return withDefaultOnError(() => xmlFormat(cleanRawXml(rawXml), options) ?? '', '');
+}
+
+function isValidXML(rawXml: string): boolean {
+ const cleanedRawXml = cleanRawXml(rawXml);
+
+ if (cleanedRawXml === '') {
+ return true;
+ }
+
+ try {
+ xmlFormat(cleanedRawXml);
+ return true;
+ }
+ catch (e) {
+ return false;
+ }
+}
diff --git a/src/tools/xml-formatter/xml-formatter.vue b/src/tools/xml-formatter/xml-formatter.vue
new file mode 100644
index 00000000..d59cf8c7
--- /dev/null
+++ b/src/tools/xml-formatter/xml-formatter.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+