From f3400dc12cda85c8b3c3b59be31d45ef3ec1f512 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Fri, 28 Mar 2025 17:01:12 +0000 Subject: [PATCH 1/6] feat: csv-rows-to-columns --- .../csv-rows-to-columns.service.test.ts | 61 +++++++ .../tools/csv/csv-rows-to-columns/index.tsx | 154 ++++++++++++++++++ .../tools/csv/csv-rows-to-columns/meta.ts | 13 ++ .../tools/csv/csv-rows-to-columns/service.ts | 34 ++++ 4 files changed, 262 insertions(+) create mode 100644 src/pages/tools/csv/csv-rows-to-columns/csv-rows-to-columns.service.test.ts create mode 100644 src/pages/tools/csv/csv-rows-to-columns/index.tsx create mode 100644 src/pages/tools/csv/csv-rows-to-columns/meta.ts create mode 100644 src/pages/tools/csv/csv-rows-to-columns/service.ts diff --git a/src/pages/tools/csv/csv-rows-to-columns/csv-rows-to-columns.service.test.ts b/src/pages/tools/csv/csv-rows-to-columns/csv-rows-to-columns.service.test.ts new file mode 100644 index 0000000..7faf0e7 --- /dev/null +++ b/src/pages/tools/csv/csv-rows-to-columns/csv-rows-to-columns.service.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { csvRowsToColumns } from './service'; + +describe('csvRowsToColumns', () => { + it('should transpose rows to columns', () => { + const input = 'a,b,c\n1,2,3\nx,y,z'; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe('a,1,x\nb,2,y\nc,3,z'); + }); + + it('should fill empty values with custom filler', () => { + const input = 'a,b\n1\nx,y,z'; + const result = csvRowsToColumns(input, false, 'FILL', '#'); + expect(result).toBe('a,1,x\nb,FILL,y\nFILL,FILL,z'); + }); + + it('should fill empty values with empty strings when emptyValuesFilling is true', () => { + const input = 'a,b\n1\nx,y,z'; + const result = csvRowsToColumns(input, true, 'FILL', '#'); + expect(result).toBe('a,1,x\nb,,y\n,,z'); + }); + + it('should ignore rows starting with the comment character', () => { + const input = '#comment\n1,2,3\nx,y,z'; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe('1,x\n2,y\n3,z'); + }); + + it('should handle an empty input', () => { + const input = ''; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe(''); + }); + + it('should handle input with only comments', () => { + const input = '#comment\n#another comment'; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe(''); + }); + + it('should handle single row input', () => { + const input = 'a,b,c'; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe('a\nb\nc'); + }); + + it('should handle single column input', () => { + const input = 'a\nb\nc'; + const result = csvRowsToColumns(input, false, '', '#'); + expect(result).toBe('a,b,c'); + }); + + it('should handle this case onlinetools #1', () => { + const input = + 'Variety,Origin\nArabica,Ethiopia\nRobusta,Africa\nLiberica,Philippines\nMocha,\n//green tea'; + const result = csvRowsToColumns(input, false, '1x', '//'); + expect(result).toBe( + 'Variety,Arabica,Robusta,Liberica,Mocha\nOrigin,Ethiopia,Africa,Philippines,1x' + ); + }); +}); diff --git a/src/pages/tools/csv/csv-rows-to-columns/index.tsx b/src/pages/tools/csv/csv-rows-to-columns/index.tsx new file mode 100644 index 0000000..5083c2e --- /dev/null +++ b/src/pages/tools/csv/csv-rows-to-columns/index.tsx @@ -0,0 +1,154 @@ +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 SelectWithDesc from '@components/options/SelectWithDesc'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import { csvRowsToColumns } from './service'; + +const initialValues = { + emptyValuesFilling: false, + customFiller: 'x', + commentCharacter: '//' +}; +type InitialValuesType = typeof initialValues; +const exampleCards: CardExampleType[] = [ + { + title: 'Convert CSV Rows to Columns', + description: + 'In this example, we transform the input CSV file with a single horizontal row of six values "a,b,c,d,e,f" into a vertical column. The program takes this row, rotates it 90 degrees, and outputs it as a column with each CSV value on a new line. This operation can also be viewed as converting a 6-dimensional row vector into a 6-dimensional column vector.', + sampleText: `a,b,c,d,e,f`, + sampleResult: `a +b +c +d +e +f`, + sampleOptions: { + emptyValuesFilling: true, + customFiller: '1', + commentCharacter: '#' + } + }, + { + title: 'Rows to Columns Transformation', + 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: `Variety,Origin +Arabica,Ethiopia + +Robusta,Africa +Liberica,Philippines + +Mocha,Yemen +//green tea`, + sampleResult: `Variety,Arabica,Robusta,Liberica,Mocha +Origin,Ethiopia,Africa,Philippines,Yemen`, + sampleOptions: { + emptyValuesFilling: true, + customFiller: '1', + commentCharacter: '//' + } + }, + { + title: 'Fill Missing Data', + 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: `Sport,Equipment,Players +Basketball,Ball,5 +Football,Ball,11 +Soccer,Ball,11 +Baseball,Bat & Ball`, + sampleResult: `Sport,Basketball,Football,Soccer,Baseball +Equipment,Ball,Ball,Ball,Bat & Ball +Players,5,11,11,NA`, + sampleOptions: { + emptyValuesFilling: false, + customFiller: 'NA', + commentCharacter: '#' + } + } +]; + +export default function CsvRowsToColumns({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (optionsValues: typeof initialValues, input: string) => { + setResult( + csvRowsToColumns( + input, + optionsValues.emptyValuesFilling, + optionsValues.customFiller, + optionsValues.commentCharacter + ) + ); + }; + + const getGroups: GetGroupsType = ({ + values, + updateField + }) => [ + { + title: 'Fix incomplete data', + component: ( + + updateField('emptyValuesFilling', value)} + description={ + 'If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?' + } + /> + updateField('customFiller', val)} + description={ + 'Use this custom value to fill in missing fields. (Works only with "Custom Values" mode above.)' + } + /> + + ) + }, + { + title: 'Lines with comments', + component: ( + + updateField('commentCharacter', val)} + description={ + 'Enter the symbol indicating the start of a comment line. (These lines are removed during conversion.)' + } + /> + + ) + } + ]; + + return ( + } + resultComponent={} + initialValues={initialValues} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + exampleCards={exampleCards} + /> + ); +} diff --git a/src/pages/tools/csv/csv-rows-to-columns/meta.ts b/src/pages/tools/csv/csv-rows-to-columns/meta.ts new file mode 100644 index 0000000..1ef4135 --- /dev/null +++ b/src/pages/tools/csv/csv-rows-to-columns/meta.ts @@ -0,0 +1,13 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('csv', { + name: 'Convert CSV Rows to Columns', + path: 'csv-rows-to-columns', + icon: 'carbon:transpose', + description: + 'This tool converts rows of a CSV (Comma Separated Values) file into columns. It extracts the horizontal lines from the input CSV one by one, rotates them 90 degrees, and outputs them as vertical columns one after another, separated by commas. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as "missing", "?", or "x". During the conversion process, the tool also cleans the CSV file from unnecessary information, such as empty lines (these are lines without visible information) and comments. To help the tool correctly identify comments, in the options, you can specify the symbol at the beginning of a line that starts a comment. This symbol is typically a hash "#" or double slash "//". Csv-abulous!.', + shortDescription: 'Convert CSV data to JSON format', + keywords: ['csv', 'rows', 'columns', 'transpose'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/csv/csv-rows-to-columns/service.ts b/src/pages/tools/csv/csv-rows-to-columns/service.ts new file mode 100644 index 0000000..137bac4 --- /dev/null +++ b/src/pages/tools/csv/csv-rows-to-columns/service.ts @@ -0,0 +1,34 @@ +function compute(rows: string[][], columnCount: number): string[][] { + const result: string[][] = []; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + for (let j = 0; j < columnCount; j++) { + if (!result[j]) { + result[j] = []; + } + result[j][i] = row[j]; + } + } + return result; +} +export function csvRowsToColumns( + input: string, + emptyValuesFilling: boolean, + customFiller: string, + commentCharacter: string +): string { + const rows = input + .split('\n') + .map((row) => row.split(',')) + .filter((row) => !row[0].trim().startsWith(commentCharacter)); + const columnCount = Math.max(...rows.map((row) => row.length)); + for (let i = 0; i < rows.length; i++) { + for (let j = 0; j < columnCount; j++) { + if (!rows[i][j]) { + rows[i][j] = emptyValuesFilling ? '' : customFiller; + } + } + } + const result = compute(rows, columnCount); + return result.join('\n'); +} From 2280059e1488c374230eb486d67e680fa8ce4ec1 Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Fri, 28 Mar 2025 17:14:19 +0000 Subject: [PATCH 2/6] feat: csv-rows-to-columns --- 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 d21922b..3480a71 100644 --- a/src/pages/tools/csv/index.ts +++ b/src/pages/tools/csv/index.ts @@ -1,4 +1,5 @@ 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'; -export const csvTools = [csvToJson, csvToXml]; +export const csvTools = [csvToJson, csvToXml, csvToRowsColumns]; From 39cf7aa4e3a537acf18eaa9480885b04b0ff4f5d Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Fri, 28 Mar 2025 19:52:14 +0000 Subject: [PATCH 3/6] fix: removing empty lines --- src/pages/tools/csv/csv-rows-to-columns/service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/csv/csv-rows-to-columns/service.ts b/src/pages/tools/csv/csv-rows-to-columns/service.ts index 137bac4..19d11dc 100644 --- a/src/pages/tools/csv/csv-rows-to-columns/service.ts +++ b/src/pages/tools/csv/csv-rows-to-columns/service.ts @@ -20,7 +20,9 @@ export function csvRowsToColumns( const rows = input .split('\n') .map((row) => row.split(',')) - .filter((row) => !row[0].trim().startsWith(commentCharacter)); + .filter( + (row) => row.length > 1 && !row[0].trim().startsWith(commentCharacter) + ); const columnCount = Math.max(...rows.map((row) => row.length)); for (let i = 0; i < rows.length; i++) { for (let j = 0; j < columnCount; j++) { From 968a354d8401e7e6c9352bccbba4e3be4616491d Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Fri, 28 Mar 2025 19:59:59 +0000 Subject: [PATCH 4/6] fix: condition fixed --- src/pages/tools/csv/csv-rows-to-columns/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tools/csv/csv-rows-to-columns/service.ts b/src/pages/tools/csv/csv-rows-to-columns/service.ts index 19d11dc..93399ee 100644 --- a/src/pages/tools/csv/csv-rows-to-columns/service.ts +++ b/src/pages/tools/csv/csv-rows-to-columns/service.ts @@ -21,7 +21,7 @@ export function csvRowsToColumns( .split('\n') .map((row) => row.split(',')) .filter( - (row) => row.length > 1 && !row[0].trim().startsWith(commentCharacter) + (row) => row.length > 0 && !row[0].trim().startsWith(commentCharacter) ); const columnCount = Math.max(...rows.map((row) => row.length)); for (let i = 0; i < rows.length; i++) { From 7979025c6f923e7653d54278777f919bf9fba695 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Fri, 28 Mar 2025 20:47:52 +0000 Subject: [PATCH 5/6] chore: check emptyValuesFilling while rendering --- .../tools/csv/csv-rows-to-columns/index.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pages/tools/csv/csv-rows-to-columns/index.tsx b/src/pages/tools/csv/csv-rows-to-columns/index.tsx index 5083c2e..8b70b75 100644 --- a/src/pages/tools/csv/csv-rows-to-columns/index.tsx +++ b/src/pages/tools/csv/csv-rows-to-columns/index.tsx @@ -111,13 +111,15 @@ export default function CsvRowsToColumns({ 'If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?' } /> - updateField('customFiller', val)} - description={ - 'Use this custom value to fill in missing fields. (Works only with "Custom Values" mode above.)' - } - /> + {!values.emptyValuesFilling && ( + updateField('customFiller', val)} + description={ + 'Use this custom value to fill in missing fields. (Works only with "Custom Values" mode above.)' + } + /> + )} ) }, From 68f82f442b81ea896d2d1ceb0fc634d6fc182e3f Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Fri, 28 Mar 2025 20:51:18 +0000 Subject: [PATCH 6/6] fix: description --- src/pages/tools/csv/csv-rows-to-columns/meta.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/csv/csv-rows-to-columns/meta.ts b/src/pages/tools/csv/csv-rows-to-columns/meta.ts index 1ef4135..e4d8c3d 100644 --- a/src/pages/tools/csv/csv-rows-to-columns/meta.ts +++ b/src/pages/tools/csv/csv-rows-to-columns/meta.ts @@ -6,7 +6,9 @@ export const tool = defineTool('csv', { path: 'csv-rows-to-columns', icon: 'carbon:transpose', description: - 'This tool converts rows of a CSV (Comma Separated Values) file into columns. It extracts the horizontal lines from the input CSV one by one, rotates them 90 degrees, and outputs them as vertical columns one after another, separated by commas. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as "missing", "?", or "x". During the conversion process, the tool also cleans the CSV file from unnecessary information, such as empty lines (these are lines without visible information) and comments. To help the tool correctly identify comments, in the options, you can specify the symbol at the beginning of a line that starts a comment. This symbol is typically a hash "#" or double slash "//". Csv-abulous!.', + 'This tool converts rows of a CSV (Comma Separated Values) file into columns. It extracts the horizontal lines from the input CSV one by one, rotates them 90 degrees, and outputs them as vertical columns one after another, separated by commas.', + longDescription: + 'This tool converts rows of a CSV (Comma Separated Values) file into columns. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as "missing", "?", or "x". During the conversion process, the tool also cleans the CSV file from unnecessary information, such as empty lines (these are lines without visible information) and comments. To help the tool correctly identify comments, in the options, you can specify the symbol at the beginning of a line that starts a comment. This symbol is typically a hash "#" or double slash "//". Csv-abulous!.', shortDescription: 'Convert CSV data to JSON format', keywords: ['csv', 'rows', 'columns', 'transpose'], component: lazy(() => import('./index'))