Skip to content

Commit

Permalink
add checking ISBN, accepting 10 & 13 digits, ignoring hypens
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew2net committed Dec 17, 2023
1 parent 2eb6a68 commit cd46a9a
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 29 deletions.
74 changes: 67 additions & 7 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ image:https://img.shields.io/github/commits-since/relaton/relaton-isbn/latest.sv
RelatonIsbn is a Ruby gem that implements the
https://github.com/metanorma/metanorma-model-iso#iso-bibliographic-item[IsoBibliographicItem model].

You can use it to retrieve metadata of Standards from https://crossref.org, and
You can use it to retrieve metadata of Standards from https://openlibrary.org, and
access such metadata through the `BibliographicItem` object.

== Installation
Expand Down Expand Up @@ -53,20 +53,80 @@ RelatonIsbn.configure do |config|
end
----

=== Retrieving items of known type using OpenLibrary API
=== Retrieving bibliographic items using OpenLibrary API

If the resulting bibliographic item returned from CrossRef is a known Relaton
flavor, such as a NIST or IEEE standard, the corresponding Relaton class object
will be returned via the call.
To retrieve bibliographic items, use `RelatonIsbn::OpenLibrary.get` method with ISBN-10 or ISBN-13 as an argument. Prefix `ISBN` and hyphens are optional. The method returns `RelatonBib::BibliographicItem` object.

[source,ruby]
----
# get document by ISBN
RelatonIsbn::OpenLibrary.get "ISBN 9780120644810"
# get document by ISBN-13
> bibitem = RelatonIsbn::OpenLibrary.get "ISBN 978-0-12-064481-0"
[relaton-isbn] (ISBN 9780120644810) Fetching from OpenLibrary ...
[relaton-isbn] (ISBN 9780120644810) Found: `9780120644810`
=> #<RelatonBib::BibliographicItem:0x0000000113889258
...
# get document by ISBN-10
> RelatonIsbn::OpenLibrary.get "0120644819"
[relaton-isbn] (ISBN 0120644819) Fetching from OpenLibrary ...
[relaton-isbn] (ISBN 0120644819) Found: `9780120644810`
=> #<RelatonBib::BibliographicItem:0x00000001098ac960
...
----

=== Serializing bibliographic items
[source,ruby]
----
# serialize to XML
> puts bibitem.to_xml
<bibitem id="9780120644810" schema-version="v1.2.7">
<title type="main" format="text/plain">Graphics gems II</title>
<uri type="src">http://openlibrary.org/books/OL21119585M/Graphics_gems_II</uri>
<docidentifier type="ISBN" primary="true">9780120644810</docidentifier>
<date type="published">
<on>1991</on>
</date>
<contributor>
<role type="author"/>
<person>
<name>
<completename>James Arvo</completename>
</name>
</person>
</contributor>
<contributor>
<role type="publisher"/>
<organization>
<name>AP Professional</name>
</organization>
</contributor>
<place>
<city>Boston</city>
</place>
<place>
<city>London</city>
</place>
</bibitem>
# serialize to bibdata XML
> puts bibitem.to_xml bibdata: true
<bibdata schema-version="v1.2.7">
<title type="main" format="text/plain">Graphics gems II</title>
...
# serialize to hash
> bibitem.to_hash
=> {"schema-version"=>"v1.2.7",
"id"=>"9780120644810",
"title"=>[{"content"=>"Graphics gems II", "format"=>"text/plain", "type"=>"main"}],
"link"=>[{"content"=>"http://openlibrary.org/books/OL21119585M/Graphics_gems_II", "type"=>"src"}],
"docid"=>[{"id"=>"9780120644810", "type"=>"ISBN", "primary"=>true}],
"date"=>[{"type"=>"published", "value"=>"1991"}],
"contributor"=>
[{"person"=>{"name"=>{"completename"=>{"content"=>"James Arvo"}}, "url"=>"http://openlibrary.org/authors/OL2646519A/James_Arvo"}, "role"=>[{"type"=>"author"}]},
{"organization"=>{"name"=>[{"content"=>"AP Professional"}]}, "role"=>[{"type"=>"publisher"}]}],
"revdate"=>"1991",
"place"=>[{"city"=>"Boston"}, {"city"=>"London"}]}
----

== Development
Expand Down
1 change: 1 addition & 0 deletions lib/relaton_isbn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative "relaton_isbn/version"
require_relative "relaton_isbn/config"
require_relative "relaton_isbn/util"
require_relative "relaton_isbn/isbn"
require_relative "relaton_isbn/parser"
require_relative "relaton_isbn/open_library"

Expand Down
59 changes: 59 additions & 0 deletions lib/relaton_isbn/isbn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module RelatonIsbn
class Isbn
#
# Create ISBN object.
#
# @param [String] isbn ISBN 13 number
#
def initialize(isbn)
@isbn = isbn&.delete("-")&.sub(/^ISBN\s/, "")
end

def parse
convert_to13
end

def check?
case @isbn
when /^\d{9}[\dX]$/i then check10?
when /^\d{13}$/ then check13?
else false
end
end

def check10?
@isbn[9] == calc_check_digit10
end

def check13?
@isbn[12] == calc_check_digit13
end

def calc_check_digit10
sum = 0
@isbn[..-2].chars.each_with_index do |c, i|
sum += c.to_i * (10 - i)
end
chk = (11 - sum % 11) % 11
chk == 10 ? "X" : chk.to_s
end

def calc_check_digit13
sum = 0
@isbn[..-2].chars.each_with_index do |c, i|
sum += c.to_i * (i.even? ? 1 : 3)
end
((10 - sum % 10) % 10).to_s
end

def convert_to13
return unless check?

return @isbn if @isbn.size == 13

@isbn = "978#{@isbn}"
@isbn[12] = calc_check_digit13
@isbn
end
end
end
15 changes: 9 additions & 6 deletions lib/relaton_isbn/open_library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ module OpenLibrary

ENDPOINT = "http://openlibrary.org/api/volumes/brief/isbn/".freeze

def get(ref, _date = nil, _opts = {})
def get(ref, _date = nil, _opts = {}) # rubocop:disable Metrics/MethodLength
Util.warn "(#{ref}) Fetching from OpenLibrary ..."

resp = request_api ref
isbn = Isbn.new(ref).parse
unless isbn
Util.warn "(#{ref}) Incorrect ISBN."
return
end

resp = request_api isbn
unless resp
Util.warn "(#{ref}) Not found."
return
Expand All @@ -21,10 +27,7 @@ def get(ref, _date = nil, _opts = {})
bib
end

def request_api(ref)
/ISBN\s*(?<isbn>\w+)/ =~ ref
return unless isbn

def request_api(isbn)
uri = URI "#{ENDPOINT}#{isbn}.json"
response = Net::HTTP.get_response uri
return unless response.is_a? Net::HTTPSuccess
Expand Down
2 changes: 1 addition & 1 deletion lib/relaton_isbn/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module RelatonIsbn
VERSION = "1.17.0".freeze
VERSION = "1.17.1".freeze
end
55 changes: 55 additions & 0 deletions spec/relaton_isbn/isbn_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe RelatonIsbn::Isbn do
it "creates ISBN object" do
subj = described_class.new("ISBN 0-12-064481-9")
expect(subj).to be_instance_of RelatonIsbn::Isbn
expect(subj.instance_variable_get(:@isbn)).to eq "0120644819"
end

context "parse ISBN" do
it "with 10-digit ISBN" do
expect(described_class.new("ISBN 0-12-064481-9").parse).to eq "9780120644810"
end

it "with 13-digit ISBN" do
expect(described_class.new("978-0-12-064481-0").parse).to eq "9780120644810"
end

it "with incorrect ISBN" do
expect(described_class.new("978-0-12-064481-").parse).to be_nil
end

it "with incorrect 10-digits ISBN" do
expect(described_class.new("0-12-064481-8").parse).to be_nil
end

it "with incorrect 13-digits ISBN" do
expect(described_class.new("978-0-12-064481-1").parse).to be_nil
end

it "with nil" do
expect(described_class.new(nil).parse).to be_nil
end
end

context "check ISBN" do
it "10-digit ISBN" do
expect(described_class.new("0-12-064481-9").check?).to be true
end

it "13-digit ISBN" do
expect(described_class.new("978-0-12-064481-0").check?).to be true
end

it "incorrect 10-digits ISBN" do
expect(described_class.new("0-12-064481-8").check?).to be false
end

it "incorrect 13-digits ISBN" do
expect(described_class.new("978-0-12-064481-1").check?).to be false
end

it "incorrect ISBN" do
expect(described_class.new("978-0-12-064481-").check?).to be false
end
end
end
31 changes: 16 additions & 15 deletions spec/relaton_isbn/openlibrary_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
context "get" do
it "success" do
expect do
expect(described_class).to receive(:request_api).with("ISBN 9780120644810").and_return :doc
expect(described_class).to receive(:request_api).with("9780120644810").and_return :doc
bib = double "bib", docidentifier: [double("id", id: "id")]
expect(RelatonIsbn::Parser).to receive(:parse).with(:doc).and_return bib
expect(described_class.get("ISBN 9780120644810")).to eq bib
Expand All @@ -12,45 +12,46 @@

it "not found" do
expect do
expect(described_class).to receive(:request_api).with("ISBN 9780120644810").and_return nil
expect(described_class).to receive(:request_api).with("9780120644810").and_return nil
expect(RelatonIsbn::Parser).not_to receive(:parse)
expect(described_class.get("ISBN 9780120644810")).to be_nil
end.to output(include("[relaton-isbn] (ISBN 9780120644810) Not found.")).to_stderr_from_any_process
end

it "incorrect ISBN" do
expect do
expect(described_class).not_to receive(:request_api)
expect(described_class.get("ISBN")).to be_nil
end.to output(include("[relaton-isbn] (ISBN) Incorrect ISBN.")).to_stderr_from_any_process
end
end

context "request_api" do
let(:resp) { double "response" }

it "success" do
before do
expect(URI).to receive(:parse).with("http://openlibrary.org/api/volumes/brief/isbn/9780120644810.json")
.and_return :uri
end

it "success" do
expect(resp).to receive(:is_a?).with(Net::HTTPSuccess).and_return true
expect(resp).to receive(:body).and_return '{"records": {"/books/OL21119585M": {"publishDates": ["2008"]}}}'
expect(Net::HTTP).to receive(:get_response).with(:uri).and_return resp
expect(described_class.request_api("ISBN 9780120644810")).to eq({ "publishDates" => ["2008"] })
end

it "incorrect ISBN" do
expect(URI).not_to receive(:parse)
expect(described_class.request_api("ISBN")).to be_nil
expect(described_class.request_api("9780120644810")).to eq({ "publishDates" => ["2008"] })
end

it "unsuccess" do
expect(URI).to receive(:parse).with("http://openlibrary.org/api/volumes/brief/isbn/9780120644810.json")
.and_return :uri
expect(resp).to receive(:is_a?).with(Net::HTTPSuccess).and_return false
expect(Net::HTTP).to receive(:get_response).with(:uri).and_return resp
expect(described_class.request_api("ISBN 9780120644810")).to be_nil
expect(described_class.request_api("9780120644810")).to be_nil
end

it "no records" do
expect(URI).to receive(:parse).with("http://openlibrary.org/api/volumes/brief/isbn/9780120644810.json")
.and_return :uri
expect(resp).to receive(:is_a?).with(Net::HTTPSuccess).and_return true
expect(resp).to receive(:body).and_return '{"records": {}}'
expect(Net::HTTP).to receive(:get_response).with(:uri).and_return resp
expect(described_class.request_api("ISBN 9780120644810")).to be_nil
expect(described_class.request_api("9780120644810")).to be_nil
end
end
end

0 comments on commit cd46a9a

Please sign in to comment.