Class: Identifiers::ISBN

Inherits:
Object
  • Object
show all
Defined in:
lib/identifiers/isbn.rb

Constant Summary collapse

ISBN_13_REGEXP =
/
  \b
  (
    97[89]            # ISBN (GS1) Bookland prefix
    ([\p{Pd}\p{Zs}])? # Optional hyphenation
    (?:
      \d              # Digit
      \2?             # Optional hyphenation
    ){9}
    \d                # Check digit
  )
  \b
/x
ISBN_10_REGEXP =
/
  (?<!              # Don't match a hyphenated or spaced ISBN-13
    97[89]
    [\p{Pd}\p{Zs}]
  )
  \b
  (
    \d{1,5}           # Registration group identifier
    ([\p{Pd}\p{Zs}])? # Optional hyphenation
    (?:
      \d              # Digit
      \2?             # Optional hyphenation
    ){4,8}
    [\dX]             # Check digit
  )
  \b
/x
ISBN_A_REGEXP =
%r{
  \b
  (?<=10\.) # Directory indicator (always 10)
  97[89]\.  # ISBN (GS1) Bookland prefix
  \d{2,8}   # ISBN registration group element and publisher prefix
  /         # Prefix/suffix divider
  \d{1,7}   # ISBN title enumerator and check digit
  \b
}x

Class Method Summary collapse

Class Method Details

.digits_of(isbn) ⇒ Object



103
104
105
# File 'lib/identifiers/isbn.rb', line 103

def self.digits_of(isbn)
  isbn.to_s.each_char.map { |char| char == 'X' ? 10 : Integer(char) }.to_enum
end

.extract(str) ⇒ Object



43
44
45
# File 'lib/identifiers/isbn.rb', line 43

def self.extract(str)
  extract_isbn_as(str) + extract_thirteen_digit_isbns(str) + extract_ten_digit_isbns(str)
end

.extract_isbn_as(str) ⇒ Object



47
48
49
# File 'lib/identifiers/isbn.rb', line 47

def self.extract_isbn_as(str)
  extract_thirteen_digit_isbns(str.to_s.scan(ISBN_A_REGEXP).join("\n").tr('/.', ''))
end

.extract_ten_digit_isbns(str) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/identifiers/isbn.rb', line 60

def self.extract_ten_digit_isbns(str)
  str
    .to_s
    .scan(ISBN_10_REGEXP)
    .select { |isbn, hyphen| !hyphen || isbn.count(hyphen) == 3 }
    .map { |isbn, hyphen| isbn.delete(hyphen.to_s) }
    .select { |isbn| valid_isbn_10?(isbn) }
    .map { |isbn|
      isbn.chop!
      isbn.prepend('978')
      isbn << isbn_13_check_digit(isbn).to_s

      isbn
    }
end

.extract_thirteen_digit_isbns(str) ⇒ Object



51
52
53
54
55
56
57
58
# File 'lib/identifiers/isbn.rb', line 51

def self.extract_thirteen_digit_isbns(str)
  str
    .to_s
    .scan(ISBN_13_REGEXP)
    .select { |isbn, hyphen| !hyphen || isbn.count(hyphen) == 4 }
    .map { |isbn, hyphen| isbn.delete(hyphen.to_s) }
    .select { |isbn| valid_isbn_13?(isbn) }
end

.isbn_13_check_digit(isbn) ⇒ Object



76
77
78
79
80
81
82
83
84
85
# File 'lib/identifiers/isbn.rb', line 76

def self.isbn_13_check_digit(isbn)
  sum = digits_of(isbn).zip([1, 3].cycle).map { |digit, weight| digit * weight }.reduce(:+)
  check_digit = 10 - (sum % 10)

  if check_digit == 10
    0
  else
    check_digit
  end
end

.valid_isbn_10?(isbn) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
98
99
100
101
# File 'lib/identifiers/isbn.rb', line 95

def self.valid_isbn_10?(isbn)
  return false unless String(isbn).length == 10 && isbn =~ ISBN_10_REGEXP

  result = digits_of(isbn).with_index.map { |digit, weight| digit * weight.succ }.reduce(:+)

  (result % 11).zero?
end

.valid_isbn_13?(isbn) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
90
91
92
93
# File 'lib/identifiers/isbn.rb', line 87

def self.valid_isbn_13?(isbn)
  return false unless String(isbn).length == 13 && isbn =~ ISBN_13_REGEXP

  result = digits_of(isbn).zip([1, 3].cycle).map { |digit, weight| digit * weight }.reduce(:+)

  (result % 10).zero?
end