ISBN
ISBNs checked, converted between 10 and 13 digits, hyphenated by the ISBN Agency's ranges into group, publisher and title parts with the group's language or country named, and every ISBN in a text found.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use isbn.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use isbn_parse, isbn_valid from isbn (Quillo 0.6).
isbn_parse(text: str) → anyAn ISBN read (10 or 13 digits; hyphens, spaces and an ISBN prefix ignored): { valid, isbn13, isbn10, hyphenated, hyphenated10, prefix, group, group_name, registrant, publication, check_digit, error }. isbn10 is null for 979 ISBNs, which have none; group_name is the language or country ("English language", "German language", "Philippines").
isbn_valid(text: str) → boolWhether text is a valid ISBN, by its check digit and the agency's ranges.
isbn_hyphenate(text: str, isbn10: bool) → stror an errorAn ISBN hyphenated as printed on the book: 978-0-306-40615-7 (13 digits; isbn10 true for the 10-digit form, 0-306-40615-2).
isbn_find(text: str) → listEvery valid ISBN in a text (a product description, an order list, OCR of a page), as 13 digits, each once in the order found. Numbers that look like ISBNs but fail the check are left out.
Get it
$ quillo install isbn # isbn.qwer into this folder$ quillo install isbn --example # and its example, isbn.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/isbn.qwer$ curl -O https://quillolang.org/plugins/files/isbn.qweThen, in your program, all of it or only what you call:
use isbn.qweruse isbn_parse, isbn_valid from isbn // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
isbn = "0.8"Example
// A bookshop's catalog import: ISBNs from a supplier's list checked, hyphenated for the product// page, and their language group shown.//// quillo run isbn.qwe then open http://localhost:8080/book/9780306406157 and http://localhost:8080/importuse "isbn.qwer"route GET /book/:isbn { let b = isbn_parse(isbn) if !b.valid { reply 422 b.error } b}let SUPPLIER = "Order 2026-118: ISBN 0-306-40615-2 x3; 978-3-16-148410-0 x1; 9789712726309 (Filipino novel); 978-0-306-40615-8 (typo); 979-10-90636-07-1"route GET /import = { found: isbn_find(SUPPLIER), pages: isbn_find(SUPPLIER) | map((i) => isbn_hyphenate(i, false)?), groups: isbn_find(SUPPLIER) | map((i) => isbn_parse(i).group_name), ten: isbn_hyphenate("9780306406157", true)?, no_ten: isbn_hyphenate("9791090636071", true).error, typo: isbn_parse("978-0-306-40615-8").error,}$ quillo run isbn.qwe$ curl 'localhost:8080/book/9780306406157'$ curl 'localhost:8080/import'Source
//! ISBNs read as publishers write them: checked, converted between 10 and 13 digits, hyphenated by the International ISBN Agency's ranges into group, publisher and title parts, the group's language or country named, and every ISBN in a text found.//! version: 1.0.0//!//! [dependencies]//! isbn = "0.8"use isbn::{Isbn, Isbn10, Isbn13};use serde_json::{json, Value};use std::str::FromStr;fn digits(s: &str) -> String { s.chars().filter(|c| c.is_ascii_alphanumeric()).map(|c| c.to_ascii_uppercase()).collect()}fn parse(s: &str) -> Result<Isbn, String> { let d = digits(s.trim().trim_start_matches("ISBN").trim_start_matches("isbn").trim_start_matches(['-', ':', ' '])); let ok = match d.len() { 13 if d.chars().all(|c| c.is_ascii_digit()) => d.chars().enumerate().map(|(i, c)| c.to_digit(10).unwrap() * if i % 2 == 0 { 1 } else { 3 }).sum::<u32>() % 10 == 0, 10 if d[..9].chars().all(|c| c.is_ascii_digit()) && d.chars().last().is_some_and(|c| c.is_ascii_digit() || c == 'X') => { d.chars().enumerate().map(|(i, c)| (10 - i as u32) * c.to_digit(10).unwrap_or(10)).sum::<u32>() % 11 == 0 } 10 | 13 => return Err("an ISBN has only digits (and X as the last of ten)".into()), _ => true, }; if !ok { return Err("the check digit is wrong: a typo".into()); } let r = match d.len() { 10 => Isbn10::from_str(&d).map(Isbn::_10), 13 => Isbn13::from_str(&d).map(Isbn::_13), n => return Err(format!("an ISBN has 10 or 13 digits, not {}", n)), }; r.map_err(|e| match e { isbn::IsbnError::InvalidChecksum => "the check digit is wrong: a typo".to_string(), isbn::IsbnError::InvalidGroup | isbn::IsbnError::UndefinedRange => "the digits are not in a range the ISBN Agency has given out".to_string(), other => other.to_string(), })}fn thirteen(i: &Isbn) -> Isbn13 { match i { Isbn::_10(t) => Isbn13::from(t.clone()), Isbn::_13(t) => t.clone(), }}/// An ISBN read (10 or 13 digits; hyphens, spaces and an `ISBN` prefix ignored): `{ valid, isbn13, isbn10, hyphenated,/// hyphenated10, prefix, group, group_name, registrant, publication, check_digit, error }`. `isbn10` is null for 979 ISBNs, which/// have none; `group_name` is the language or country ("English language", "German language", "Philippines").pub fn isbn_parse(text: &str) -> Value { let i = match parse(text) { Ok(i) => i, Err(e) => return json!({ "valid": false, "error": e }), }; let t13 = thirteen(&i); let h13 = t13.hyphenate().map(|h| h.to_string()).unwrap_or_default(); let ten = Isbn10::try_from(t13.clone()).ok(); let h10 = ten.as_ref().and_then(|t| t.hyphenate().ok()).map(|h| h.to_string()); let parts: Vec<&str> = h13.split('-').collect(); json!({ "valid": true, "isbn13": h13.replace('-', ""), "isbn10": h10.as_ref().map(|h| h.replace('-', "")), "hyphenated": h13, "hyphenated10": h10, "prefix": parts.first(), "group": parts.get(1), "group_name": t13.registration_group().ok(), "registrant": parts.get(2), "publication": parts.get(3), "check_digit": parts.get(4), "error": null, })}/// Whether `text` is a valid ISBN, by its check digit and the agency's ranges.pub fn isbn_valid(text: &str) -> bool { parse(text).is_ok()}/// An ISBN hyphenated as printed on the book: `978-0-306-40615-7` (13 digits; `isbn10` true for the 10-digit form, `0-306-40615-2`).pub fn isbn_hyphenate(text: &str, isbn10: bool) -> Result<String, String> { let i = parse(text)?; let t13 = thirteen(&i); if isbn10 { let t = Isbn10::try_from(t13).map_err(|_| "a 979 ISBN has no 10-digit form".to_string())?; t.hyphenate().map(|h| h.to_string()).map_err(|e| e.to_string()) } else { t13.hyphenate().map(|h| h.to_string()).map_err(|e| e.to_string()) }}/// Every valid ISBN in a text (a product description, an order list, OCR of a page), as 13 digits, each once in the order found./// Numbers that look like ISBNs but fail the check are left out.pub fn isbn_find(text: &str) -> Vec<String> { let mut out: Vec<String> = vec![]; let chars: Vec<char> = text.chars().collect(); let mut i = 0; while i < chars.len() { if chars[i].is_ascii_digit() && (i == 0 || !chars[i - 1].is_ascii_alphanumeric()) { let mut j = i; let mut d = String::new(); while j < chars.len() && d.len() < 13 && (chars[j].is_ascii_digit() || ((chars[j] == 'X' || chars[j] == 'x') && d.len() == 9) || ((chars[j] == '-' || chars[j] == ' ') && j + 1 < chars.len() && chars[j + 1].is_ascii_alphanumeric())) { if chars[j] != '-' && chars[j] != ' ' { d.push(chars[j].to_ascii_uppercase()); } j += 1; } for len in [13usize, 10] { if d.len() >= len { if let Ok(isbn) = parse(&d[..len]) { let t = thirteen(&isbn).hyphenate().map(|h| h.replace('-', "")).unwrap_or_default(); if !out.contains(&t) { out.push(t); } break; } } } i = j.max(i + 1); } else { i += 1; } } out}Related
check_digits.qwer · 1.0.0Check digits made and checked: ISBN-10 and ISBN-13 with conversion, EAN-13, EAN-8 and UPC-A barcodes, IMEI, Luhn, ISO 7064 mod 97-10 references and Verhoeff.6 views · 7 downloadsResearch identifiers scholarly_ids.qwer · 1.0.0The identifiers of research: DOIs cleaned from links and citations, ORCID iDs and ISNIs checked, ISSNs checked with their barcode, arXiv ids read with their version, PubMed ids recognized, and all of them found in a reference list.6 views · 6 downloadsSecurity identifiers securities_ids.qwer · 1.0.0ISIN, CUSIP and CINS, SEDOL and FIGI checked by their check digits and told apart, ISINs made from a CUSIP, SEDOL or other national number, and ISINs found in text.7 views · 6 downloadsCard numbers cards.qwer · 1.0.0Payment card numbers checked before they reach a payment provider: Luhn check digit, brand and length, expiry, and the number formatted or masked for receipts.7 views · 7 downloadsLegal Entity Identifiers lei.qwer · 1.0.0Legal Entity Identifiers (ISO 17442) checked by their check digits, taken apart, made from an 18-character base, found in text, and looked up in GLEIF's public register for the company's name and status.8 views · 9 downloadsNational ID numbers national_ids.qwer · 1.0.0National identity numbers of 29 countries checked by their own rules (US SSN, UK NINO, CPF, Aadhaar, PH TIN, DNI and NIE, codice fiscale, PESEL...), with the birth date and sex read out where the number holds them.7 views · 7 downloadsThread 0 posts
On ISBN: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.