Skip to content

Commit 7308cf0

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents c8a320c + 1b2fcdb commit 7308cf0

File tree

14 files changed

+942
-72
lines changed

14 files changed

+942
-72
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@types/omggif": "^1.0.5",
4242
"browser-image-compression": "^2.0.2",
4343
"color": "^4.2.3",
44+
"dayjs": "^1.11.13",
4445
"formik": "^2.4.6",
4546
"jimp": "^0.22.12",
4647
"lint-staged": "^15.4.3",
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { expect, describe, it } from 'vitest';
2+
import { changeCsvSeparator } from './service';
3+
import { InitialValuesType } from './types';
4+
5+
describe('changeCsvSeparator', () => {
6+
it('should change the separator from comma to semicolon', () => {
7+
const inputCsv = 'name,age,city\nJohn,30,New York';
8+
const options: InitialValuesType = {
9+
inputSeparator: ',',
10+
inputQuoteCharacter: '"',
11+
commentCharacter: '#',
12+
emptyLines: false,
13+
outputSeparator: ';',
14+
outputQuoteAll: false,
15+
OutputQuoteCharacter: '"'
16+
};
17+
const result = changeCsvSeparator(inputCsv, options);
18+
expect(result).toBe('name;age;city\nJohn;30;New York');
19+
});
20+
21+
it('should handle empty input gracefully', () => {
22+
const inputCsv = '';
23+
const options: InitialValuesType = {
24+
inputSeparator: ',',
25+
inputQuoteCharacter: '"',
26+
commentCharacter: '#',
27+
emptyLines: false,
28+
outputSeparator: ';',
29+
outputQuoteAll: false,
30+
OutputQuoteCharacter: '"'
31+
};
32+
const result = changeCsvSeparator(inputCsv, options);
33+
expect(result).toBe('');
34+
});
35+
36+
it('should not modify the CSV if the separator is already correct', () => {
37+
const inputCsv = 'name;age;city\nJohn;30;New York';
38+
const options: InitialValuesType = {
39+
inputSeparator: ';',
40+
inputQuoteCharacter: '"',
41+
commentCharacter: '#',
42+
emptyLines: false,
43+
outputSeparator: ';',
44+
outputQuoteAll: false,
45+
OutputQuoteCharacter: '"'
46+
};
47+
const result = changeCsvSeparator(inputCsv, options);
48+
expect(result).toBe(inputCsv);
49+
});
50+
51+
it('should handle custom separators', () => {
52+
const inputCsv = 'name|age|city\nJohn|30|New York';
53+
const options: InitialValuesType = {
54+
inputSeparator: '|',
55+
inputQuoteCharacter: '"',
56+
commentCharacter: '#',
57+
emptyLines: false,
58+
outputSeparator: ';',
59+
outputQuoteAll: false,
60+
OutputQuoteCharacter: '"'
61+
};
62+
const result = changeCsvSeparator(inputCsv, options);
63+
expect(result).toBe('name;age;city\nJohn;30;New York');
64+
});
65+
66+
it('should quote all output values', () => {
67+
const inputCsv = 'name|age|city\nJohn|30|New York';
68+
const options: InitialValuesType = {
69+
inputSeparator: '|',
70+
inputQuoteCharacter: '"',
71+
commentCharacter: '#',
72+
emptyLines: false,
73+
outputSeparator: ';',
74+
outputQuoteAll: true,
75+
OutputQuoteCharacter: '"'
76+
};
77+
const result = changeCsvSeparator(inputCsv, options);
78+
expect(result).toBe('"name";"age";"city"\n"John";"30";"New York"');
79+
});
80+
81+
it('should remove quotes from input values', () => {
82+
const inputCsv = '"name"|"age"|"city"\n"John"|"30"|"New York"';
83+
const options: InitialValuesType = {
84+
inputSeparator: '|',
85+
inputQuoteCharacter: '"',
86+
commentCharacter: '#',
87+
emptyLines: false,
88+
outputSeparator: ';',
89+
outputQuoteAll: false,
90+
OutputQuoteCharacter: '"'
91+
};
92+
const result = changeCsvSeparator(inputCsv, options);
93+
expect(result).toBe('name;age;city\nJohn;30;New York');
94+
});
95+
96+
it('should handle emptylines', () => {
97+
const inputCsv = '"name"|"age"|"city"\n\n"John"|"30"|"New York"';
98+
const options: InitialValuesType = {
99+
inputSeparator: '|',
100+
inputQuoteCharacter: '"',
101+
commentCharacter: '#',
102+
emptyLines: true,
103+
outputSeparator: ';',
104+
outputQuoteAll: false,
105+
OutputQuoteCharacter: '"'
106+
};
107+
const result = changeCsvSeparator(inputCsv, options);
108+
expect(result).toBe('name;age;city\nJohn;30;New York');
109+
});
110+
111+
it('should handle emptylines', () => {
112+
const inputCsv = '"name"|"age"|"city"\n\n"John"|"30"|"New York"';
113+
const options: InitialValuesType = {
114+
inputSeparator: '|',
115+
inputQuoteCharacter: '"',
116+
commentCharacter: '#',
117+
emptyLines: true,
118+
outputSeparator: ';',
119+
outputQuoteAll: false,
120+
OutputQuoteCharacter: '"'
121+
};
122+
const result = changeCsvSeparator(inputCsv, options);
123+
expect(result).toBe('name;age;city\nJohn;30;New York');
124+
});
125+
});
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { Box } from '@mui/material';
2+
import React, { useState } from 'react';
3+
import ToolContent from '@components/ToolContent';
4+
import { ToolComponentProps } from '@tools/defineTool';
5+
import ToolTextInput from '@components/input/ToolTextInput';
6+
import ToolTextResult from '@components/result/ToolTextResult';
7+
import { GetGroupsType } from '@components/options/ToolOptions';
8+
import { CardExampleType } from '@components/examples/ToolExamples';
9+
import { changeCsvSeparator } from './service';
10+
import { InitialValuesType } from './types';
11+
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
12+
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
13+
14+
const initialValues: InitialValuesType = {
15+
inputSeparator: ',',
16+
inputQuoteCharacter: '"',
17+
commentCharacter: '#',
18+
emptyLines: false,
19+
outputSeparator: ';',
20+
outputQuoteAll: false,
21+
OutputQuoteCharacter: '"'
22+
};
23+
24+
const exampleCards: CardExampleType<InitialValuesType>[] = [
25+
{
26+
title: 'Change the CSV Delimiter to a Semicolon',
27+
description:
28+
'In this example, we change the column separator to the semicolon separator in a CSV file containing data about countries, their populations, and population densities. As you can see, the input CSV file uses the standard commas as separators. After specifying this delimiter in the source CSV options, we set a new CSV delimiter for the output file to a semicolon, resulting in a new CSV file that now uses semicolons ";" in the output. Such CSV files with semicolons are called SSV files (semicolon-separated values files)',
29+
sampleText: `country,population,density
30+
China,1412,152
31+
India,1408,428
32+
United States,331,37
33+
Indonesia,273,145
34+
Pakistan,231,232
35+
Brazil,214,26`,
36+
sampleResult: `country;population;density
37+
China;1412;152
38+
India;1408;428
39+
United States;331;37
40+
Indonesia;273;145
41+
Pakistan;231;232
42+
Brazil;214;26`,
43+
sampleOptions: {
44+
inputSeparator: ',',
45+
inputQuoteCharacter: '"',
46+
commentCharacter: '#',
47+
emptyLines: false,
48+
outputSeparator: ';',
49+
outputQuoteAll: false,
50+
OutputQuoteCharacter: '"'
51+
}
52+
},
53+
{
54+
title: 'Restore a CSV File to the Standard Format',
55+
description:
56+
'In this example, a data scientist working with flowers was given an unusual CSV file that uses the vertical bar symbol as the field separator (such files are called PSV files – pipe-separated values files). To transform the file back to the standard comma-separated values (CSV) file, in the options, she set the input delimiter to "|" and the new delimiter to ",". She also wrapped the output fields in single quotes, enabled the option to remove empty lines from the input, and discarded comment lines starting with the "#" symbol.',
57+
sampleText: `species|height|days|temperature
58+
59+
Sunflower|50cm|30|25°C
60+
Rose|40cm|25|22°C
61+
Tulip|35cm|20|18°C
62+
Daffodil|30cm|15|20°C
63+
64+
Lily|45cm|28|23°C
65+
#pumpkin
66+
Brazil,214,26`,
67+
sampleResult: `'species','height','days','temperature'
68+
'Sunflower','50cm','30','25°C'
69+
'Rose','40cm','25','22°C'
70+
'Tulip','35cm','20','18°C'
71+
'Daffodil','30cm','15','20°C'
72+
'Lily','45cm','28','23°C'`,
73+
sampleOptions: {
74+
inputSeparator: '|',
75+
inputQuoteCharacter: '"',
76+
commentCharacter: '#',
77+
emptyLines: true,
78+
outputSeparator: ',',
79+
outputQuoteAll: true,
80+
OutputQuoteCharacter: "'"
81+
}
82+
},
83+
{
84+
title: 'Plants vs. Zombies CSV',
85+
description:
86+
'In this example, we import CSV data with zombie characters from the game Plants vs. Zombies. The data includes zombies names, the level at which they first appear in the game, their health, damage, and speed. The data follows the standard CSV format, with commas serving as field separators. To change the readability of the file, we replace the usual comma delimiter with a slash symbol, creating a slash-separated values file.',
87+
sampleText: `zombie_name,first_seen,health,damage,speed
88+
Normal Zombie,Level 1-1,181,100,4.7
89+
Conehead Zombie,Level 1-3,551,100,4.7
90+
Buckethead Zombi,Level 1-8,1281,100,4.7
91+
Newspaper Zombie,Level 2-1,331,100,4.7
92+
Football Zombie,Level 2-6,1581,100,2.5
93+
Dancing Zombie,Level 2-8,335,100,1.5
94+
Zomboni,Level 3-6,1151,Instant-kill,varies
95+
Catapult Zombie,Level 5-6,651,75,2.5
96+
Gargantuar,Level 5-8,3000,Instant-kill,4.7`,
97+
sampleResult: `zombie_name/first_seen/health/damage/speed
98+
Normal Zombie/Level 1-1/181/100/4.7
99+
Conehead Zombie/Level 1-3/551/100/4.7
100+
Buckethead Zombi/Level 1-8/1281/100/4.7
101+
Newspaper Zombie/Level 2-1/331/100/4.7
102+
Football Zombie/Level 2-6/1581/100/2.5
103+
Dancing Zombie/Level 2-8/335/100/1.5
104+
Zomboni/Level 3-6/1151/Instant-kill/varies
105+
Catapult Zombie/Level 5-6/651/75/2.5
106+
Gargantuar/Level 5-8/3000/Instant-kill/4.7`,
107+
sampleOptions: {
108+
inputSeparator: ',',
109+
inputQuoteCharacter: '"',
110+
commentCharacter: '#',
111+
emptyLines: true,
112+
outputSeparator: '/',
113+
outputQuoteAll: false,
114+
OutputQuoteCharacter: "'"
115+
}
116+
}
117+
];
118+
export default function ChangeCsvDelimiter({
119+
title,
120+
longDescription
121+
}: ToolComponentProps) {
122+
const [input, setInput] = useState<string>('');
123+
const [result, setResult] = useState<string>('');
124+
125+
const compute = (values: InitialValuesType, input: string) => {
126+
setResult(changeCsvSeparator(input, values));
127+
};
128+
129+
const getGroups: GetGroupsType<InitialValuesType> | null = ({
130+
values,
131+
updateField
132+
}) => [
133+
{
134+
title: 'Adjust CSV input options',
135+
component: (
136+
<Box>
137+
<TextFieldWithDesc
138+
value={values.inputSeparator}
139+
onOwnChange={(val) => updateField('inputSeparator', val)}
140+
description={
141+
'Enter the character used to delimit columns in the CSV input file.'
142+
}
143+
/>
144+
<TextFieldWithDesc
145+
value={values.inputQuoteCharacter}
146+
onOwnChange={(val) => updateField('inputQuoteCharacter', val)}
147+
description={
148+
'Enter the quote character used to quote the CSV input fields.'
149+
}
150+
/>
151+
<TextFieldWithDesc
152+
value={values.commentCharacter}
153+
onOwnChange={(val) => updateField('commentCharacter', val)}
154+
description={
155+
'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'
156+
}
157+
/>
158+
<CheckboxWithDesc
159+
checked={values.emptyLines}
160+
onChange={(value) => updateField('emptyLines', value)}
161+
title="Delete Lines with No Data"
162+
description="Remove empty lines from CSV input file."
163+
/>
164+
</Box>
165+
)
166+
},
167+
{
168+
title: 'Output options',
169+
component: (
170+
<Box>
171+
<TextFieldWithDesc
172+
value={values.outputSeparator}
173+
onOwnChange={(val) => updateField('outputSeparator', val)}
174+
description={
175+
'Enter the character used to delimit columns in the CSV output file.'
176+
}
177+
/>
178+
<CheckboxWithDesc
179+
checked={values.outputQuoteAll}
180+
onChange={(value) => updateField('outputQuoteAll', value)}
181+
title="Quote All Output Fields"
182+
description="Wrap all fields of the output CSV file in quotes"
183+
/>
184+
{values.outputQuoteAll && (
185+
<TextFieldWithDesc
186+
value={values.OutputQuoteCharacter}
187+
onOwnChange={(val) => updateField('OutputQuoteCharacter', val)}
188+
description={
189+
'Enter the quote character used to quote the CSV output fields.'
190+
}
191+
/>
192+
)}
193+
</Box>
194+
)
195+
}
196+
];
197+
return (
198+
<ToolContent
199+
title={title}
200+
input={input}
201+
inputComponent={
202+
<ToolTextInput title={'Input CSV'} value={input} onChange={setInput} />
203+
}
204+
resultComponent={<ToolTextResult title={'Output CSV'} value={result} />}
205+
initialValues={initialValues}
206+
exampleCards={exampleCards}
207+
getGroups={getGroups}
208+
setInput={setInput}
209+
compute={compute}
210+
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
211+
/>
212+
);
213+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineTool } from '@tools/defineTool';
2+
import { lazy } from 'react';
3+
4+
export const tool = defineTool('csv', {
5+
name: 'Change csv separator',
6+
path: 'change-csv-separator',
7+
icon: 'material-symbols:split-scene-rounded',
8+
description:
9+
'Just upload your CSV file in the form below and it will automatically get a new column delimiter character. In the tool options, you can specify which delimiter and quote characters are used in the source CSV file and customize the desired delimiter and quote characters for the output CSV. You can also filter the input CSV before the conversion process and skip blank lines and comment lines.',
10+
shortDescription: 'Quickly change the CSV column delimiter to a new symbol.',
11+
keywords: ['change', 'csv', 'sepa rator'],
12+
longDescription:
13+
'This tool changes the field separator in CSV (Comma Separated Values) files. This is useful because different programs may use different default separators. While a comma is the most common separator in CSV files, some programs require files to be tab-separated (TSV), semicolon-separated (SSV), pipe-separated (PSV), or have another separation symbol. The default comma may not be so convenient as a delimiter in CSV files because commas are frequently present within fields. In such cases, it can be difficult and confusing to distinguish between commas as delimiters and commas as punctuation symbols. By replacing the comma with another delimiter, you can convert the file into a more easily readable and parsable format. In the options section of this tool, you can configure both the input and output CSV file formats. For the input CSV, you can specify its current delimiter (by default, it is a comma) and also indicate the quotation mark character used to wrap fields. For the output CSV, you can set a new delimiter, choose a new quotation mark character, and optionally enclose all the fields in quotes. Additionally, you have the option to remove empty lines from the input CSV and eliminate comment lines that start with a specified character (usually a hash "#" or double slashes "//"). Csv-abulous!',
14+
component: lazy(() => import('./index'))
15+
});

0 commit comments

Comments
 (0)