mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 21:37:11 -04:00
fix(text-to-binary): return valid UTF-8 results for non-ASCII text
This commit is contained in:
parent
e876d03608
commit
35d8264c2b
5 changed files with 105 additions and 50 deletions
|
@ -389,5 +389,5 @@ tools:
|
||||||
description: Encode text to URL-encoded format (also known as "percent-encoded"), or decode from it.
|
description: Encode text to URL-encoded format (also known as "percent-encoded"), or decode from it.
|
||||||
|
|
||||||
text-to-binary:
|
text-to-binary:
|
||||||
title: Text to ASCII binary
|
title: Text to UTF-8 binary
|
||||||
description: Convert text to its ASCII binary representation and vice-versa.
|
description: Convert text to its UTF-8 binary representation and vice-versa.
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
test.describe('Tool - Text to ASCII binary', () => {
|
test.describe('Tool - Text to UTF-8 binary', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/text-to-binary');
|
await page.goto('/text-to-binary');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Has correct title', async ({ page }) => {
|
test('Has correct title', async ({ page }) => {
|
||||||
await expect(page).toHaveTitle('Text to ASCII binary - IT Tools');
|
await expect(page).toHaveTitle('Text to UTF-8 binary - IT Tools');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Text to binary conversion', async ({ page }) => {
|
test('Text to binary conversion', async ({ page }) => {
|
||||||
|
@ -17,7 +17,9 @@ test.describe('Tool - Text to ASCII binary', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Binary to text conversion', async ({ page }) => {
|
test('Binary to text conversion', async ({ page }) => {
|
||||||
await page.getByTestId('binary-to-text-input').fill('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
|
await page
|
||||||
|
.getByTestId('binary-to-text-input')
|
||||||
|
.fill('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
|
||||||
const text = await page.getByTestId('binary-to-text-output').inputValue();
|
const text = await page.getByTestId('binary-to-text-output').inputValue();
|
||||||
|
|
||||||
expect(text).toEqual('it-tools');
|
expect(text).toEqual('it-tools');
|
||||||
|
|
|
@ -1,32 +1,56 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
|
import { convertTextToUtf8Binary, convertUtf8BinaryToText } from './text-to-binary.models';
|
||||||
|
|
||||||
describe('text-to-binary', () => {
|
describe('text-to-binary', () => {
|
||||||
describe('convertTextToAsciiBinary', () => {
|
const utf8Tests = [
|
||||||
it('a text string is converted to its ascii binary representation', () => {
|
{ text: '文字', binary: '11100110 10010110 10000111 11100101 10101101 10010111' },
|
||||||
expect(convertTextToAsciiBinary('A')).toBe('01000001');
|
{ text: '💩', binary: '11110000 10011111 10010010 10101001' },
|
||||||
expect(convertTextToAsciiBinary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
|
];
|
||||||
expect(convertTextToAsciiBinary('')).toBe('');
|
|
||||||
|
describe('convertTextToUtf8Binary', () => {
|
||||||
|
it('a text string is converted to its UTF-8 binary representation', () => {
|
||||||
|
expect(convertTextToUtf8Binary('A')).toBe('01000001');
|
||||||
|
expect(convertTextToUtf8Binary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
|
||||||
|
expect(convertTextToUtf8Binary('')).toBe('');
|
||||||
});
|
});
|
||||||
it('the separator between octets can be changed', () => {
|
it('the separator between octets can be changed', () => {
|
||||||
expect(convertTextToAsciiBinary('hello', { separator: '' })).toBe('0110100001100101011011000110110001101111');
|
expect(convertTextToUtf8Binary('hello', { separator: '' })).toBe('0110100001100101011011000110110001101111');
|
||||||
|
expect(convertTextToUtf8Binary('hello', { separator: '-' })).toBe('01101000-01100101-01101100-01101100-01101111');
|
||||||
|
});
|
||||||
|
it('works with non-ASCII input', () => {
|
||||||
|
for (const { text, binary } of utf8Tests) {
|
||||||
|
const converted = convertTextToUtf8Binary(text);
|
||||||
|
expect(converted).toBe(binary);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('convertAsciiBinaryToText', () => {
|
describe('convertUtf8BinaryToText', () => {
|
||||||
it('an ascii binary string is converted to its text representation', () => {
|
it('an ascii binary string is converted to its text representation', () => {
|
||||||
expect(convertAsciiBinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
expect(convertUtf8BinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
||||||
expect(convertAsciiBinaryToText('01000001')).toBe('A');
|
expect(convertUtf8BinaryToText('01000001')).toBe('A');
|
||||||
expect(convertTextToAsciiBinary('')).toBe('');
|
expect(convertTextToUtf8Binary('')).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('the given binary string is cleaned before conversion', () => {
|
it('the given binary string is cleaned before conversion', () => {
|
||||||
expect(convertAsciiBinaryToText(' 01000 001garbage')).toBe('A');
|
expect(convertUtf8BinaryToText(' 01000 001garbage')).toBe('A');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if the given binary string as no complete octet', () => {
|
it('throws an error if the given binary string is not an integer number of complete octets', () => {
|
||||||
expect(() => convertAsciiBinaryToText('010000011')).toThrow('Invalid binary string');
|
expect(() => convertUtf8BinaryToText('010000011')).toThrow('Invalid binary string');
|
||||||
expect(() => convertAsciiBinaryToText('1')).toThrow('Invalid binary string');
|
expect(() => convertUtf8BinaryToText('010000011 010000011')).toThrow('Invalid binary string');
|
||||||
|
expect(() => convertUtf8BinaryToText('1')).toThrow('Invalid binary string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if the given binary string is not valid UTF-8', () => {
|
||||||
|
expect(() => convertUtf8BinaryToText('11111111')).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with non-ASCII input', () => {
|
||||||
|
for (const { text, binary } of utf8Tests) {
|
||||||
|
const reverted = convertUtf8BinaryToText(binary);
|
||||||
|
expect(reverted).toBe(text);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
export { convertTextToAsciiBinary, convertAsciiBinaryToText };
|
export { convertTextToUtf8Binary, convertUtf8BinaryToText };
|
||||||
|
|
||||||
function convertTextToAsciiBinary(text: string, { separator = ' ' }: { separator?: string } = {}): string {
|
function convertTextToUtf8Binary(text: string, { separator = ' ' }: { separator?: string } = {}): string {
|
||||||
return text
|
return [...new TextEncoder().encode(text)].map(x => x.toString(2).padStart(8, '0')).join(separator);
|
||||||
.split('')
|
|
||||||
.map(char => char.charCodeAt(0).toString(2).padStart(8, '0'))
|
|
||||||
.join(separator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertAsciiBinaryToText(binary: string): string {
|
function convertUtf8BinaryToText(binary: string): string {
|
||||||
const cleanBinary = binary.replace(/[^01]/g, '');
|
const cleanBinary = binary.replace(/[^01]+/g, '');
|
||||||
|
|
||||||
if (cleanBinary.length % 8) {
|
if (cleanBinary.length % 8) {
|
||||||
throw new Error('Invalid binary string');
|
throw new Error('Invalid binary string');
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanBinary
|
return new TextDecoder(undefined, { fatal: true }).decode(
|
||||||
.split(/(\d{8})/)
|
Uint8Array.from({ length: cleanBinary.length / 8 }, (_, i) =>
|
||||||
.filter(Boolean)
|
Number.parseInt(cleanBinary.slice(i * 8, (i + 1) * 8), 2),
|
||||||
.map(binary => String.fromCharCode(Number.parseInt(binary, 2)))
|
),
|
||||||
.join('');
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,74 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
|
import { convertTextToUtf8Binary, convertUtf8BinaryToText } from './text-to-binary.models';
|
||||||
import { withDefaultOnError } from '@/utils/defaults';
|
import { withDefaultOnError } from '@/utils/defaults';
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
import { isNotThrowing } from '@/utils/boolean';
|
import { isNotThrowing } from '@/utils/boolean';
|
||||||
|
|
||||||
const inputText = ref('');
|
const inputText = ref('');
|
||||||
const binaryFromText = computed(() => convertTextToAsciiBinary(inputText.value));
|
const binaryFromText = computed(() => convertTextToUtf8Binary(inputText.value));
|
||||||
const { copy: copyBinary } = useCopy({ source: binaryFromText });
|
const { copy: copyBinary } = useCopy({ source: binaryFromText });
|
||||||
|
|
||||||
const inputBinary = ref('');
|
const inputBinary = ref('');
|
||||||
const textFromBinary = computed(() => withDefaultOnError(() => convertAsciiBinaryToText(inputBinary.value), ''));
|
const textFromBinary = computed(() => withDefaultOnError(() => convertUtf8BinaryToText(inputBinary.value), ''));
|
||||||
const inputBinaryValidationRules = [
|
const inputBinaryValidationRules = [
|
||||||
{
|
{
|
||||||
validator: (value: string) => isNotThrowing(() => convertAsciiBinaryToText(value)),
|
validator: (value: string) => isNotThrowing(() => convertUtf8BinaryToText(value)),
|
||||||
message: 'Binary should be a valid ASCII binary string with multiples of 8 bits',
|
message: 'Binary should be a valid UTF-8 binary string with multiples of 8 bits',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { copy: copyText } = useCopy({ source: textFromBinary });
|
const { copy: copyText } = useCopy({ source: textFromBinary });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card title="Text to ASCII binary">
|
<c-card title="Text to UTF-8 binary">
|
||||||
<c-input-text v-model:value="inputText" multiline placeholder="e.g. 'Hello world'" label="Enter text to convert to binary" autosize autofocus raw-text test-id="text-to-binary-input" />
|
<c-input-text
|
||||||
<c-input-text v-model:value="binaryFromText" label="Binary from your text" multiline raw-text readonly mt-2 placeholder="The binary representation of your text will be here" test-id="text-to-binary-output" />
|
v-model:value="inputText"
|
||||||
|
multiline
|
||||||
|
placeholder="e.g. 'Hello world'"
|
||||||
|
label="Enter text to convert to binary"
|
||||||
|
autosize
|
||||||
|
autofocus
|
||||||
|
raw-text
|
||||||
|
test-id="text-to-binary-input"
|
||||||
|
/>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="binaryFromText"
|
||||||
|
label="Binary from your text"
|
||||||
|
multiline
|
||||||
|
raw-text
|
||||||
|
readonly
|
||||||
|
mt-2
|
||||||
|
placeholder="The binary representation of your text will be here"
|
||||||
|
test-id="text-to-binary-output"
|
||||||
|
/>
|
||||||
<div mt-2 flex justify-center>
|
<div mt-2 flex justify-center>
|
||||||
<c-button :disabled="!binaryFromText" @click="copyBinary()">
|
<c-button :disabled="!binaryFromText" @click="copyBinary()"> Copy binary to clipboard </c-button>
|
||||||
Copy binary to clipboard
|
|
||||||
</c-button>
|
|
||||||
</div>
|
</div>
|
||||||
</c-card>
|
</c-card>
|
||||||
|
|
||||||
<c-card title="ASCII binary to text">
|
<c-card title="UTF-8 binary to text">
|
||||||
<c-input-text v-model:value="inputBinary" multiline placeholder="e.g. '01001000 01100101 01101100 01101100 01101111'" label="Enter binary to convert to text" autosize raw-text :validation-rules="inputBinaryValidationRules" test-id="binary-to-text-input" />
|
<c-input-text
|
||||||
<c-input-text v-model:value="textFromBinary" label="Text from your binary" multiline raw-text readonly mt-2 placeholder="The text representation of your binary will be here" test-id="binary-to-text-output" />
|
v-model:value="inputBinary"
|
||||||
|
multiline
|
||||||
|
placeholder="e.g. '01001000 01100101 01101100 01101100 01101111'"
|
||||||
|
label="Enter binary to convert to text"
|
||||||
|
autosize
|
||||||
|
raw-text
|
||||||
|
:validation-rules="inputBinaryValidationRules"
|
||||||
|
test-id="binary-to-text-input"
|
||||||
|
/>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="textFromBinary"
|
||||||
|
label="Text from your binary"
|
||||||
|
multiline
|
||||||
|
raw-text
|
||||||
|
readonly
|
||||||
|
mt-2
|
||||||
|
placeholder="The text representation of your binary will be here"
|
||||||
|
test-id="binary-to-text-output"
|
||||||
|
/>
|
||||||
<div mt-2 flex justify-center>
|
<div mt-2 flex justify-center>
|
||||||
<c-button :disabled="!textFromBinary" @click="copyText()">
|
<c-button :disabled="!textFromBinary" @click="copyText()"> Copy text to clipboard </c-button>
|
||||||
Copy text to clipboard
|
|
||||||
</c-button>
|
|
||||||
</div>
|
</div>
|
||||||
</c-card>
|
</c-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue