Skip to content

Commit 29e0107

Browse files
committed
feat(new tool): ISBN Parser and Formatter
ISBN Parser and Formatter
1 parent 670f735 commit 29e0107

File tree

6 files changed

+133
-7
lines changed

6 files changed

+133
-7
lines changed

components.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ declare module '@vue/runtime-core' {
9191
'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
9292
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
9393
IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default']
94-
IconMdiArrowRight: typeof import('~icons/mdi/arrow-right')['default']
9594
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
9695
IconMdiCamera: typeof import('~icons/mdi/camera')['default']
9796
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
@@ -117,6 +116,7 @@ declare module '@vue/runtime-core' {
117116
Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default']
118117
Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default']
119118
Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default']
119+
IsbnValidatorAndParser: typeof import('./src/tools/isbn-validator-and-parser/isbn-validator-and-parser.vue')['default']
120120
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
121121
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
122122
JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default']
@@ -171,8 +171,6 @@ declare module '@vue/runtime-core' {
171171
NTable: typeof import('naive-ui')['NTable']
172172
NTag: typeof import('naive-ui')['NTag']
173173
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
174-
NUpload: typeof import('naive-ui')['NUpload']
175-
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
176174
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
177175
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
178176
PdfSignatureChecker: typeof import('./src/tools/pdf-signature-checker/pdf-signature-checker.vue')['default']

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"highlight.js": "^11.7.0",
6363
"iarna-toml-esm": "^3.0.5",
6464
"ibantools": "^4.3.3",
65+
"isbn3": "^1.1.44",
6566
"json5": "^2.2.3",
6667
"jwt-decode": "^3.1.2",
6768
"libphonenumber-js": "^1.10.28",

pnpm-lock.yaml

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

src/tools/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import { tool as urlParser } from './url-parser';
7575
import { tool as uuidGenerator } from './uuid-generator';
7676
import { tool as macAddressLookup } from './mac-address-lookup';
7777
import { tool as xmlFormatter } from './xml-formatter';
78+
import { tool as isbnValidatorAndParser } from './isbn-validator-and-parser';
7879

7980
export const toolsByCategory: ToolCategory[] = [
8081
{
@@ -159,7 +160,11 @@ export const toolsByCategory: ToolCategory[] = [
159160
},
160161
{
161162
name: 'Data',
162-
components: [phoneParserAndFormatter, ibanValidatorAndParser],
163+
components: [
164+
phoneParserAndFormatter,
165+
ibanValidatorAndParser,
166+
isbnValidatorAndParser,
167+
],
163168
},
164169
];
165170

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Books } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'ISBN Validator and Parser',
6+
path: '/isbn-validator-and-parser',
7+
description: 'Parse, validate, format and get infos for an ISBN',
8+
keywords: ['isbn', 'validator', 'parser', 'formatter'],
9+
component: () => import('./isbn-validator-and-parser.vue'),
10+
icon: Books,
11+
createdAt: new Date('2024-01-10'),
12+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<script setup lang="ts">
2+
import ISBN3 from 'isbn3';
3+
import type { CKeyValueListItems } from '@/ui/c-key-value-list/c-key-value-list.types';
4+
5+
const rawIsbn = ref('9782021304534');
6+
7+
const isbnInfos = computed<CKeyValueListItems>(() => {
8+
const isbn = ISBN3.parse(rawIsbn.value);
9+
10+
if (isbn == null) {
11+
return [];
12+
}
13+
14+
return [
15+
{
16+
label: 'Is ISBN valid ?',
17+
value: isbn.isValid,
18+
},
19+
{
20+
label: 'Country',
21+
value: isbn.groupname,
22+
},
23+
{
24+
label: 'ISBN 13',
25+
value: isbn.isbn13,
26+
},
27+
{
28+
label: 'ISBN 13 Formatted',
29+
value: isbn.isbn13h,
30+
},
31+
{
32+
label: 'ISBN 10',
33+
value: isbn.isbn10,
34+
},
35+
{
36+
label: 'ISBN 10 Formatted',
37+
value: isbn.isbn10h,
38+
},
39+
];
40+
});
41+
42+
function isbnChecksum(isbn: string) {
43+
let check = 0;
44+
45+
if (isbn.length === 9) {
46+
for (let n = 0; n < 9; n += 1) {
47+
check += (10 - n) * Number(isbn.charAt(n));
48+
}
49+
check = (11 - check % 11) % 11;
50+
return check === 10 ? 'X' : String(check);
51+
}
52+
else if (isbn.length === 12) {
53+
for (let n = 0; n < 12; n += 2) {
54+
check += Number(isbn.charAt(n)) + 3 * Number(isbn.charAt(n + 1));
55+
}
56+
return String((10 - check % 10) % 10);
57+
}
58+
59+
return null;
60+
}
61+
62+
const normalizedISBN = computed(() => {
63+
let normalized = rawIsbn.value.replace(/[^\dX]/g, '');
64+
if (normalized.length >= 13) {
65+
normalized = normalized.substring(0, 12);
66+
}
67+
else if (normalized.length >= 10) {
68+
normalized = normalized.substring(0, 9);
69+
}
70+
const checksum = isbnChecksum(normalized);
71+
return ISBN3.parse(`${normalized}${checksum}`)?.isbn13h;
72+
});
73+
74+
const isbnAuditInfos = computed<{ isValid: boolean; clues: CKeyValueListItems }>(() => {
75+
const isbn = ISBN3.audit(rawIsbn.value);
76+
77+
const isValid = (isbn?.validIsbn ?? false);
78+
return {
79+
isValid,
80+
clues: Array.from((isbn?.clues ?? []),
81+
clue => ({ label: `${clue.message} (${clue.groupname})`, value: clue.candidate })),
82+
};
83+
});
84+
</script>
85+
86+
<template>
87+
<div>
88+
<c-input-text v-model:value="rawIsbn" placeholder="Enter an ISBN to check for validity..." test-id="isbn-input" />
89+
<n-alert v-if="!isbnAuditInfos.isValid" type="error">
90+
Invalid ISBN.
91+
<input-copyable v-if="normalizedISBN" label="Probably correct" label-position="left" :value="normalizedISBN" disabled="true" />
92+
</n-alert>
93+
94+
<c-card v-if="isbnInfos.length > 0" mt-5 title="ISBN Infos">
95+
<c-key-value-list :items="isbnInfos" data-test-id="isbn-info" />
96+
</c-card>
97+
<c-card v-if="isbnAuditInfos.clues.length > 0" mt-5 title="ISBN Audit Infos">
98+
<c-key-value-list :items="isbnAuditInfos" data-test-id="isbn-info" />
99+
</c-card>
100+
</div>
101+
</template>

0 commit comments

Comments
 (0)