diff --git a/src/pages/tools/csv/csv-to-xml/index.tsx b/src/pages/tools/csv/csv-to-xml/index.tsx new file mode 100644 index 0000000..68cf1d6 --- /dev/null +++ b/src/pages/tools/csv/csv-to-xml/index.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { convertCsvToXml } 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 CsvToXml({ title }: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (values: InitialValuesType, input: string) => { + if (input) { + try { + const xmlResult = convertCsvToXml(input, { + delimiter: values.delimiter, + quote: values.quote, + comment: values.comment, + useHeaders: values.useHeaders, + skipEmptyLines: values.skipEmptyLines + }); + setResult(xmlResult); + } catch (error) { + setResult( + `Error: ${ + error instanceof Error ? error.message : 'Invalid CSV format' + }` + ); + } + } + }; + + return ( + + } + resultComponent={} + getGroups={({ values, updateField }) => [ + { + title: 'Input CSV 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/csv/csv-to-xml/meta.ts b/src/pages/tools/csv/csv-to-xml/meta.ts new file mode 100644 index 0000000..7a64a43 --- /dev/null +++ b/src/pages/tools/csv/csv-to-xml/meta.ts @@ -0,0 +1,12 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('csv', { + name: 'Convert CSV to XML', + path: 'csv-to-xml', + icon: 'lets-icons:xml-light', + description: 'Convert CSV files to XML format with customizable options.', + shortDescription: 'Convert CSV data to XML format', + keywords: ['csv', 'xml', 'convert', 'transform', 'parse'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/csv/csv-to-xml/service.ts b/src/pages/tools/csv/csv-to-xml/service.ts new file mode 100644 index 0000000..a8ea452 --- /dev/null +++ b/src/pages/tools/csv/csv-to-xml/service.ts @@ -0,0 +1,76 @@ +type CsvToXmlOptions = { + delimiter: string; + quote: string; + comment: string; + useHeaders: boolean; + skipEmptyLines: boolean; +}; + +const defaultOptions: CsvToXmlOptions = { + delimiter: ',', + quote: '"', + comment: '#', + useHeaders: true, + skipEmptyLines: true +}; + +export const convertCsvToXml = ( + csv: string, + options: Partial = {} +): string => { + const opts = { ...defaultOptions, ...options }; + const lines = csv.split('\n').map((line) => line.trim()); + + let xmlResult = `\n\n`; + let headers: string[] = []; + + const validLines = lines.filter( + (line) => + line && + !line.startsWith(opts.comment) && + (!opts.skipEmptyLines || line.trim() !== '') + ); + + if (validLines.length === 0) { + return `\n`; + } + + if (opts.useHeaders) { + headers = parseCsvLine(validLines[0], opts); + validLines.shift(); + } + + validLines.forEach((line, index) => { + const values = parseCsvLine(line, opts); + xmlResult += ` \n`; + headers.forEach((header, i) => { + xmlResult += ` <${header}>${values[i] || ''}\n`; + }); + xmlResult += ` \n`; + }); + + xmlResult += ``; + return xmlResult; +}; + +const parseCsvLine = (line: string, options: CsvToXmlOptions): string[] => { + const values: string[] = []; + let currentValue = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + + if (char === options.quote) { + inQuotes = !inQuotes; + } else if (char === options.delimiter && !inQuotes) { + values.push(currentValue.trim()); + currentValue = ''; + } else { + currentValue += char; + } + } + + values.push(currentValue.trim()); + return values; +}; diff --git a/src/pages/tools/csv/index.ts b/src/pages/tools/csv/index.ts index 6caa28c..d21922b 100644 --- a/src/pages/tools/csv/index.ts +++ b/src/pages/tools/csv/index.ts @@ -1,3 +1,4 @@ import { tool as csvToJson } from './csv-to-json/meta'; +import { tool as csvToXml } from './csv-to-xml/meta'; -export const csvTools = [csvToJson]; +export const csvTools = [csvToJson, csvToXml];