alexandria-book-collection-manager 0.7.8 → 0.7.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +48 -53
  3. data/.rubocop.yml +18 -5
  4. data/.rubocop_todo.yml +31 -37
  5. data/.simplecov +2 -2
  6. data/CHANGELOG.md +37 -0
  7. data/ChangeLog.0 +19 -19
  8. data/INSTALL.md +3 -5
  9. data/README.md +0 -5
  10. data/Rakefile +14 -14
  11. data/alexandria-book-collection-manager.gemspec +35 -34
  12. data/doc/FAQ +2 -2
  13. data/lib/alexandria/about.rb +1 -1
  14. data/lib/alexandria/book_providers/bl_provider.rb +88 -0
  15. data/lib/alexandria/book_providers/loc_provider.rb +38 -0
  16. data/lib/alexandria/book_providers/sbn_provider.rb +108 -0
  17. data/lib/alexandria/book_providers/thalia_provider.rb +1 -1
  18. data/lib/alexandria/book_providers/web.rb +2 -2
  19. data/lib/alexandria/book_providers/worldcat.rb +9 -7
  20. data/lib/alexandria/book_providers/z3950_provider.rb +199 -0
  21. data/lib/alexandria/book_providers.rb +10 -25
  22. data/lib/alexandria/console.rb +2 -2
  23. data/lib/alexandria/default_preferences.rb +1 -1
  24. data/lib/alexandria/export_library.rb +14 -14
  25. data/lib/alexandria/image_fetcher.rb +25 -0
  26. data/lib/alexandria/import_library.rb +10 -10
  27. data/lib/alexandria/library_store.rb +4 -5
  28. data/lib/alexandria/models/book.rb +13 -0
  29. data/lib/alexandria/models/library.rb +15 -23
  30. data/lib/alexandria/preferences.rb +4 -6
  31. data/lib/alexandria/{book_providers/pseudomarc.rb → pseudo_marc_parser.rb} +2 -2
  32. data/lib/alexandria/scanners/cue_cat.rb +1 -1
  33. data/lib/alexandria/smart_library.rb +2 -2
  34. data/lib/alexandria/ui/about_dialog.rb +1 -1
  35. data/lib/alexandria/ui/acquire_dialog.rb +6 -9
  36. data/lib/alexandria/ui/alert_dialog.rb +2 -2
  37. data/lib/alexandria/ui/barcode_animation.rb +1 -1
  38. data/lib/alexandria/ui/book_properties_dialog_base.rb +5 -9
  39. data/lib/alexandria/ui/completion_models.rb +1 -5
  40. data/lib/alexandria/ui/conflict_while_copying_dialog.rb +1 -1
  41. data/lib/alexandria/ui/icons.rb +2 -2
  42. data/lib/alexandria/ui/init.rb +10 -4
  43. data/lib/alexandria/ui/listview.rb +1 -1
  44. data/lib/alexandria/ui/multi_drag_treeview.rb +1 -1
  45. data/lib/alexandria/ui/new_book_dialog.rb +11 -13
  46. data/lib/alexandria/ui/new_book_dialog_manual.rb +1 -1
  47. data/lib/alexandria/ui/preferences_dialog.rb +2 -2
  48. data/lib/alexandria/ui/provider_preferences_base_dialog.rb +1 -1
  49. data/lib/alexandria/ui/really_delete_dialog.rb +1 -1
  50. data/lib/alexandria/ui/ui_manager.rb +17 -25
  51. data/lib/alexandria/version.rb +1 -1
  52. data/lib/alexandria/web_themes.rb +1 -1
  53. data/lib/alexandria.rb +6 -5
  54. data/po/cs.po +90 -125
  55. data/po/cy.po +87 -125
  56. data/po/de.po +96 -125
  57. data/po/el.po +96 -125
  58. data/po/es.po +96 -125
  59. data/po/fr.po +90 -125
  60. data/po/ga.po +83 -124
  61. data/po/gl.po +90 -125
  62. data/po/it.po +90 -125
  63. data/po/ja.po +90 -125
  64. data/po/mk.po +96 -125
  65. data/po/nb.po +90 -125
  66. data/po/nl.po +107 -124
  67. data/po/pl.po +113 -124
  68. data/po/pt.po +90 -125
  69. data/po/pt_BR.po +90 -125
  70. data/po/ru.po +92 -124
  71. data/po/sk.po +90 -125
  72. data/po/sv.po +90 -125
  73. data/po/uk.po +90 -125
  74. data/po/zh_TW.po +90 -125
  75. data/schemas/alexandria.schemas +1 -1
  76. data/share/gnome/help/alexandria/C/adding-books.xml +3 -4
  77. data/share/gnome/help/alexandria/C/introduction.xml +0 -16
  78. data/share/gnome/help/alexandria/C/searching.xml +1 -4
  79. data/share/gnome/help/alexandria/C/settings.xml +0 -30
  80. data/share/gnome/help/alexandria/fr/alexandria.xml +4 -159
  81. data/share/gnome/help/alexandria/ja/adding-books.xml +1 -1
  82. data/share/gnome/help/alexandria/ja/introduction.xml +0 -15
  83. data/share/gnome/help/alexandria/ja/searching.xml +3 -7
  84. data/share/gnome/help/alexandria/ja/settings.xml +0 -27
  85. data/spec/alexandria/book_providers/bl_provider_spec.rb +13 -0
  86. data/spec/alexandria/book_providers/loc_provider_spec.rb +17 -0
  87. data/spec/alexandria/book_providers/sbn_provider_spec.rb +13 -0
  88. data/spec/alexandria/book_providers/thalia_provider_spec.rb +1 -1
  89. data/spec/alexandria/book_providers/world_cat_provider_spec.rb +22 -10
  90. data/spec/alexandria/book_providers_spec.rb +0 -81
  91. data/spec/alexandria/book_spec.rb +5 -3
  92. data/spec/alexandria/export_library_spec.rb +8 -8
  93. data/spec/alexandria/library_spec.rb +83 -51
  94. data/spec/alexandria/library_store_spec.rb +1 -1
  95. data/spec/alexandria/preferences_spec.rb +7 -7
  96. data/spec/alexandria/pseudo_marc_parser_spec.rb +71 -0
  97. data/spec/alexandria/scanners/cue_cat_spec.rb +11 -4
  98. data/spec/alexandria/smart_library_spec.rb +7 -5
  99. data/spec/alexandria/ui/about_dialog_spec.rb +1 -1
  100. data/spec/alexandria/ui/acquire_dialog_spec.rb +1 -1
  101. data/spec/alexandria/ui/alert_dialog_spec.rb +5 -3
  102. data/spec/alexandria/ui/bad_isbns_dialog_spec.rb +1 -1
  103. data/spec/alexandria/ui/book_properties_dialog_spec.rb +4 -4
  104. data/spec/alexandria/ui/confirm_erase_dialog_spec.rb +18 -2
  105. data/spec/alexandria/ui/conflict_while_copying_dialog_spec.rb +1 -1
  106. data/spec/alexandria/ui/error_dialog_spec.rb +13 -2
  107. data/spec/alexandria/ui/export_dialog_spec.rb +3 -3
  108. data/spec/alexandria/ui/iconview_spec.rb +1 -1
  109. data/spec/alexandria/ui/import_dialog_spec.rb +3 -3
  110. data/spec/alexandria/ui/keep_bad_isbn_dialog_spec.rb +1 -1
  111. data/spec/alexandria/ui/new_book_dialog_manual_spec.rb +4 -4
  112. data/spec/alexandria/ui/new_book_dialog_spec.rb +2 -2
  113. data/spec/alexandria/ui/new_provider_dialog_spec.rb +3 -3
  114. data/spec/alexandria/ui/new_smart_library_dialog_spec.rb +9 -7
  115. data/spec/alexandria/ui/preferences_dialog_spec.rb +1 -1
  116. data/spec/alexandria/ui/provider_preferences_dialog_spec.rb +2 -2
  117. data/spec/alexandria/ui/really_delete_dialog_spec.rb +1 -1
  118. data/spec/alexandria/ui/sidepane_manager_spec.rb +1 -1
  119. data/spec/alexandria/ui/skip_entry_dialog_spec.rb +18 -2
  120. data/spec/alexandria/ui/smart_library_properties_dialog_spec.rb +2 -2
  121. data/spec/alexandria/ui/ui_manager_spec.rb +83 -5
  122. data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +2 -0
  123. data/spec/spec_helper.rb +23 -32
  124. data/util/rake/fileinstall.rb +12 -12
  125. data/util/rake/gettextgenerate.rb +1 -1
  126. data/util/rake/omfgenerate.rb +1 -1
  127. metadata +73 -58
  128. data/lib/alexandria/book_providers/adlibris.rb +0 -191
  129. data/lib/alexandria/book_providers/amazon_aws.rb +0 -239
  130. data/lib/alexandria/book_providers/amazon_ecs_util.rb +0 -373
  131. data/lib/alexandria/book_providers/barnes_and_noble.rb +0 -209
  132. data/lib/alexandria/book_providers/proxis.rb +0 -176
  133. data/lib/alexandria/book_providers/siciliano.rb +0 -256
  134. data/lib/alexandria/book_providers/z3950.rb +0 -408
@@ -2,14 +2,10 @@
2
2
 
3
3
  require_relative "lib/alexandria/version"
4
4
 
5
- Gem::Specification.new do |s|
6
- s.name = "alexandria-book-collection-manager"
7
- s.version = Alexandria::VERSION
8
-
9
- s.summary = "GNOME application for managing collections of books"
10
- s.required_ruby_version = ">= 2.5.0"
11
-
12
- s.authors = [
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "alexandria-book-collection-manager"
7
+ spec.version = Alexandria::VERSION
8
+ spec.authors = [
13
9
  "Alexander McCormmach",
14
10
  "Aymeric Nys",
15
11
  "Cathal Mc Ginley",
@@ -32,37 +28,42 @@ Gem::Specification.new do |s|
32
28
  "Timothy Malone",
33
29
  "Zachary P. Landau"
34
30
  ]
35
- s.email = ["matijs@matijs.net"]
36
- s.homepage = "http://www.github.com/mvz/alexandria-book-collection-manager"
31
+ spec.email = ["matijs@matijs.net"]
37
32
 
38
- s.license = "GPL-2"
33
+ spec.summary = "GNOME application for managing collections of books"
39
34
 
40
- s.files = `git ls-files -z`.split("\0") |
41
- ["lib/alexandria/default_preferences.rb"]
35
+ spec.homepage = "http://www.github.com/mvz/alexandria-book-collection-manager"
36
+ spec.license = "GPL-2.0+"
42
37
 
43
- s.executables = ["alexandria"]
38
+ spec.required_ruby_version = ">= 2.6.0"
39
+
40
+ spec.metadata["rubygems_mfa_required"] = "true"
41
+
42
+ spec.files = `git ls-files -z`.split("\0") |
43
+ ["lib/alexandria/default_preferences.rb"]
44
44
 
45
- s.rdoc_options = ["--main", "README.md"]
45
+ spec.executables = ["alexandria"]
46
46
 
47
- s.add_runtime_dependency("gettext", ["~> 3.1"])
48
- s.add_runtime_dependency("gstreamer", ["~> 3.4.3"])
49
- s.add_runtime_dependency("gtk3", ["~> 3.4.3"])
50
- s.add_runtime_dependency("hpricot", ["~> 0.8.5"])
51
- s.add_runtime_dependency("htmlentities", ["~> 4.3"])
52
- s.add_runtime_dependency("image_size", ["~> 2.0"])
53
- s.add_runtime_dependency("marc", ["~> 1.0.0"])
54
- s.add_runtime_dependency("psych", ["~> 3.2.0"])
55
- s.add_runtime_dependency("zoom", ["~> 0.5.0"])
47
+ spec.require_paths = ["lib"]
48
+ spec.rdoc_options = ["--main", "README.md"]
56
49
 
57
- s.add_development_dependency("gnome_app_driver", "~> 0.3.0")
58
- s.add_development_dependency("minitest", ["~> 5.0"])
59
- s.add_development_dependency("rake", ["~> 13.0"])
60
- s.add_development_dependency("rspec", ["~> 3.0"])
61
- s.add_development_dependency("rubocop", "~> 0.93.1")
62
- s.add_development_dependency("rubocop-i18n", ["~> 2.0.2"])
63
- s.add_development_dependency("rubocop-performance", "~> 1.9.0")
64
- s.add_development_dependency("rubocop-rspec", "~> 1.44.1")
65
- s.add_development_dependency("webmock", "~> 3.9")
50
+ spec.add_runtime_dependency "gettext", ["~> 3.1"]
51
+ spec.add_runtime_dependency "gstreamer", ["~> 4.0.2"]
52
+ spec.add_runtime_dependency "gtk3", ["~> 4.0.2"]
53
+ spec.add_runtime_dependency "htmlentities", ["~> 4.3"]
54
+ spec.add_runtime_dependency "image_size", ["~> 3.0"]
55
+ spec.add_runtime_dependency "marc", ">= 1.0", "< 1.3"
56
+ spec.add_runtime_dependency "nokogiri", ["~> 1.11"]
57
+ spec.add_runtime_dependency "psych", ">= 3.2", "< 4.1"
58
+ spec.add_runtime_dependency "zoom", ["~> 0.5.0"]
66
59
 
67
- s.require_paths = ["lib"]
60
+ spec.add_development_dependency "gnome_app_driver", "~> 0.3.2"
61
+ spec.add_development_dependency "rake", ["~> 13.0"]
62
+ spec.add_development_dependency "rspec", ["~> 3.0"]
63
+ spec.add_development_dependency "rubocop", "~> 1.32"
64
+ spec.add_development_dependency "rubocop-i18n", ["~> 3.0"]
65
+ spec.add_development_dependency "rubocop-performance", "~> 1.13"
66
+ spec.add_development_dependency "rubocop-rake", "~> 0.6.0"
67
+ spec.add_development_dependency "rubocop-rspec", "~> 2.7"
68
+ spec.add_development_dependency "webmock", "~> 3.9"
68
69
  end
data/doc/FAQ CHANGED
@@ -291,8 +291,8 @@ of "Name Surname".
291
291
 
292
292
  Answer: Alexandria relies on what the providers say:
293
293
  some providers report "Surname Name": Webster.it, Ibs.it,
294
- some "Name Surname": Bn, Thalia, Bol.it, Adlibris, L Siciliano, Worldcat,
295
- and some without any rule: Amazon, Proxis.
294
+ some "Name Surname": Bn, Thalia, Bol.it, Worldcat,
295
+ and some without any rule: Amazon.
296
296
  You can change, in the preferences, the order in which the providers are
297
297
  queried.
298
298
 
@@ -26,7 +26,7 @@ module Alexandria
26
26
  bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
27
27
  DESCRIPTION = _("A program to help you manage your book collection.")
28
28
  COPYRIGHT = "Copyright (C) 2004-2006 Laurent Sansonetti\n" \
29
- "Copyright (C) 2007-2010,2014,2015 Alexandria Contributors"
29
+ "Copyright (C) 2007-2010,2014,2015 Alexandria Contributors"
30
30
  AUTHORS = [
31
31
  "Alexander McCormmach <alexander@tunicate.org>",
32
32
  "Aymeric Nys <aymeric@nnx.com>",
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "alexandria/book_providers/z3950_provider"
8
+
9
+ module Alexandria
10
+ class BookProviders
11
+ class BLProvider < Z3950Provider
12
+ # http://en.wikipedia.org/wiki/Copac
13
+ # http://en.wikipedia.org/wiki/British_Library
14
+ # http://www.bl.uk/catalogues/z3950fullaccess.html
15
+ # http://www.bl.uk/catalogues/z3950copacaccess.html
16
+ #
17
+ # FIXME: switch from BL to Copac, which incudes the BL itself and many more
18
+ # libraries: http://copac.ac.uk/libraries/
19
+ #
20
+ # Details: http://copac.ac.uk/interfaces/z39.50/
21
+ # The SUTRS format used by Copac is different from the one used by BL
22
+ unabstract
23
+
24
+ include GetText
25
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
26
+
27
+ def initialize
28
+ super("BL", _("British Library"))
29
+ prefs.variable_named("hostname").default_value = "z3950cat.bl.uk"
30
+ prefs.variable_named("port").default_value = 9909
31
+ prefs.variable_named("database").default_value = "BLAC"
32
+ prefs.variable_named("record_syntax").default_value = "SUTRS"
33
+ prefs.variable_named("charset").default_value = "ISO-8859-1"
34
+ prefs.read
35
+ end
36
+
37
+ def url(book)
38
+ "http://copac.ac.uk/openurl?isbn=" + Library.canonicalise_isbn(book.isbn)
39
+ rescue StandardError => ex
40
+ log.warn { "Cannot create url for book #{book}; #{ex.message}" }
41
+ nil
42
+ end
43
+
44
+ private
45
+
46
+ def books_from_sutrs(resultset)
47
+ results = []
48
+ resultset[0..9].each do |record|
49
+ text = record.render(prefs["charset"], "UTF-8")
50
+ # File.open(',bl.marc', 'wb') {|f| f.write(text) }
51
+ log.debug { text }
52
+
53
+ title = isbn = publisher = publish_year = edition = nil
54
+ authors = []
55
+
56
+ text.split("\n").each do |line|
57
+ if (md = /^Title:\s+(.*)$/.match(line))
58
+ title = md[1].sub(/\.$/, "").squeeze(" ")
59
+ elsif (md = /^(?:Added Person|ME-Personal) Name:\s+(.*),[^,]+$/.match(line))
60
+ authors << md[1]
61
+ elsif (md = /^ISBN:\s+([\dXx]+)/.match(line))
62
+ isbn = Library.canonicalise_ean(md[1])
63
+ elsif (md = /^Imprint:.+:\s*(.+),/.match(line))
64
+ publisher = md[1]
65
+ end
66
+ end
67
+
68
+ log.debug do
69
+ msg = "Parsing SUTRS"
70
+ msg += "\n title: #{title}"
71
+ msg += "\n authors: #{authors.join(' and ')}"
72
+ msg += "\n isbn: #{isbn}"
73
+ msg += "\n publisher: #{publisher}"
74
+ msg += "\n edition: #{edition}"
75
+ msg
76
+ end
77
+
78
+ if title # and !authors.empty?
79
+ book = Book.new(title, authors, isbn, (publisher || nil),
80
+ (publish_year || nil), (edition || nil))
81
+ results << [book]
82
+ end
83
+ end
84
+ results
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "alexandria/book_providers/z3950_provider"
8
+
9
+ module Alexandria
10
+ class BookProviders
11
+ class LOCProvider < Z3950Provider
12
+ # http://en.wikipedia.org/wiki/Library_of_Congress
13
+ unabstract
14
+
15
+ include GetText
16
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
17
+
18
+ def initialize
19
+ super("LOC", _("Library of Congress (Usa)"))
20
+ prefs.variable_named("hostname").default_value = "z3950.loc.gov"
21
+ prefs.variable_named("port").default_value = 7090
22
+ prefs.variable_named("database").default_value = "Voyager"
23
+ prefs.variable_named("record_syntax").default_value = "USMARC"
24
+ prefs.variable_named("charset").default_value = "ISO-8859-1"
25
+ prefs.read
26
+ end
27
+
28
+ def url(book)
29
+ isbn = Library.canonicalise_isbn(book.isbn)
30
+ "http://catalog.loc.gov/cgi-bin/Pwebrecon.cgi?" \
31
+ "DB=local&CNT=25+records+per+page&CMD=isbn+#{isbn}"
32
+ rescue StandardError => ex
33
+ log.warn { "Cannot create url for book #{book}; #{ex.message}" }
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "alexandria/book_providers/z3950_provider"
8
+
9
+ module Alexandria
10
+ class BookProviders
11
+ class SBNProvider < Z3950Provider
12
+ # http://sbnonline.sbn.it/
13
+ # http://it.wikipedia.org/wiki/ICCU
14
+ unabstract
15
+
16
+ include GetText
17
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
18
+
19
+ def initialize
20
+ super("SBN", "Servizio Bibliotecario Nazionale (Italy)")
21
+ prefs.variable_named("hostname").default_value = "opac.sbn.it"
22
+ prefs.variable_named("port").default_value = 3950
23
+ prefs.variable_named("database").default_value = "nopac"
24
+ # supported 'USMARC', 'UNIMARC' , 'SUTRS'
25
+ prefs.variable_named("record_syntax").default_value = "USMARC"
26
+ prefs.variable_named("charset").default_value = "ISO-8859-1"
27
+ prefs.read
28
+ end
29
+
30
+ def url(book)
31
+ "http://sbnonline.sbn.it/cgi-bin/zgw/BRIEF.pl?displayquery=" \
32
+ "%253CB%253E%253Cfont%2520color%253D%2523000064%253E" \
33
+ "Codice%2520ISBN%253C%2FB%253E%253C%2Ffont%253E%2520" \
34
+ "contiene%2520%2522%2520%253CFONT%2520COLOR%253Dred%253E" +
35
+ canonicalise_isbn_with_dashes(book.isbn) +
36
+ "%253C%2FFONT%253E%2522&session=&zurl=opac" \
37
+ "&zquery=%281%3D7+4%3D2+2%3D3+5%3D100+6%3D1+3%3D3+%22" +
38
+ canonicalise_isbn_with_dashes(book.isbn) +
39
+ "%22%29&language=it&maxentries=10&target=0&position=1"
40
+ rescue StandardError => ex
41
+ log.warn { "Cannot create url for book #{book}; #{ex.message}" }
42
+ nil
43
+ end
44
+
45
+ private
46
+
47
+ def canonicalise_criterion(criterion, _type)
48
+ canonicalise_isbn_with_dashes(criterion)
49
+ end
50
+
51
+ def request_count(_type)
52
+ 0
53
+ end
54
+
55
+ def canonicalise_isbn_with_dashes(isbn)
56
+ # The reference for the position of the dashes is
57
+ # http://www.isbn-international.org/converter/ranges.htm
58
+
59
+ isbn = Alexandria::Library.canonicalise_isbn(isbn)
60
+
61
+ if isbn[0..1] == "88"
62
+ # Italian speaking area
63
+ if isbn > "8895000" && (isbn <= "8899999996")
64
+ isbn[0..1] + "-" + isbn[2..6] + "-" + isbn[7..8] + "-" + isbn[9..9]
65
+ elsif isbn > "88900000"
66
+ isbn[0..1] + "-" + isbn[2..7] + "-" + isbn[8..8] + "-" + isbn[9..9]
67
+ elsif isbn > "8885000"
68
+ isbn[0..1] + "-" + isbn[2..6] + "-" + isbn[7..8] + "-" + isbn[9..9]
69
+ elsif isbn > "886000"
70
+ isbn[0..1] + "-" + isbn[2..5] + "-" + isbn[6..8] + "-" + isbn[9..9]
71
+ elsif isbn > "88200"
72
+ isbn[0..1] + "-" + isbn[2..4] + "-" + isbn[5..8] + "-" + isbn[9..9]
73
+ elsif isbn > "8800"
74
+ isbn[0..1] + "-" + isbn[2..3] + "-" + isbn[4..8] + "-" + isbn[9..9]
75
+ else
76
+ raise _("Invalid ISBN")
77
+ end
78
+
79
+ else
80
+ isbn
81
+ end
82
+ end
83
+ #
84
+ # Remarks about SBN
85
+ #
86
+ # This provider requires that value of conn.count is 0.
87
+ # It's a Yaz option "Number of records to be retrieved".
88
+ # This provider requires to specify the value of conn.element_set_name = 'F'.
89
+ # It's a Yaz option "Element-Set name of records".
90
+ # See http://www.indexdata.dk/yaz/doc/zoom.resultsets.tkl
91
+ #
92
+ # Dashes:
93
+ # this database requires that Italian books are searched with dashes :(
94
+ # However, they have also books with dashes in wrong positions, for
95
+ # instance 88-061-4934-2
96
+ #
97
+ # References:
98
+ # http://opac.internetculturale.it/cgi-bin/main.cgi?type=field
99
+ # http://www.internetculturale.it/
100
+ # http://sbnonline.sbn.it/zgw/homeit.html
101
+ # http://www.iccu.sbn.it/genera.jsp?id=124
102
+ # with link at http://www.iccu.sbn.it/upload/documenti/cartecsbn.pdf
103
+ # http://www.loc.gov/cgi-bin/zgstart?ACTION=INIT&FORM_HOST_PORT=/prod/www/data/z3950/iccu.html,opac.sbn.it,2100
104
+ # http://gwz.cilea.it/cgi-bin/reportOpac.cgi
105
+ #
106
+ end
107
+ end
108
+ end
@@ -151,7 +151,7 @@ module Alexandria
151
151
  trace = ex.backtrace.join("\n> ")
152
152
  log.warn do
153
153
  "Failed parsing search results for Thalia " \
154
- "#{ex.message} #{trace}"
154
+ "#{ex.message} #{trace}"
155
155
  end
156
156
  raise NoResultsError
157
157
  end
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # See the file README.md for authorship and licensing information.
6
6
 
7
- require "hpricot"
7
+ require "nokogiri"
8
8
  require "htmlentities"
9
9
 
10
10
  module Alexandria
@@ -19,7 +19,7 @@ module Alexandria
19
19
  html.force_encoding source_data_charset
20
20
  utf8_html = html.encode("utf-8")
21
21
  normalized_html = @htmlentities.decode(utf8_html)
22
- Hpricot(normalized_html)
22
+ Nokogiri.parse(normalized_html)
23
23
  end
24
24
 
25
25
  ## from Palatina
@@ -76,9 +76,10 @@ module Alexandria
76
76
  doc = html_to_doc(html, "UTF-8")
77
77
  book_search_results = []
78
78
  begin
79
- result_cells = doc / "td.result/div.name/.."
80
- result_cells.each do |td|
81
- type_icon = (td % "div.type/img.icn")
79
+ result_divs = doc / "td.result/div.name"
80
+ result_divs.each do |div|
81
+ td = div.parent
82
+ type_icon = td % "div.type/img.icn"
82
83
  next unless type_icon && type_icon["src"].include?("icon-bks")
83
84
 
84
85
  name_div = td % "div.name"
@@ -96,7 +97,7 @@ module Alexandria
96
97
  trace = ex.backtrace.join("\n> ")
97
98
  log.warn do
98
99
  "Failed parsing search results for WorldCat " \
99
- "#{ex.message} #{trace}"
100
+ "#{ex.message} #{trace}"
100
101
  end
101
102
  end
102
103
  book_search_results
@@ -170,9 +171,10 @@ module Alexandria
170
171
  # can we do better? get the City name?? or multiple publishers?
171
172
  bibdata = doc % "div#bibdata"
172
173
  bibdata_table = bibdata % :table
173
- publisher_row = bibdata_table % "th[text()*=Publisher]/.."
174
+ publisher_header = bibdata_table % "th[text()*=Publisher]"
174
175
 
175
- if publisher_row
176
+ if publisher_header
177
+ publisher_row = publisher_header.parent
176
178
  publication_info = (publisher_row / "td").last.inner_text
177
179
 
178
180
  publication_info =~ if publication_info.index(";")
@@ -213,7 +215,7 @@ module Alexandria
213
215
  trace = ex.backtrace.join("\n> ")
214
216
  log.warn do
215
217
  "Failed parsing search results for WorldCat " \
216
- "#{ex.message} #{trace}"
218
+ "#{ex.message} #{trace}"
217
219
  end
218
220
  raise NoResultsError
219
221
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "zoom"
8
+ require "alexandria/pseudo_marc_parser"
9
+ require "marc"
10
+
11
+ module Alexandria
12
+ class BookProviders
13
+ class Z3950Provider < AbstractProvider
14
+ include Logging
15
+ include GetText
16
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
17
+
18
+ def initialize(name = "Z3950", fullname = "Z39.50")
19
+ super
20
+ prefs.add("hostname", _("Hostname"), "")
21
+ prefs.add("port", _("Port"), 7090)
22
+ prefs.add("database", _("Database"), "")
23
+ prefs.add("record_syntax", _("Record syntax"), "USMARC",
24
+ ["USMARC", "UNIMARC", "SUTRS"])
25
+ prefs.add("username", _("Username"), "", nil, false)
26
+ prefs.add("password", _("Password"), "", nil, false)
27
+ prefs.add("charset", _("Charset encoding"), "ISO-8859-1")
28
+
29
+ # HACK : piggybacking support
30
+ prefs.add("piggyback", "Piggyback", true, [true, false])
31
+ prefs.read
32
+ end
33
+
34
+ def search(criterion, type)
35
+ prefs.read
36
+ criterion = criterion.encode(prefs["charset"])
37
+
38
+ isbn = type == SEARCH_BY_ISBN ? criterion : nil
39
+ criterion = canonicalise_criterion(criterion, type)
40
+ conn_count = request_count(type)
41
+ resultset = search_records(criterion, type, conn_count)
42
+ log.debug { "total #{resultset.length}" }
43
+
44
+ results = books_from_resultset(resultset, isbn)
45
+ raise NoResultsError if results.empty?
46
+
47
+ type == SEARCH_BY_ISBN ? results.first : results
48
+ end
49
+
50
+ def url(_book)
51
+ nil
52
+ end
53
+
54
+ def books_from_resultset(resultset, isbn)
55
+ case prefs["record_syntax"]
56
+ when /MARC$/
57
+ books_from_marc(resultset, isbn)
58
+ when "SUTRS"
59
+ books_from_sutrs(resultset)
60
+ else
61
+ raise NoResultsError
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def request_count(type)
68
+ type == SEARCH_BY_ISBN ? 1 : 10 # results to retrieve
69
+ end
70
+
71
+ def canonicalise_criterion(criterion, type)
72
+ criterion = Library.canonicalise_isbn(criterion) if type == SEARCH_BY_ISBN
73
+ criterion
74
+ end
75
+
76
+ def books_from_sutrs(_resultset)
77
+ # SUTRS needs to be decoded separately, because each Z39.50 server has a
78
+ # different one.
79
+ raise NoResultsError
80
+ end
81
+
82
+ def marc_to_book(marc_txt, isbn)
83
+ begin
84
+ marc = MARC::Record.new_from_marc(marc_txt, forgiving: true)
85
+ rescue StandardError => ex
86
+ log.error { ex.message }
87
+ log.error { ex.backtrace.join("> \n") }
88
+ begin
89
+ marc = MARC::Record.new(marc_txt)
90
+ rescue StandardError => ex
91
+ log.error { ex.message }
92
+ log.error { ex.backtrace.join("> \n") }
93
+ raise ex
94
+ end
95
+ end
96
+
97
+ log.debug do
98
+ msg = "Parsing MARC"
99
+ msg += "\n title: #{marc.title}"
100
+ msg += "\n authors: #{marc.authors.join(', ')}"
101
+ msg += "\n isbn: #{marc.isbn}, #{isbn}"
102
+ msg += "\n publisher: #{marc.publisher}"
103
+ msg += "\n publish year: #{marc.publish_year}" if marc.respond_to?(:publish_year)
104
+ msg += "\n edition: #{marc.edition}"
105
+ msg
106
+ end
107
+
108
+ return if marc.title.nil? # or marc.authors.empty?
109
+
110
+ isbn ||= marc.isbn
111
+ isbn = Library.canonicalise_ean(isbn)
112
+
113
+ Book.new(marc.title, marc.authors,
114
+ isbn,
115
+ (marc.publisher || ""),
116
+ marc.respond_to?(:publish_year) ? marc.publish_year.to_i : nil,
117
+ (marc.edition || ""))
118
+ end
119
+
120
+ def books_from_marc(resultset, isbn)
121
+ results = []
122
+ resultset[0..9].each do |record|
123
+ marc_txt = record.render(prefs["charset"], "UTF-8")
124
+ log.debug { marc_txt }
125
+ File.binwrite(",marc.txt", marc_txt) if $DEBUG
126
+ book = nil
127
+ begin
128
+ mappings = Alexandria::PseudoMarcParser::USMARC_MAPPINGS
129
+ if prefs["hostname"] == "z3950.bnf.fr"
130
+ mappings = Alexandria::PseudoMarcParser::BNF_FR_MAPPINGS
131
+ end
132
+ # try pseudo-marc parser first (it seems to have more luck)
133
+ book = Alexandria::PseudoMarcParser.marc_text_to_book(marc_txt,
134
+ mappings)
135
+ if book.nil?
136
+ # failing that, try the genuine MARC parser
137
+ book = marc_to_book(marc_txt, isbn)
138
+ end
139
+ rescue StandardError => ex
140
+ log.warn { ex }
141
+ log.warn { ex.backtrace }
142
+ end
143
+
144
+ results << [book] unless book.nil?
145
+ end
146
+ results
147
+ end
148
+
149
+ def marc?
150
+ prefs["record_syntax"].end_with?("MARC")
151
+ end
152
+
153
+ def search_records(criterion, type, conn_count)
154
+ options = {}
155
+ unless prefs["username"].empty? || prefs["password"].empty?
156
+ options["user"] = prefs["username"]
157
+ options["password"] = prefs["password"]
158
+ end
159
+ hostname = prefs["hostname"]
160
+ port = prefs["port"].to_i
161
+ log.debug { "hostname #{hostname} port #{port} options #{options}" }
162
+ conn = ZOOM::Connection.new(options).connect(hostname, port)
163
+ conn.database_name = prefs["database"]
164
+
165
+ conn.preferred_record_syntax = prefs["record_syntax"]
166
+ conn.element_set_name = "F"
167
+ conn.count = conn_count
168
+ attr = case type
169
+ when SEARCH_BY_ISBN then [7]
170
+ when SEARCH_BY_TITLE then [4]
171
+ when SEARCH_BY_AUTHORS then [1, 1003]
172
+ when SEARCH_BY_KEYWORD then [1016]
173
+ end
174
+ pqf = ""
175
+ attr.each { |att| pqf += "@attr 1=#{att} " }
176
+ pqf += '"' + criterion.upcase + '"'
177
+ log.debug { "pqf is #{pqf}, syntax #{prefs['record_syntax']}" }
178
+
179
+ begin
180
+ if prefs.variable_named("piggyback") && !prefs["piggyback"]
181
+ log.debug { "setting conn.piggyback to false" }
182
+ conn.piggyback = false
183
+ end
184
+ conn.search(pqf)
185
+ rescue StandardError => ex
186
+ if /1005/.match?(ex.message) &&
187
+ prefs.variable_named("piggyback") && prefs["piggyback"]
188
+ log.error { "Z39.50 search failed:: #{ex.message}" }
189
+ log.info { "Turning off piggybacking for this provider" }
190
+ prefs.variable_named("piggyback").new_value = false
191
+ retry
192
+ end
193
+
194
+ raise ex
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end