Validation

Check digits

Check 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.

Version 1.0.0 · 5 functions · 0 crates

#validation#barcodes#books#import

Functions

What use check_digits.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use check_digit, check_digit_valid from check_digits (Quillo 0.6).

check_digit(code: str, scheme: str) → stror an error

The check digit to append to code (digits only, spaces and dashes ignored) by scheme: "luhn" (card numbers, IMEI), "ean" (EAN-13, EAN-8, UPC-A, GTIN, ISBN-13), "isbn10" (may be "X"), "mod97" (ISO 7064 97-10: two digits, as IBANs and Belgian references use; letters count as 10 to 35) or "verhoeff".

check_digit_valid(code: str, scheme: str) → bool

Whether code, its check digit included at the end, is right by scheme (as for check_digit).

isbn_check(isbn: str) → any

An ISBN read and checked, 10 or 13 digits, dashes and spaces ignored: { valid, isbn13, isbn10 }, both forms written out when it has them (only 978 ISBNs have a 10-digit form).

barcode_check(code: str) → any

A product barcode read and checked: { valid, kind }, kind being "EAN-13", "EAN-8", "UPC-A", "GTIN-14", "ISBN" (EAN-13 starting 978 or 979) or null.

imei_valid(imei: str) → bool

Whether imei is a valid 15-digit phone IMEI (by its Luhn check digit).

Get it

$ quillo install check_digits               # check_digits.qwer into this folder$ quillo install check_digits --example     # and its example, check_digits.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/check_digits.qwer$ curl -O https://quillolang.org/plugins/files/check_digits.qwe

Then, in your program, all of it or only what you call:

main.qwe
use check_digits.qweruse check_digit, check_digit_valid from check_digits      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

check_digits.qwe
// A bookshop's catalog import: ISBNs and barcodes checked, and new internal codes given check digits.////   quillo run check_digits.qwe      then open http://localhost:8080/isbn?code=0-306-40615-2use "check_digits.qwer"route GET /isbn ? { code: str } = isbn_check(code)route GET /barcodes = ["4006381333931", "036000291452", "96385074", "4006381333932", "978-0-306-40615-7"] | map((c) => { code: c, check: barcode_check(c) })route GET /make ? { code: str = "100023" } = {    luhn: `${code}${check_digit(code, "luhn")?}`,    ean: check_digit("400638133393", "ean")?,    verhoeff: `${code}${check_digit(code, "verhoeff")?}`,    // An ISO 11649 creditor reference: the check digits of the reference with "RF" after it.    reference: `RF${check_digit("539007547034RF", "mod97")?}539007547034`,    verhoeff_236: check_digit("236", "verhoeff")?,    imei: imei_valid("490154203237518"),}
$ quillo run check_digits.qwe$ curl 'localhost:8080/isbn?code=0-306-40615-2'$ curl 'localhost:8080/barcodes'$ curl 'localhost:8080/make'

Source

check_digits.qwer
//! Check digits made and checked for the codes on books, products, phones and bank references: ISBN-10 and ISBN-13, EAN-13, EAN-8 and UPC-A, IMEI, Luhn, ISO 7064 mod 97-10 and Verhoeff.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};fn clean(code: &str) -> String {    code.chars().filter(|c| !c.is_whitespace() && *c != '-').collect::<String>().to_uppercase()}fn digits(s: &str) -> Option<Vec<u32>> {    if s.is_empty() || s.len() > 100 { return None }    s.chars().map(|c| c.to_digit(10)).collect()}fn luhn_digit(ds: &[u32]) -> u32 {    let s: u32 = ds.iter().rev().enumerate().map(|(i, &n)| if i % 2 == 0 { let x = n * 2; if x > 9 { x - 9 } else { x } } else { n }).sum();    (10 - s % 10) % 10}/// GS1 (EAN, UPC, ISBN-13): weights 3 and 1 from the right.fn gs1_digit(ds: &[u32]) -> u32 {    let s: u32 = ds.iter().rev().enumerate().map(|(i, &n)| if i % 2 == 0 { n * 3 } else { n }).sum();    (10 - s % 10) % 10}fn isbn10_digit(ds: &[u32]) -> char {    let s: u32 = ds.iter().enumerate().map(|(i, &n)| n * (10 - i as u32)).sum();    match (11 - s % 11) % 11 { 10 => 'X', c => char::from_digit(c, 10).unwrap_or('0') }}fn mod97(s: &str) -> Option<u64> {    let mut r = 0u64;    for c in s.chars() {        let v = if c.is_ascii_digit() { c.to_digit(10)? as u64 } else if c.is_ascii_uppercase() { c as u64 - 'A' as u64 + 10 } else { return None };        r = if v >= 10 { (r * 100 + v) % 97 } else { (r * 10 + v) % 97 };    }    Some(r)}const VD: [[u8; 10]; 10] = [[0,1,2,3,4,5,6,7,8,9],[1,2,3,4,0,6,7,8,9,5],[2,3,4,0,1,7,8,9,5,6],[3,4,0,1,2,8,9,5,6,7],[4,0,1,2,3,9,5,6,7,8],[5,9,8,7,6,0,4,3,2,1],[6,5,9,8,7,1,0,4,3,2],[7,6,5,9,8,2,1,0,4,3],[8,7,6,5,9,3,2,1,0,4],[9,8,7,6,5,4,3,2,1,0]];const VP: [[u8; 10]; 8] = [[0,1,2,3,4,5,6,7,8,9],[1,5,7,6,2,8,3,0,9,4],[5,8,0,3,7,9,6,1,4,2],[8,9,1,6,0,4,3,5,2,7],[9,4,5,3,1,2,6,8,7,0],[4,2,8,6,5,7,3,9,0,1],[2,7,9,3,8,0,6,4,1,5],[7,0,4,6,9,1,3,2,5,8]];const VINV: [u8; 10] = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];fn verhoeff(ds: &[u32], with_check: bool) -> u8 {    let mut c = 0u8;    for (i, &n) in ds.iter().rev().enumerate() {        let pos = if with_check { i } else { i + 1 };        c = VD[c as usize][VP[pos % 8][n as usize] as usize];    }    c}/// The check digit to append to `code` (digits only, spaces and dashes ignored) by `scheme`: "luhn" (card numbers, IMEI), "ean" (EAN-13, EAN-8, UPC-A, GTIN, ISBN-13), "isbn10" (may be "X"), "mod97" (ISO 7064 97-10: two digits, as IBANs and Belgian references use; letters count as 10 to 35) or "verhoeff".pub fn check_digit(code: &str, scheme: &str) -> Result<String, String> {    let c = clean(code);    if scheme == "mod97" {        let r = mod97(&format!("{}00", c)).ok_or("digits and letters only")?;        return Ok(format!("{:02}", 98 - r));    }    let ds = digits(&c).ok_or("digits only")?;    Ok(match scheme {        "luhn" => luhn_digit(&ds).to_string(),        "ean" => gs1_digit(&ds).to_string(),        "isbn10" => {            if ds.len() != 9 { return Err("an ISBN-10 has 9 digits before its check digit".into()) }            isbn10_digit(&ds).to_string()        }        "verhoeff" => VINV[verhoeff(&ds, false) as usize].to_string(),        o => return Err(format!("scheme is luhn, ean, isbn10, mod97 or verhoeff, not {:?}", o)),    })}/// Whether `code`, its check digit included at the end, is right by `scheme` (as for `check_digit`).pub fn check_digit_valid(code: &str, scheme: &str) -> bool {    let c = clean(code);    match scheme {        "mod97" => c.len() > 2 && mod97(&c) == Some(1),        "isbn10" => c.len() == 10 && digits(&c[..9]).map_or(false, |ds| isbn10_digit(&ds) == c.chars().last().unwrap_or(' ')),        "verhoeff" => digits(&c).map_or(false, |ds| ds.len() > 1 && verhoeff(&ds, true) == 0),        _ => c.len() > 1 && check_digit(&c[..c.len() - 1], scheme).map_or(false, |d| c.ends_with(&d)),    }}/// An ISBN read and checked, 10 or 13 digits, dashes and spaces ignored: `{ valid, isbn13, isbn10 }`, both forms written out when it has them (only 978 ISBNs have a 10-digit form).pub fn isbn_check(isbn: &str) -> Value {    let c = clean(isbn);    let (valid, isbn13) = match c.len() {        10 if check_digit_valid(&c, "isbn10") => {            let body = format!("978{}", &c[..9]);            (true, Some(format!("{}{}", body, gs1_digit(&digits(&body).unwrap_or_default()))))        }        13 if (c.starts_with("978") || c.starts_with("979")) && check_digit_valid(&c, "ean") => (true, Some(c.clone())),        _ => (false, None),    };    let isbn10 = isbn13.as_ref().filter(|s| s.starts_with("978")).and_then(|s| digits(&s[3..12]).map(|ds| format!("{}{}", &s[3..12], isbn10_digit(&ds))));    json!({ "valid": valid, "isbn13": isbn13, "isbn10": isbn10 })}/// A product barcode read and checked: `{ valid, kind }`, `kind` being "EAN-13", "EAN-8", "UPC-A", "GTIN-14", "ISBN" (EAN-13 starting 978 or 979) or null.pub fn barcode_check(code: &str) -> Value {    let c = clean(code);    let ok = digits(&c).is_some() && check_digit_valid(&c, "ean");    let kind = match c.len() {        8 => Some("EAN-8"),        12 => Some("UPC-A"),        13 if c.starts_with("978") || c.starts_with("979") => Some("ISBN"),        13 => Some("EAN-13"),        14 => Some("GTIN-14"),        _ => None,    };    json!({ "valid": ok && kind.is_some(), "kind": kind })}/// Whether `imei` is a valid 15-digit phone IMEI (by its Luhn check digit).pub fn imei_valid(imei: &str) -> bool {    let c = clean(imei);    c.len() == 15 && check_digit_valid(&c, "luhn")}
ISBN isbn.qwer · 1.0.0ISBNs 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.booksvalidationcodesebooks6 views · 6 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.validationlinksbookssearch6 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.financevalidationcodesimport7 views · 6 downloadsBibTeX bibtex.qwer · 1.0.0BibTeX and BibLaTeX bibliographies read, with LaTeX accents and macros resolved; reference lists in APA or IEEE style, CSL-JSON for Zotero and pandoc, and entries written back.booksimportexportwritingdocs7 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.paymentsformscheckoutvalidation7 views · 7 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.validationidentitycomplianceforms7 views · 7 downloads
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On Check digits: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.