From a986e86019226e88b2199ca214aec4a44a83f95c Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 13:03:55 +0000 Subject: [PATCH 1/8] feat: csv-to-tsv (service) --- src/pages/tools/csv/csv-to-tsv/service.ts | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/pages/tools/csv/csv-to-tsv/service.ts diff --git a/src/pages/tools/csv/csv-to-tsv/service.ts b/src/pages/tools/csv/csv-to-tsv/service.ts new file mode 100644 index 0000000..98ecaf0 --- /dev/null +++ b/src/pages/tools/csv/csv-to-tsv/service.ts @@ -0,0 +1,35 @@ +function unquoteIfQuoted(value: string, quoteCharacter: string): string { + if (value.startsWith(quoteCharacter) && value.endsWith(quoteCharacter)) { + return value.slice(1, -1); // Remove first and last character + } + return value; +} +export function csvToTsv( + input: string, + delimiter: string, + quoteCharacter: string, + commentCharacter: string, + header: boolean, + emptyLines: boolean +): string { + // edge case: if input is empty, return empty string + if (!input) return ''; + const lines = input.split('\n'); + // is header is set to false, remove the first line + if (!header) lines.shift(); + + const tsvLines = lines.map((line) => { + // if comment character is set, remove the lines that start with it + if (commentCharacter && line.startsWith(commentCharacter)) return ''; + const cells = line.split(delimiter); + cells.forEach((cell, index) => { + cells[index] = unquoteIfQuoted(cell, quoteCharacter); + }); + return cells.join('\t'); + }); + // if empty lines is set to true, remove the empty lines + + return !emptyLines + ? tsvLines.join('\n') + : tsvLines.filter((line) => line.trim() !== '').join('\n'); +} From fe0e936554f38b2001f952bdae8f82682d149a2c Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 13:04:30 +0000 Subject: [PATCH 2/8] feat: csv-to-tsv (test file) --- .../csv/csv-to-tsv/csv-to-tsv.service.test.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/pages/tools/csv/csv-to-tsv/csv-to-tsv.service.test.ts diff --git a/src/pages/tools/csv/csv-to-tsv/csv-to-tsv.service.test.ts b/src/pages/tools/csv/csv-to-tsv/csv-to-tsv.service.test.ts new file mode 100644 index 0000000..139878c --- /dev/null +++ b/src/pages/tools/csv/csv-to-tsv/csv-to-tsv.service.test.ts @@ -0,0 +1,82 @@ +import { describe, it, expect } from 'vitest'; +import { csvToTsv } from './service'; + +describe('csvToTsv', () => { + it('should convert CSV to TSV with default settings', () => { + const input = 'a,b,c\n1,2,3\n4,5,6'; + const result = csvToTsv(input, ',', '"', '', true, false); + expect(result).toBe('a\tb\tc\n1\t2\t3\n4\t5\t6'); + }); + + it('should handle quoted values correctly', () => { + const input = '"a","b","c"\n"1","2","3"'; + const result = csvToTsv(input, ',', '"', '', true, false); + expect(result).toBe('a\tb\tc\n1\t2\t3'); + }); + + it('should remove comments if commentCharacter is set', () => { + const input = '#comment\na,b,c\n1,2,3'; + const result = csvToTsv(input, ',', '"', '#', true, false); + expect(result).toBe('\na\tb\tc\n1\t2\t3'); + }); + + it('should remove comments if commentCharacter is set, emptyline set to true', () => { + const input = '#comment\na,b,c\n1,2,3'; + const result = csvToTsv(input, ',', '"', '#', true, true); + expect(result).toBe('a\tb\tc\n1\t2\t3'); + }); + + it('should remove empty lines if emptyLines is true', () => { + const input = 'a,b,c\n\n1,2,3\n\n4,5,6'; + const result = csvToTsv(input, ',', '"', '', true, true); + expect(result).toBe('a\tb\tc\n1\t2\t3\n4\t5\t6'); + }); + + it('should not unquote if value is not properly quoted', () => { + const input = '"a,b,c\n\n1,2,3\n\n4,5,6'; + const result = csvToTsv(input, ',', '"', '', true, true); + expect(result).toBe('"a\tb\tc\n1\t2\t3\n4\t5\t6'); + }); + + it('should unquote if value is properly quoted', () => { + const input = '"a",b,c\n\n1,"2",3\n\n"4",5,6'; + const result = csvToTsv(input, ',', '"', '', true, true); + expect(result).toBe('a\tb\tc\n1\t2\t3\n4\t5\t6'); + }); + + it('should exclude the header if header is false', () => { + const input = 'a,b,c\n1,2,3\n4,5,6'; + const result = csvToTsv(input, ',', '"', '', false, false); + expect(result).toBe('1\t2\t3\n4\t5\t6'); + }); + + it('should handle empty input gracefully', () => { + const input = ''; + const result = csvToTsv(input, ',', '"', '', true, false); + expect(result).toBe(''); + }); + + it('should handle input with only comments and empty lines', () => { + const input = '#comment\n\n#another comment\n'; + const result = csvToTsv(input, ',', '"', '#', true, true); + expect(result).toBe(''); + }); + + it('should handle custom delimiters', () => { + const input = 'a|b|c\n1|2|3\n4|5|6'; + const result = csvToTsv(input, '|', '"', '', true, false); + expect(result).toBe('a\tb\tc\n1\t2\t3\n4\t5\t6'); + }); + + it('should handle custom quote characters', () => { + const input = "'a','b','c'\n'1','2','3'"; + const result = csvToTsv(input, ',', "'", '', true, false); + expect(result).toBe('a\tb\tc\n1\t2\t3'); + }); + + it('should handle mixed quoted and unquoted values', () => { + const input = '"a",b,"c"\n"1",2,"3"'; + const result = csvToTsv(input, ',', '"', '', true, false); + expect(result).toBe('a\tb\tc\n1\t2\t3'); + }); +}); From 6be494a4b99f2c097b5acf21a7f341317b8a7ae2 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 13:05:04 +0000 Subject: [PATCH 3/8] feat: csv-to-tsv(index file) --- src/pages/tools/csv/csv-to-tsv/index.tsx | 185 +++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/pages/tools/csv/csv-to-tsv/index.tsx diff --git a/src/pages/tools/csv/csv-to-tsv/index.tsx b/src/pages/tools/csv/csv-to-tsv/index.tsx new file mode 100644 index 0000000..22a7a4e --- /dev/null +++ b/src/pages/tools/csv/csv-to-tsv/index.tsx @@ -0,0 +1,185 @@ +import React, { useState } from 'react'; +import { Box } from '@mui/material'; +import ToolContent from '@components/ToolContent'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { ToolComponentProps } from '@tools/defineTool'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import { csvToTsv } from './service'; + +const initialValues = { + delimiter: ',', + quoteCharacter: '"', + commentCharacter: '#', + header: true, + emptyLines: true +}; +type InitialValuesType = typeof initialValues; +const exampleCards: CardExampleType[] = [ + { + title: 'Convert Game Data from the CSV Format to the TSV Format', + description: + 'This tool transforms Comma Separated Values (CSV) data to Tab Separated Values (TSV) data. Both CSV and TSV are popular file formats for storing tabular data but they use different delimiters to separate values – CSV uses commas (","), while TSV uses tabs ("\t"). If we compare CSV files to TSV files, then CSV files are much harder to parse than TSV files because the values themselves may contain commas, so it is not always obvious where one field starts and ends without complicated parsing rules. TSV, on the other hand, uses just a tab symbol, which does not usually appear in data, so separating fields in TSV is as simple as splitting the input by the tab character. To convert CSV to TSV, simply input the CSV data in the input of this tool. In rare cases when a CSV file has a delimiter other than a comma, you can specify the current delimiter in the options of the tool. You can also specify the current quote character and the comment start character. Additionally, empty CSV lines can be skipped by activating the "Ignore Lines with No Data" option. If this option is off, then empty lines in the CSV are converted to empty TSV lines. The "Preserve Headers" option allows you to choose whether to process column headers of a CSV file. If the option is selected, then the resulting TSV file will include the first row of the input CSV file, which contains the column names. Alternatively, if the headers option is not selected, the first row will be skipped during the data conversion process. For the reverse conversion from TSV to CSV, you can use our Convert TSV to CSV tool. Csv-abulous!', + sampleText: `player_name,score,time,goals +ToniJackson,2500,30:00,15 +HenryDalton,1800,25:00,12 +DavidLee,3200,40:00,20 +EmmaJones,2100,35:00,17 +KrisDavis,1500,20:00,10`, + sampleResult: `player_name score time goals +ToniJackson 2500 30:00 15 +HenryDalton 1800 25:00 12 +DavidLee 3200 40:00 20 +EmmaJones 2100 35:00 17 +KrisDavis 1500 20:00 10`, + sampleOptions: { + delimiter: ',', + quoteCharacter: '"', + commentCharacter: '#', + header: true, + emptyLines: true + } + }, + { + title: 'Mythical Creatures', + description: + 'In this example, we load a CSV file containing coffee varieties and their origins. The file is quite messy, with numerous empty lines and comments, and it is hard to work with. To clean up the file, we specify the comment pattern // in the options, and the program automatically removes the comment lines from the input. Also, the empty lines are automatically removed. Once the file is cleaned up, we transform the five clean rows into five columns, each having a height of two fields.', + sampleText: `creature;origin;habitat;powers +Unicorn;Mythology;Forest;Magic horn +Mermaid;Mythology;Ocean;Hypnotic singing +Vampire;Mythology;Castles;Immortality +Phoenix;Mythology;Desert;Rebirth from ashes + +#Dragon;Mythology;Mountains;Fire breathing +#Werewolf;Mythology;Forests;Shapeshifting`, + sampleResult: `Unicorn Mythology Forest Magic horn +Mermaid Mythology Ocean Hypnotic singing +Vampire Mythology Castles Immortality +Phoenix Mythology Desert Rebirth from ashes`, + sampleOptions: { + delimiter: ';', + quoteCharacter: '"', + commentCharacter: '#', + header: false, + emptyLines: true + } + }, + { + title: 'Convet Fitness Tracker Data from CSV to TSV', + description: + 'In this example, we swap rows and columns in CSV data about team sports, the equipment used, and the number of players. The input has 5 rows and 3 columns and once rows and columns have been swapped, the output has 3 rows and 5 columns. Also notice that in the last data record, for the "Baseball" game, the number of players is missing. To create a fully-filled CSV, we use a custom message "NA", specified in the options, and fill the missing CSV field with this value.', + sampleText: `Sday,steps,distance,calories + +Mon,7500,3.75,270 +Tue,12000,6.00,420 + +Wed,8000,4.00,300 +Thu,9500,4.75,330 +Fri,10000,5.00,350`, + sampleResult: `day steps distance calories +Mon 7500 3.75 270 +Tue 12000 6.00 420 +Wed 8000 4.00 300 +Thu 9500 4.75 330 +Fri 10000 5.00 350`, + sampleOptions: { + delimiter: ',', + quoteCharacter: '"', + commentCharacter: '#', + header: true, + emptyLines: true + } + } +]; + +export default function CsvToTsv({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (optionsValues: typeof initialValues, input: string) => { + setResult( + csvToTsv( + input, + optionsValues.delimiter, + optionsValues.quoteCharacter, + optionsValues.commentCharacter, + optionsValues.header, + optionsValues.emptyLines + ) + ); + }; + + const getGroups: GetGroupsType = ({ + values, + updateField + }) => [ + { + title: 'CSV Format Options', + component: ( + + updateField('delimiter', val)} + description={ + 'Enter the character used to delimit columns in the CSV file.' + } + /> + updateField('quoteCharacter', val)} + description={ + 'Enter the quote character used to quote the CSV fields.' + } + /> + updateField('commentCharacter', val)} + description={ + 'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.' + } + /> + + ) + }, + { + title: 'Conversion Options', + component: ( + + updateField('header', value)} + title="Use Headers" + description="Keep the first row as column names." + /> + updateField('emptyLines', value)} + title="Ignore Lines with No Data" + description="Enable to prevent the conversion of empty lines in the input CSV file." + /> + + ) + } + ]; + + return ( + } + resultComponent={} + initialValues={initialValues} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + exampleCards={exampleCards} + /> + ); +} From 95e1a4ed6dc459c608e2ccceb9e5acde28fb74cd Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 13:07:35 +0000 Subject: [PATCH 4/8] feat: csv-to-tsv (meta file) --- src/pages/tools/csv/csv-to-tsv/meta.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/pages/tools/csv/csv-to-tsv/meta.ts diff --git a/src/pages/tools/csv/csv-to-tsv/meta.ts b/src/pages/tools/csv/csv-to-tsv/meta.ts new file mode 100644 index 0000000..eb2fb18 --- /dev/null +++ b/src/pages/tools/csv/csv-to-tsv/meta.ts @@ -0,0 +1,13 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('csv', { + name: 'Convert CSV to TSV', + path: 'csv-to-tsv', + icon: 'codicon:keyboard-tab', + description: + 'Upload your CSV file in the form below and it will automatically get converted to a TSV file. In the tool options, you can customize the input CSV format – specify the field delimiter, quotation character, and comment symbol, as well as skip empty CSV lines, and choose whether to preserve CSV column headers.', + shortDescription: 'Convert CSV data to TSV format', + keywords: ['csv', 'tsv', 'convert', 'transform', 'parse'], + component: lazy(() => import('./index')) +}); From c518161ceb9fa12cf80109a6b7425c4e7ccf2214 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 13:08:07 +0000 Subject: [PATCH 5/8] feat: csv-to-tsv (import it as csv tool) --- src/pages/tools/csv/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/csv/index.ts b/src/pages/tools/csv/index.ts index 3480a71..b84513c 100644 --- a/src/pages/tools/csv/index.ts +++ b/src/pages/tools/csv/index.ts @@ -1,5 +1,6 @@ import { tool as csvToJson } from './csv-to-json/meta'; import { tool as csvToXml } from './csv-to-xml/meta'; import { tool as csvToRowsColumns } from './csv-rows-to-columns/meta'; +import { tool as csvToTsv } from './csv-to-tsv/meta'; -export const csvTools = [csvToJson, csvToXml, csvToRowsColumns]; +export const csvTools = [csvToJson, csvToXml, csvToRowsColumns, csvToTsv]; From 42447dbf77422ea035507d3223380a1b6d2cafd1 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 15:10:10 +0000 Subject: [PATCH 6/8] fix: leading S removal --- src/pages/tools/csv/csv-to-tsv/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tools/csv/csv-to-tsv/index.tsx b/src/pages/tools/csv/csv-to-tsv/index.tsx index 22a7a4e..995cf87 100644 --- a/src/pages/tools/csv/csv-to-tsv/index.tsx +++ b/src/pages/tools/csv/csv-to-tsv/index.tsx @@ -71,7 +71,7 @@ Phoenix Mythology Desert Rebirth from ashes`, title: 'Convet Fitness Tracker Data from CSV to TSV', description: 'In this example, we swap rows and columns in CSV data about team sports, the equipment used, and the number of players. The input has 5 rows and 3 columns and once rows and columns have been swapped, the output has 3 rows and 5 columns. Also notice that in the last data record, for the "Baseball" game, the number of players is missing. To create a fully-filled CSV, we use a custom message "NA", specified in the options, and fill the missing CSV field with this value.', - sampleText: `Sday,steps,distance,calories + sampleText: `day,steps,distance,calories Mon,7500,3.75,270 Tue,12000,6.00,420 From dd8ed76dcff1af32aefe1904f1bec8a46dfc7568 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Sun, 30 Mar 2025 15:34:46 +0000 Subject: [PATCH 7/8] fix: longDesc for csv-to-tsv --- src/pages/tools/csv/csv-to-tsv/meta.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/tools/csv/csv-to-tsv/meta.ts b/src/pages/tools/csv/csv-to-tsv/meta.ts index eb2fb18..11f2cb2 100644 --- a/src/pages/tools/csv/csv-to-tsv/meta.ts +++ b/src/pages/tools/csv/csv-to-tsv/meta.ts @@ -8,6 +8,8 @@ export const tool = defineTool('csv', { description: 'Upload your CSV file in the form below and it will automatically get converted to a TSV file. In the tool options, you can customize the input CSV format – specify the field delimiter, quotation character, and comment symbol, as well as skip empty CSV lines, and choose whether to preserve CSV column headers.', shortDescription: 'Convert CSV data to TSV format', + longDescription: + 'This tool transforms Comma Separated Values (CSV) data to Tab Separated Values (TSV) data. Both CSV and TSV are popular file formats for storing tabular data but they use different delimiters to separate values – CSV uses commas (","), while TSV uses tabs ("\t"). If we compare CSV files to TSV files, then CSV files are much harder to parse than TSV files because the values themselves may contain commas, so it is not always obvious where one field starts and ends without complicated parsing rules. TSV, on the other hand, uses just a tab symbol, which does not usually appear in data, so separating fields in TSV is as simple as splitting the input by the tab character. To convert CSV to TSV, simply input the CSV data in the input of this tool. In rare cases when a CSV file has a delimiter other than a comma, you can specify the current delimiter in the options of the tool. You can also specify the current quote character and the comment start character. Additionally, empty CSV lines can be skipped by activating the "Ignore Lines with No Data" option. If this option is off, then empty lines in the CSV are converted to empty TSV lines. The "Preserve Headers" option allows you to choose whether to process column headers of a CSV file. If the option is selected, then the resulting TSV file will include the first row of the input CSV file, which contains the column names. Alternatively, if the headers option is not selected, the first row will be skipped during the data conversion process. For the reverse conversion from TSV to CSV, you can use our Convert TSV to CSV tool. Csv-abulous!', keywords: ['csv', 'tsv', 'convert', 'transform', 'parse'], component: lazy(() => import('./index')) }); From 4d45c694b78ddbdf8b9647495f47115be7008394 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sun, 30 Mar 2025 15:48:48 +0000 Subject: [PATCH 8/8] fix: typos --- src/pages/tools/csv/csv-to-tsv/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/tools/csv/csv-to-tsv/index.tsx b/src/pages/tools/csv/csv-to-tsv/index.tsx index 995cf87..49898df 100644 --- a/src/pages/tools/csv/csv-to-tsv/index.tsx +++ b/src/pages/tools/csv/csv-to-tsv/index.tsx @@ -22,7 +22,7 @@ const exampleCards: CardExampleType[] = [ { title: 'Convert Game Data from the CSV Format to the TSV Format', description: - 'This tool transforms Comma Separated Values (CSV) data to Tab Separated Values (TSV) data. Both CSV and TSV are popular file formats for storing tabular data but they use different delimiters to separate values – CSV uses commas (","), while TSV uses tabs ("\t"). If we compare CSV files to TSV files, then CSV files are much harder to parse than TSV files because the values themselves may contain commas, so it is not always obvious where one field starts and ends without complicated parsing rules. TSV, on the other hand, uses just a tab symbol, which does not usually appear in data, so separating fields in TSV is as simple as splitting the input by the tab character. To convert CSV to TSV, simply input the CSV data in the input of this tool. In rare cases when a CSV file has a delimiter other than a comma, you can specify the current delimiter in the options of the tool. You can also specify the current quote character and the comment start character. Additionally, empty CSV lines can be skipped by activating the "Ignore Lines with No Data" option. If this option is off, then empty lines in the CSV are converted to empty TSV lines. The "Preserve Headers" option allows you to choose whether to process column headers of a CSV file. If the option is selected, then the resulting TSV file will include the first row of the input CSV file, which contains the column names. Alternatively, if the headers option is not selected, the first row will be skipped during the data conversion process. For the reverse conversion from TSV to CSV, you can use our Convert TSV to CSV tool. Csv-abulous!', + 'In this example, we transform a Comma Separated Values (CSV) file containing a leaderboard of gaming data into a Tab Separated Values (TSV) file. The input data shows the players\' names, scores, times, and goals. We preserve the CSV column headers by enabling the "Preserve Headers" option and convert all data rows into TSV format. The resulting data is easier to work with as it\'s organized in neat columns', sampleText: `player_name,score,time,goals ToniJackson,2500,30:00,15 HenryDalton,1800,25:00,12 @@ -54,7 +54,7 @@ Vampire;Mythology;Castles;Immortality Phoenix;Mythology;Desert;Rebirth from ashes #Dragon;Mythology;Mountains;Fire breathing -#Werewolf;Mythology;Forests;Shapeshifting`, +#Werewolf;Mythology;Forests;Shape shifting`, sampleResult: `Unicorn Mythology Forest Magic horn Mermaid Mythology Ocean Hypnotic singing Vampire Mythology Castles Immortality @@ -68,7 +68,7 @@ Phoenix Mythology Desert Rebirth from ashes`, } }, { - title: 'Convet Fitness Tracker Data from CSV to TSV', + title: 'Convert Fitness Tracker Data from CSV to TSV', description: 'In this example, we swap rows and columns in CSV data about team sports, the equipment used, and the number of players. The input has 5 rows and 3 columns and once rows and columns have been swapped, the output has 3 rows and 5 columns. Also notice that in the last data record, for the "Baseball" game, the number of players is missing. To create a fully-filled CSV, we use a custom message "NA", specified in the options, and fill the missing CSV field with this value.', sampleText: `day,steps,distance,calories