LCOV - code coverage report
Current view: top level - src/formats - isbn.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 93.1 % 29 27
Test Date: 2026-06-16 03:31:00 Functions: - 0 0
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright 2026 The Authors
       2              : //
       3              : // Licensed under the Apache License, Version 2.0 (the "License");
       4              : // you may not use this file except in compliance with the License.
       5              : // You may obtain a copy of the License at
       6              : //
       7              : //      https://www.apache.org/licenses/LICENSE-2.0
       8              : //
       9              : // Unless required by applicable law or agreed to in writing, software
      10              : // distributed under the License is distributed on an "AS IS" BASIS,
      11              : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12              : // See the License for the specific language governing permissions and
      13              : // limitations under the License.
      14              : 
      15              : import 'package:characters/characters.dart';
      16              : 
      17              : import 'digit_string.dart' show DigitString;
      18              : 
      19              : /// An International Standard Book Number (ISBN).
      20              : ///
      21              : /// See:
      22              : ///
      23              : /// - [ISBN Users' Manual](https://www.isbn-international.org/content/isbn-users-manual/29)
      24              : /// - [OCLC International Standard Book Number](https://www.oclc.org/bibformats/en/0xx/020.html)
      25              : /// - [Administration of the ISBN System](https://isbn-information.com/administration-of-the-isbn-system.html)
      26              : class Isbn13 {
      27            2 :   static final gsiPrefixes = const ['978', '979'];
      28              : 
      29              :   final DigitString isbn;
      30              : 
      31              :   /// The GS1 (formerly EAN) Element (e.g., '978' or '979').
      32            0 :   DigitString get prefix => isbn.substring(0, 3);
      33              : 
      34              :   /// The ISBN 13 check digit.
      35            0 :   DigitString get checkDigit => isbn.valueAt(isbn.length - 1);
      36              : 
      37            1 :   Isbn13._(this.isbn);
      38              : 
      39            1 :   @override
      40            2 :   String toString() => isbn.toString();
      41              : 
      42            1 :   static DigitString? _extractISBNString(String input) {
      43            3 :     if (input.characters.length > 22) {
      44              :       return null;
      45              :     }
      46              : 
      47            1 :     var str = DigitString.extract(input);
      48              : 
      49            2 :     return str.length == 13 ? str : null;
      50              :   }
      51              : 
      52            1 :   static Isbn13? tryParse(String input) {
      53            1 :     var isbn = _extractISBNString(input);
      54              : 
      55              :     if (isbn == null) {
      56              :       return null;
      57              :     }
      58              : 
      59            1 :     if (!_validatePrefix(isbn)) {
      60              :       return null;
      61              :     }
      62              : 
      63            1 :     if (!validateChecksum(isbn)) {
      64              :       return null;
      65              :     }
      66              : 
      67            1 :     return Isbn13._(isbn);
      68              :   }
      69              : 
      70            1 :   static int? calculateIsbn13CheckDigit(DigitString input) {
      71              :     const weights = [1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3];
      72              : 
      73            1 :     var inputList = input.intList;
      74              : 
      75            2 :     if (input.length != 13) {
      76              :       return null;
      77              :     }
      78              : 
      79              :     var sumProd = 0;
      80            2 :     for (var i = 0; i < 12; i++) {
      81            4 :       sumProd += inputList[i] * weights[i];
      82              :     }
      83            3 :     return (10 - sumProd % 10) % 10;
      84              :   }
      85              : 
      86            1 :   static bool _validatePrefix(DigitString isbn) {
      87            2 :     final prefix = isbn.substring(0, 3).toString();
      88              : 
      89            2 :     if (!gsiPrefixes.contains(prefix)) {
      90              :       return false;
      91              :     }
      92              :     return true;
      93              :   }
      94              : 
      95              :   /// If [input] is a valid ISBN, returns `true`.
      96              :   ///
      97              :   /// Refer to
      98              :   /// [APPENDIX 1 Check digit calculation](https://www.isbn-international.org/content/isbn-users-manual/29)
      99            1 :   static bool validateChecksum(DigitString isbn) {
     100            2 :     if (isbn.length != 13) {
     101              :       return false;
     102              :     }
     103              : 
     104            1 :     var checkDigit = isbn.valueAt(12);
     105              : 
     106            3 :     return calculateIsbn13CheckDigit(isbn) == checkDigit.intValue;
     107              :   }
     108              : 
     109            2 :   static bool isValid(String value) => tryParse(value) != null;
     110              : }
        

Generated by: LCOV version 2.0-1