feat: transpose-CSV

This commit is contained in:
Chesterkxng
2025-04-10 18:39:09 +02:00
parent c4d78a6c0c
commit e48f1757e2
6 changed files with 319 additions and 1 deletions

View File

@@ -1,3 +1,4 @@
import { tool as transposeCsv } from './transpose-csv/meta';
import { tool as findIncompleteCsvRecords } from './find-incomplete-csv-records/meta'; import { tool as findIncompleteCsvRecords } from './find-incomplete-csv-records/meta';
import { tool as ChangeCsvDelimiter } from './change-csv-separator/meta'; import { tool as ChangeCsvDelimiter } from './change-csv-separator/meta';
import { tool as csvToYaml } from './csv-to-yaml/meta'; import { tool as csvToYaml } from './csv-to-yaml/meta';
@@ -15,5 +16,6 @@ export const csvTools = [
swapCsvColumns, swapCsvColumns,
csvToYaml, csvToYaml,
ChangeCsvDelimiter, ChangeCsvDelimiter,
findIncompleteCsvRecords findIncompleteCsvRecords,
transposeCsv
]; ];

View File

@@ -0,0 +1,178 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { GetGroupsType } from '@components/options/ToolOptions';
import { CardExampleType } from '@components/examples/ToolExamples';
import { transposeCSV } from './service';
import { InitialValuesType } from './types';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc';
const initialValues: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
};
const exampleCards: CardExampleType<InitialValuesType>[] = [
{
title: 'Transpose a 2x3 CSV',
description:
'This example transposes a CSV with 2 rows and 3 columns. The tool splits the input data by the comma character, creating a 2 by 3 matrix. It then exchanges elements, turning columns into rows and vice versa. The output is a transposed CSV with flipped dimensions',
sampleText: `foo,bar,baz
val1,val2,val3`,
sampleResult: `foo,val1
bar,val2
baz,val3`,
sampleOptions: {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
}
},
{
title: 'Transpose a Long CSV',
description:
'In this example, we flip a vertical single-column CSV file containing a list of our favorite fruits and their emojis. This single column is transformed into a single-row CSV file and the rows length matches the height of the original CSV.',
sampleText: `Tasty Fruit
🍑 peaches
🍒 cherries
🥝 kiwis
🍓 strawberries
🍎 apples
🍐 pears
🥭 mangos
🍍 pineapples
🍌 bananas
🍊 tangerines
🍉 watermelons
🍇 grapes`,
sampleResult: `fTasty Fruit,🍑 peaches,🍒 cherries,🥝 kiwis,🍓 strawberries,🍎 apples,🍐 pears,🥭 mangos,🍍 pineapples,🍌 bananas,🍊 tangerines,🍉 watermelons,🍇 grapes`,
sampleOptions: {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
}
},
{
title: 'Clean and Transpose CSV Data',
description:
'In this example, we perform three tasks simultaneously: transpose a CSV file, remove comments and empty lines, and fix missing data. The transposition operation is the same as flipping a matrix across its diagonal and it is done automatically by the program. Additionally, the program automatically removes all empty lines as they cannot be transposed. The comments are removed by specifying the "#" symbol in the options. The program also fixes missing data using a custom bullet symbol "•", which is specified in the options.',
sampleText: `Fish Type,Color,Habitat
Goldfish,Gold,Freshwater
#Clownfish,Orange,Coral Reefs
Tuna,Silver,Saltwater
Shark,Grey,Saltwater
Salmon,Silver`,
sampleResult: `Fish Type,Goldfish,Tuna,Shark,Salmon
Color,Gold,Silver,Grey,Silver
Habitat,Freshwater,Saltwater,Saltwater,•`,
sampleOptions: {
separator: ',',
commentCharacter: '#',
customFill: true,
customFillValue: '•',
quoteChar: '"'
}
}
];
export default function TransposeCsv({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (values: InitialValuesType, input: string) => {
setResult(transposeCSV(input, values));
};
const getGroups: GetGroupsType<InitialValuesType> | null = ({
values,
updateField
}) => [
{
title: 'Csv input Options',
component: (
<Box>
<TextFieldWithDesc
value={values.separator}
onOwnChange={(val) => updateField('separator', val)}
description={
'Enter the character used to delimit columns in the CSV input file.'
}
/>
<TextFieldWithDesc
value={values.quoteChar}
onOwnChange={(val) => updateField('quoteChar', val)}
description={
'Enter the quote character used to quote the CSV input fields.'
}
/>
<TextFieldWithDesc
value={values.commentCharacter}
onOwnChange={(val) => updateField('commentCharacter', val)}
description={
'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'
}
/>
</Box>
)
},
{
title: 'Fixing CSV Options',
component: (
<Box>
<SelectWithDesc
selected={values.customFill}
options={[
{ label: 'Fill With Empty Values', value: false },
{ label: 'Fill with Custom Values', value: true }
]}
onChange={(value) => updateField('customFill', value)}
description={
'Insert empty fields or custom values where the CSV data is missing (not empty).'
}
/>
{values.customFill && (
<TextFieldWithDesc
value={values.customFillValue}
onOwnChange={(val) => updateField('customFillValue', val)}
description={
'Enter the character used to fill missing values in the CSV input file.'
}
/>
)}
</Box>
)
}
];
return (
<ToolContent
title={title}
input={input}
inputComponent={
<ToolTextInput title="Input CSV" value={input} onChange={setInput} />
}
resultComponent={<ToolTextResult title="Transposed CSV" value={result} />}
initialValues={initialValues}
exampleCards={exampleCards}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
/>
);
}

View File

@@ -0,0 +1,15 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('csv', {
name: 'Transpose CSV',
path: 'transpose-csv',
icon: 'carbon:transpose',
description:
'Just upload your CSV file in the form below, and this tool will automatically transpose your CSV. In the tool options, you can specify the character that starts the comment lines in the CSV to remove them. Additionally, if the CSV is incomplete (missing values), you can replace missing values with the empty character or a custom character.',
shortDescription: 'Quickly transpose a CSV file.',
keywords: ['transpose', 'csv'],
longDescription:
'This tool transposes Comma Separated Values (CSV). It treats the CSV as a matrix of data and flips all elements across the main diagonal. The output contains the same CSV data as the input, but now all the rows have become columns, and all the columns have become rows. After transposition, the CSV file will have opposite dimensions. For example, if the input file has 4 columns and 3 rows, the output file will have 3 columns and 4 rows. During conversion, the program also cleans the data from unnecessary lines and corrects incomplete data. Specifically, the tool automatically deletes all empty records and comments that begin with a specific character, which you can set in the option. Additionally, in cases where the CSV data is corrupted or lost, the utility completes the file with empty fields or custom fields that can be specified in the options. Csv-abulous!',
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,27 @@
import { InitialValuesType } from './types';
import { transpose, normalizeAndFill } from '@utils/array';
import { splitCsv } from '@utils/csv';
export function transposeCSV(
input: string,
options: InitialValuesType
): string {
if (!input) return '';
const rows = splitCsv(
input,
true,
options.commentCharacter,
true,
options.separator,
options.quoteChar
);
const normalizeAndFillRows = options.customFill
? normalizeAndFill(rows, options.customFillValue)
: normalizeAndFill(rows, '');
return transpose(normalizeAndFillRows)
.map((row) => row.join(options.separator))
.join('\n');
}

View File

@@ -0,0 +1,89 @@
import { expect, describe, it } from 'vitest';
import { transposeCSV } from './service';
import { InitialValuesType } from './types';
describe('transposeCsv', () => {
it('should transpose a simple CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
};
const input = 'a,b,c\n1,2,3';
const expectedOutput = 'a,1\nb,2\nc,3';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
it('should handle an empty CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
};
const input = '';
const expectedOutput = '';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
it('should handle a single row CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
};
const input = 'a,b,c';
const expectedOutput = 'a\nb\nc';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
it('should handle a single column CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: false,
customFillValue: 'x',
quoteChar: '"'
};
const input = 'a\nb\nc';
const expectedOutput = 'a,b,c';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
it('should handle uneven rows in the CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: true,
customFillValue: 'x',
quoteChar: '"'
};
const input = 'a,b\n1,2,3';
const expectedOutput = 'a,1\nb,2\nx,3';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
it('should skip comment in the CSV', () => {
const options: InitialValuesType = {
separator: ',',
commentCharacter: '#',
customFill: true,
customFillValue: 'x',
quoteChar: '"'
};
const input = 'a,b\n1,2\n#c,3\nd,4';
const expectedOutput = 'a,1,d\nb,2,4';
const result = transposeCSV(input, options);
expect(result).toEqual(expectedOutput);
});
});

View File

@@ -0,0 +1,7 @@
export type InitialValuesType = {
separator: string;
commentCharacter: string;
customFill: boolean;
customFillValue: string;
quoteChar: string;
};