mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 22:19:36 +02:00
feat: csv-rows-to-columns
This commit is contained in:
@@ -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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
154
src/pages/tools/csv/csv-rows-to-columns/index.tsx
Normal file
154
src/pages/tools/csv/csv-rows-to-columns/index.tsx
Normal file
@@ -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<InitialValuesType>[] = [
|
||||||
|
{
|
||||||
|
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<string>('');
|
||||||
|
const [result, setResult] = useState<string>('');
|
||||||
|
|
||||||
|
const compute = (optionsValues: typeof initialValues, input: string) => {
|
||||||
|
setResult(
|
||||||
|
csvRowsToColumns(
|
||||||
|
input,
|
||||||
|
optionsValues.emptyValuesFilling,
|
||||||
|
optionsValues.customFiller,
|
||||||
|
optionsValues.commentCharacter
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroups: GetGroupsType<InitialValuesType> = ({
|
||||||
|
values,
|
||||||
|
updateField
|
||||||
|
}) => [
|
||||||
|
{
|
||||||
|
title: 'Fix incomplete data',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<SelectWithDesc
|
||||||
|
selected={values.emptyValuesFilling}
|
||||||
|
options={[
|
||||||
|
{ label: 'Fill With Empty Values', value: true },
|
||||||
|
{ label: 'Fill With Customs Values', value: false }
|
||||||
|
]}
|
||||||
|
onChange={(value) => 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?'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextFieldWithDesc
|
||||||
|
value={values.customFiller}
|
||||||
|
onOwnChange={(val) => updateField('customFiller', val)}
|
||||||
|
description={
|
||||||
|
'Use this custom value to fill in missing fields. (Works only with "Custom Values" mode above.)'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Lines with comments',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<TextFieldWithDesc
|
||||||
|
value={values.commentCharacter}
|
||||||
|
onOwnChange={(val) => updateField('commentCharacter', val)}
|
||||||
|
description={
|
||||||
|
'Enter the symbol indicating the start of a comment line. (These lines are removed during conversion.)'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolContent
|
||||||
|
title={title}
|
||||||
|
input={input}
|
||||||
|
inputComponent={<ToolTextInput value={input} onChange={setInput} />}
|
||||||
|
resultComponent={<ToolTextResult value={result} />}
|
||||||
|
initialValues={initialValues}
|
||||||
|
getGroups={getGroups}
|
||||||
|
setInput={setInput}
|
||||||
|
compute={compute}
|
||||||
|
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
|
||||||
|
exampleCards={exampleCards}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
13
src/pages/tools/csv/csv-rows-to-columns/meta.ts
Normal file
13
src/pages/tools/csv/csv-rows-to-columns/meta.ts
Normal file
@@ -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'))
|
||||||
|
});
|
34
src/pages/tools/csv/csv-rows-to-columns/service.ts
Normal file
34
src/pages/tools/csv/csv-rows-to-columns/service.ts
Normal file
@@ -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');
|
||||||
|
}
|
Reference in New Issue
Block a user