aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar jmmanzano <manzano.jm@gmail.com> 2023-06-18 12:27:26 +0200
committerGravatar GitHub <noreply@github.com> 2023-06-18 10:27:26 +0000
commita6bbeaebd8e4f629ff20d6e2386be9c8734f6d8d (patch)
tree586b395e8c4854d362d844a6b15bb9cc85f52b64 /src
parentf771e7a99f962463401cdd8addd455782605675f (diff)
downloadit-tools-a6bbeaebd8e4f629ff20d6e2386be9c8734f6d8d.tar.gz
it-tools-a6bbeaebd8e4f629ff20d6e2386be9c8734f6d8d.tar.zst
it-tools-a6bbeaebd8e4f629ff20d6e2386be9c8734f6d8d.zip
feat(new tool): xml formatter (#457)
* feat(new tool): xml formatter * feat(xml-formatter): added happy path e2e tests * refactor(xml-formatter): improved unit tests * refactor(xml-formatter): add better suitable icon * feat(xml-formatter): added happy path e2e tests * feat(xml-formatter): registered xml as syntax highlighter * chore(auto-import): removed unused NSpace --------- Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/TextareaCopyable.vue1
-rw-r--r--src/tools/index.ts2
-rw-r--r--src/tools/xml-formatter/index.ts12
-rw-r--r--src/tools/xml-formatter/xml-formatter.e2e.spec.ts23
-rw-r--r--src/tools/xml-formatter/xml-formatter.service.test.ts27
-rw-r--r--src/tools/xml-formatter/xml-formatter.service.ts28
-rw-r--r--src/tools/xml-formatter/xml-formatter.vue46
7 files changed, 139 insertions, 0 deletions
diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue
index 78f26d7..16e1151 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 211a8a8..f451d67 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 0000000..fe28d3a
--- /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 0000000..11fbbd8
--- /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('<foo><bar>baz</bar><bar>baz</bar></foo>');
+
+ const formattedXml = await page.getByTestId('area-content').innerText();
+
+ expect(formattedXml.trim()).toEqual(`
+<foo>
+ <bar>baz</bar>
+ <bar>baz</bar>
+</foo>`.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 0000000..2b14558
--- /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 = '<hello><world>foo</world><world>bar</world></hello>';
+
+ expect(formatXml(initString)).toMatchInlineSnapshot(`
+ "<hello>
+ <world>
+ foo
+ </world>
+ <world>
+ bar
+ </world>
+ </hello>"
+ `);
+ });
+
+ 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 0000000..3441f0b
--- /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 0000000..d59cf8c
--- /dev/null
+++ b/src/tools/xml-formatter/xml-formatter.vue
@@ -0,0 +1,46 @@
+<script setup lang="ts">
+import { formatXml, isValidXML } from './xml-formatter.service';
+import type { UseValidationRule } from '@/composable/validation';
+
+const defaultValue = '<hello><world>foo</world><world>bar</world></hello>';
+const indentSize = useStorage('xml-formatter:indent-size', 2);
+const collapseContent = useStorage('xml-formatter:collapse-content', true);
+
+function transformer(value: string) {
+ return formatXml(value, {
+ indentation: ' '.repeat(indentSize.value),
+ collapseContent: collapseContent.value,
+ lineSeparator: '\n',
+ });
+}
+
+const rules: UseValidationRule<string>[] = [
+ {
+ validator: isValidXML,
+ message: 'Provided XML is not valid.',
+ },
+];
+</script>
+
+<template>
+ <div important:flex-full important:flex-shrink-0 important:flex-grow-0>
+ <div flex justify-center>
+ <n-form-item label="Collapse content:" label-placement="left">
+ <n-switch v-model:value="collapseContent" />
+ </n-form-item>
+ <n-form-item label="Indent size:" label-placement="left" label-width="100" :show-feedback="false">
+ <n-input-number v-model:value="indentSize" min="0" max="10" w-100px />
+ </n-form-item>
+ </div>
+ </div>
+
+ <format-transformer
+ input-label="Your XML"
+ input-placeholder="Paste your XML here..."
+ output-label="Formatted XML from your XML"
+ output-language="xml"
+ :input-validation-rules="rules"
+ :transformer="transformer"
+ :input-default="defaultValue"
+ />
+</template>