marc 1.0.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  3. data/.github/workflows/ruby.yml +24 -0
  4. data/.gitignore +17 -0
  5. data/.standard.yml +1 -0
  6. data/{Changes → CHANGELOG.md} +106 -29
  7. data/Gemfile +15 -0
  8. data/README.md +240 -47
  9. data/Rakefile +14 -14
  10. data/bin/marc +14 -0
  11. data/bin/marc2xml +17 -0
  12. data/examples/xml2marc.rb +10 -0
  13. data/lib/marc/constants.rb +3 -3
  14. data/lib/marc/controlfield.rb +35 -23
  15. data/lib/marc/datafield.rb +70 -63
  16. data/lib/marc/dublincore.rb +59 -41
  17. data/lib/marc/exception.rb +9 -1
  18. data/lib/marc/jsonl_reader.rb +33 -0
  19. data/lib/marc/jsonl_writer.rb +44 -0
  20. data/lib/marc/marc8/map_to_unicode.rb +16417 -16420
  21. data/lib/marc/marc8/to_unicode.rb +80 -86
  22. data/lib/marc/reader.rb +119 -121
  23. data/lib/marc/record.rb +72 -62
  24. data/lib/marc/subfield.rb +12 -10
  25. data/lib/marc/unsafe_xmlwriter.rb +93 -0
  26. data/lib/marc/version.rb +1 -1
  27. data/lib/marc/writer.rb +27 -30
  28. data/lib/marc/xml_parsers.rb +222 -197
  29. data/lib/marc/xmlreader.rb +131 -114
  30. data/lib/marc/xmlwriter.rb +93 -81
  31. data/lib/marc.rb +20 -18
  32. data/marc.gemspec +23 -0
  33. data/test/marc8/tc_marc8_mapping.rb +3 -3
  34. data/test/marc8/tc_to_unicode.rb +28 -32
  35. data/test/messed_up_leader.xml +9 -0
  36. data/test/tc_controlfield.rb +37 -34
  37. data/test/tc_datafield.rb +65 -60
  38. data/test/tc_dublincore.rb +9 -11
  39. data/test/tc_hash.rb +10 -13
  40. data/test/tc_jsonl.rb +19 -0
  41. data/test/tc_marchash.rb +17 -21
  42. data/test/tc_parsers.rb +108 -144
  43. data/test/tc_reader.rb +35 -36
  44. data/test/tc_reader_char_encodings.rb +149 -169
  45. data/test/tc_record.rb +143 -148
  46. data/test/tc_subfield.rb +14 -13
  47. data/test/tc_unsafe_xml.rb +95 -0
  48. data/test/tc_writer.rb +101 -108
  49. data/test/tc_xml.rb +99 -87
  50. data/test/tc_xml_error_handling.rb +7 -8
  51. data/test/ts_marc.rb +8 -8
  52. metadata +94 -9
@@ -1,16 +1,15 @@
1
- require File.dirname(__FILE__) + '/xml_parsers'
1
+ require File.dirname(__FILE__) + "/xml_parsers"
2
2
  module MARC
3
-
4
3
  # the constructor which you can pass either a filename:
5
4
  #
6
5
  # reader = MARC::XMLReader.new('/Users/edsu/marc.xml')
7
6
  #
8
- # or a File object,
7
+ # or a File object,
9
8
  #
10
9
  # reader = Marc::XMLReader.new(File.new('/Users/edsu/marc.xml'))
11
10
  #
12
11
  # or really any object that responds to read(n)
13
- #
12
+ #
14
13
  # reader = MARC::XMLReader.new(StringIO.new(xml))
15
14
  #
16
15
  # By default, XMLReader uses REXML's pull parser, but you can swap
@@ -18,7 +17,7 @@ module MARC
18
17
  # 'best' one). The :parser can either be one of the defined constants
19
18
  # or the constant's value.
20
19
  #
21
- # reader = MARC::XMLReader.new(fh, :parser=>'magic')
20
+ # reader = MARC::XMLReader.new(fh, :parser=>'magic')
22
21
  #
23
22
  # It is also possible to set the default parser at the class level so
24
23
  # all subsequent instances will use it instead:
@@ -28,151 +27,169 @@ module MARC
28
27
  #
29
28
  # Use:
30
29
  # MARC::XMLReader.best_available!
31
- #
30
+ #
32
31
  # or
33
32
  # MARC::XMLReader.nokogiri!
34
- #
33
+ #
34
+ # By default, all XML parsers except REXML require the MARC namespace
35
+ # (http://www.loc.gov/MARC21/slim) to be included. Adding the option
36
+ # `ignore_namespace` to the call to `new` with a true value
37
+ # will allow parsing to proceed, e.g.,
38
+ #
39
+ # reader = MARC::XMLReader.new(filename, parser: :nokogiri, ignore_namespace: true)
40
+ #
41
+ # You can also pass in an error_handler option that will be called if
42
+ # there are any validation errors found when parsing a record.
43
+ #
44
+ # reader = MARC::XMLReader.new(fh, error_handler: ->(reader, record, block) { ... })
45
+ #
46
+ # By default, a MARC::RecordException is raised halting all future parsing.
35
47
  class XMLReader
36
48
  include Enumerable
37
- USE_BEST_AVAILABLE = 'magic'
38
- USE_REXML = 'rexml'
39
- USE_NOKOGIRI = 'nokogiri'
40
- USE_JREXML = 'jrexml'
41
- USE_JSTAX = 'jstax'
42
- USE_LIBXML = 'libxml'
49
+ USE_BEST_AVAILABLE = "magic"
50
+ USE_REXML = "rexml"
51
+ USE_NOKOGIRI = "nokogiri"
52
+ USE_JREXML = "jrexml"
53
+ USE_JSTAX = "jstax"
54
+ USE_LIBXML = "libxml"
43
55
  @@parser = USE_REXML
44
- attr_reader :parser
45
-
56
+ attr_reader :parser, :error_handler
57
+
46
58
  def initialize(file, options = {})
47
59
  if file.is_a?(String)
48
60
  handle = File.new(file)
49
- elsif file.respond_to?("read", 5)
61
+ elsif file.respond_to?(:read, 5)
50
62
  handle = file
51
63
  else
52
64
  raise ArgumentError, "must pass in path or File"
53
65
  end
54
66
  @handle = handle
55
67
 
56
- if options[:parser]
57
- parser = self.class.choose_parser(options[:parser].to_s)
68
+ if options[:ignore_namespace]
69
+ @ignore_namespace = options[:ignore_namespace]
70
+ end
71
+
72
+ parser = if options[:parser]
73
+ self.class.choose_parser(options[:parser].to_s)
58
74
  else
59
- parser = @@parser
75
+ @@parser
60
76
  end
77
+
61
78
  case parser
62
- when 'magic' then extend MagicReader
63
- when 'rexml' then extend REXMLReader
64
- when 'jrexml' then
79
+ when "magic" then extend MagicReader
80
+ when "rexml" then extend REXMLReader
81
+ when "jrexml"
65
82
  raise ArgumentError, "jrexml only available under jruby" unless defined? JRUBY_VERSION
66
83
  extend JREXMLReader
67
- when 'nokogiri' then extend NokogiriReader
68
- when 'jstax' then
84
+ when "nokogiri" then extend NokogiriReader
85
+ when "jstax"
69
86
  raise ArgumentError, "jstax only available under jruby" unless defined? JRUBY_VERSION
70
87
  extend JRubySTAXReader
71
- when 'libxml' then extend LibXMLReader
72
- raise ArgumentError, "libxml not available under jruby" if defined? JRUBY_VERSION
88
+ when "libxml" then extend LibXMLReader
89
+ raise ArgumentError, "libxml not available under jruby" if defined? JRUBY_VERSION
73
90
  end
74
- end
75
91
 
76
- # Returns the currently set parser type
77
- def self.parser
78
- return @@parser
79
- end
80
-
81
- # Returns an array of all the parsers available
82
- def self.parsers
83
- p = []
84
- self.constants.each do | const |
85
- next unless const.match("^USE_")
86
- p << const
87
- end
88
- return p
89
- end
90
-
91
- # Sets the class parser
92
- def self.parser=(p)
93
- @@parser = choose_parser(p)
92
+ @error_handler = options[:error_handler]
94
93
  end
95
94
 
96
- # Returns the value of the best available parser
97
- def self.best_available
98
- parser = nil
99
- jruby = [USE_NOKOGIRI, USE_JSTAX, USE_JREXML]
100
- ruby = [USE_NOKOGIRI, USE_LIBXML]
101
- if defined? JRUBY_VERSION
102
- unless parser
103
- begin
104
- require 'nokogiri'
105
- parser = USE_NOKOGIRI
106
- rescue LoadError
107
- end
95
+ class << self
96
+ # Returns the currently set parser type
97
+ def parser
98
+ @@parser
99
+ end
100
+
101
+ # Returns an array of all the parsers available
102
+ def parsers
103
+ p = []
104
+ constants.each do |const|
105
+ next unless const.match?("^USE_")
106
+ p << const
108
107
  end
109
- unless parser
110
- begin
111
- # try to find the class, so we throw an error if not found
112
- java.lang.Class.forName("javax.xml.stream.XMLInputFactory")
113
- parser = USE_JSTAX
114
- rescue java.lang.ClassNotFoundException
108
+ p
109
+ end
110
+
111
+ # Sets the class parser
112
+ def parser=(p)
113
+ @@parser = choose_parser(p)
114
+ end
115
+
116
+ # Returns the value of the best available parser
117
+ def best_available
118
+ parser = nil
119
+ if defined? JRUBY_VERSION
120
+ unless parser
121
+ begin
122
+ require "nokogiri"
123
+ parser = USE_NOKOGIRI
124
+ rescue LoadError
125
+ end
115
126
  end
116
- end
117
- unless parser
118
- begin
119
- require 'jrexml'
120
- parser = USE_JREXML
121
- rescue LoadError
127
+ unless parser
128
+ begin
129
+ # try to find the class, so we throw an error if not found
130
+ java.lang.Class.forName("javax.xml.stream.XMLInputFactory")
131
+ parser = USE_JSTAX
132
+ rescue java.lang.ClassNotFoundException
133
+ end
122
134
  end
123
- end
124
- else
125
- begin
126
- require 'nokogiri'
127
- parser = USE_NOKOGIRI
128
- rescue LoadError
129
- end
130
- unless defined? JRUBY_VERSION
131
135
  unless parser
132
136
  begin
133
- require 'xml'
134
- parser = USE_LIBXML
137
+ require "jrexml"
138
+ parser = USE_JREXML
135
139
  rescue LoadError
136
140
  end
137
- end
141
+ end
142
+ else
143
+ begin
144
+ require "nokogiri"
145
+ parser = USE_NOKOGIRI
146
+ rescue LoadError
147
+ end
148
+ unless defined? JRUBY_VERSION
149
+ unless parser
150
+ begin
151
+ require "xml"
152
+ parser = USE_LIBXML
153
+ rescue LoadError
154
+ end
155
+ end
156
+ end
138
157
  end
158
+ parser ||= USE_REXML
159
+ parser
139
160
  end
140
- parser = USE_REXML unless parser
141
- parser
142
- end
143
-
144
- # Sets the best available parser as the default
145
- def self.best_available!
146
- @@parser = self.best_available
147
- end
148
-
149
- # Sets Nokogiri as the default parser
150
- def self.nokogiri!
151
- @@parser = USE_NOKOGIRI
152
- end
153
-
154
- # Sets jrexml as the default parser
155
- def self.jrexml!
156
- @@parser = USE_JREXML
157
- end
158
-
159
- # Sets REXML as the default parser
160
- def self.rexml!
161
- @@parser = USE_REXML
162
- end
163
-
164
- protected
165
-
166
- def self.choose_parser(p)
167
- match = false
168
- self.constants.each do | const |
169
- next unless const.to_s.match("^USE_")
170
- if self.const_get(const) == p
171
- match = true
172
- return p
161
+
162
+ # Sets the best available parser as the default
163
+ def best_available!
164
+ @@parser = best_available
165
+ end
166
+
167
+ # Sets Nokogiri as the default parser
168
+ def nokogiri!
169
+ @@parser = USE_NOKOGIRI
170
+ end
171
+
172
+ # Sets jrexml as the default parser
173
+ def jrexml!
174
+ @@parser = USE_JREXML
175
+ end
176
+
177
+ # Sets REXML as the default parser
178
+ def rexml!
179
+ @@parser = USE_REXML
180
+ end
181
+
182
+ def choose_parser(p)
183
+ match = false
184
+ constants.each do |const|
185
+ next unless const.to_s.match?("^USE_")
186
+ if const_get(const) == p
187
+ match = true
188
+ return p
189
+ end
173
190
  end
191
+ raise ArgumentError.new("Parser '#{p}' not defined") unless match
174
192
  end
175
- raise ArgumentError.new("Parser '#{p}' not defined") unless match
176
193
  end
177
194
  end
178
195
  end
@@ -1,155 +1,167 @@
1
- require 'rexml/document'
2
- require 'rexml/text'
3
- require 'rexml/formatters/default'
1
+ require "rexml/document"
2
+ require "rexml/text"
3
+ require "rexml/formatters/default"
4
4
 
5
5
  module MARC
6
-
7
6
  # A class for writing MARC records as MARCXML.
8
7
  # BIG CAVEAT! XMLWriter will *not* convert your MARC8 to UTF8
9
8
  # bug the authors to do this if you need it
10
-
9
+
11
10
  class XMLWriter
12
-
13
11
  # the constructor which you must pass a file path
14
12
  # or an object that responds to a write message
15
13
  # the second argument is a hash of options, currently
16
14
  # only supporting one option, stylesheet
17
- #
15
+ #
18
16
  # writer = XMLWriter.new 'marc.xml', :stylesheet => 'style.xsl'
19
17
  # writer.write record
20
-
21
- def initialize(file, opts={})
18
+ #
19
+
20
+ COLLECTION_TAG = %(<collection xmlns='#{MARC_NS}'
21
+ xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
22
+ xsi:schemaLocation="#{MARC_NS} #{MARC_XSD}">).freeze
23
+
24
+ def initialize(file, opts = {}, &blk)
22
25
  @writer = REXML::Formatters::Default.new
23
- if file.class == String
24
- @fh = File.new(file,"w")
25
- elsif file.respond_to?('write')
26
+ if file.instance_of?(String)
27
+ @fh = File.new(file, "w")
28
+ elsif file.respond_to?(:write)
26
29
  @fh = file
27
30
  else
28
31
  raise ArgumentError, "must pass in file name or handle"
29
32
  end
30
-
33
+
34
+ @stylesheet = opts[:stylesheet]
35
+
31
36
  @fh.write("<?xml version='1.0'?>\n")
32
- if opts[:stylesheet]
33
- @fh.write(
34
- %Q{<?xml-stylesheet type="text/xsl" href="#{opts[:stylesheet]}"?>\n})
35
- end
36
- @fh.write("<collection xmlns='" + MARC_NS + "' " +
37
- "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
38
- "xsi:schemaLocation='" + MARC_NS + " " + MARC_XSD + "'>")
37
+ @fh.write(stylesheet_tag)
38
+ @fh.write(COLLECTION_TAG)
39
39
  @fh.write("\n")
40
+
41
+ if block_given?
42
+ blk.call(self)
43
+ self.close
44
+ end
45
+ end
46
+
47
+ def stylesheet_tag
48
+ if @stylesheet
49
+ %(<?xml-stylesheet type="text/xsl" href="#{@stylesheet}"?>\n)
50
+ else
51
+ ""
52
+ end
40
53
  end
41
-
42
-
54
+
43
55
  # write a record to the file or handle
44
-
56
+
45
57
  def write(record)
46
58
  @writer.write(MARC::XMLWriter.encode(record), @fh)
47
59
  @fh.write("\n")
48
60
  end
49
-
50
-
61
+
51
62
  # close underlying filehandle
52
-
63
+
53
64
  def close
54
65
  @fh.write("</collection>")
55
66
  @fh.close
56
67
  end
57
68
 
58
-
69
+ def self.fix_leader(leader)
70
+ fixed_leader = leader.gsub(/[^\w|^\s]/, "Z")
71
+
72
+ # The leader must have at least 24 characters
73
+ fixed_leader = fixed_leader.ljust(24) if fixed_leader.length < 24
74
+
75
+ # MARCXML is particular about last four characters; ILSes aren't
76
+ if fixed_leader[20..23] != "4500"
77
+ fixed_leader[20..23] = "4500"
78
+ end
79
+
80
+ # MARCXML doesn't like a space here so we need a filler character: Z
81
+ if fixed_leader[6..6] == " "
82
+ fixed_leader[6..6] = "Z"
83
+ end
84
+
85
+ fixed_leader
86
+ end
87
+
59
88
  # a static method that accepts a MARC::Record object
60
89
  # and returns a REXML::Document for the XML serialization.
90
+ def self.encode(record, opts = {})
91
+ single_char = Regexp.new('[\da-z ]{1}')
92
+ subfield_char = Regexp.new('[\dA-Za-z!"#$%&\'()*+,-./:;<=>?{}_^`~\[\]\\\]{1}')
93
+ control_field_tag = Regexp.new("00[1-9A-Za-z]{1}")
61
94
 
62
- def self.encode(record, opts={})
63
- singleChar = Regexp.new('[\da-z ]{1}')
64
- ctrlFieldTag = Regexp.new('00[1-9A-Za-z]{1}')
65
-
66
95
  # Right now, this writer handles input from the strict and
67
96
  # lenient MARC readers. Because it can get 'loose' MARC in, it
68
97
  # attempts to do some cleanup on data values that are not valid
69
98
  # MARCXML.
70
-
99
+
71
100
  # TODO? Perhaps the 'loose MARC' checks should be split out
72
101
  # into a tolerant MARCXMLWriter allowing the main one to skip
73
102
  # this extra work.
74
-
103
+
75
104
  # TODO: At the very least there should be some logging
76
105
  # to record our attempts to account for less than perfect MARC.
77
-
78
- e = REXML::Element.new('record')
106
+
107
+ e = REXML::Element.new("record")
79
108
  e.add_namespace(MARC_NS) if opts[:include_namespace]
80
109
 
81
- # MARCXML only allows alphanumerics or spaces in the leader
82
- record.leader.gsub!(/[^\w|^\s]/, 'Z')
83
-
84
- # MARCXML is particular about last four characters; ILSes aren't
85
- if (record.leader[20..23] != "4500")
86
- record.leader[20..23] = "4500"
87
- end
110
+ leader_element = REXML::Element.new("leader")
111
+ leader_element.add_text(fix_leader(record.leader))
112
+ e.add_element(leader_element)
88
113
 
89
- # MARCXML doesn't like a space here so we need a filler character: Z
90
- if (record.leader[6..6] == " ")
91
- record.leader[6..6] = "Z"
92
- end
93
-
94
- leader = REXML::Element.new("leader")
95
- leader.add_text(record.leader)
96
- e.add_element(leader)
97
-
98
114
  record.each do |field|
99
- if field.class == MARC::DataField
115
+ if field.instance_of?(MARC::DataField)
100
116
  datafield_elem = REXML::Element.new("datafield")
101
-
117
+
118
+ ind1 = field.indicator1
102
119
  # If marc is leniently parsed, we may have some dirty data; using
103
120
  # the 'z' ind1 value should help us locate these later to fix
104
- if field.indicator1.nil? || (field.indicator1.match(singleChar) == nil)
105
- field.indicator1 = 'z'
106
- end
107
-
121
+ ind1 = "z" if ind1.nil? || !ind1.match?(single_char)
122
+ ind2 = field.indicator2
108
123
  # If marc is leniently parsed, we may have some dirty data; using
109
124
  # the 'z' ind2 value should help us locate these later to fix
110
- if field.indicator2.nil? || (field.indicator2.match(singleChar) == nil)
111
- field.indicator2 = 'z'
112
- end
113
-
125
+
126
+ ind2 = "z" if field.indicator2.nil? || !ind2.match?(single_char)
127
+
114
128
  datafield_elem.add_attributes({
115
- "tag"=>field.tag,
116
- "ind1"=>field.indicator1,
117
- "ind2"=>field.indicator2
129
+ "tag" => field.tag,
130
+ "ind1" => ind1,
131
+ "ind2" => ind2
118
132
  })
119
133
 
120
- for subfield in field.subfields
134
+ field.subfields.each do |subfield|
121
135
  subfield_element = REXML::Element.new("subfield")
122
-
136
+
137
+ code = subfield.code
123
138
  # If marc is leniently parsed, we may have some dirty data; using
124
139
  # the blank subfield code should help us locate these later to fix
125
- if (subfield.code.match(singleChar) == nil)
126
- subfield.code = ' '
127
- end
128
-
129
- subfield_element.add_attribute("code", subfield.code)
140
+ code = " " if subfield.code.match(subfield_char).nil?
141
+
142
+ subfield_element.add_attribute("code", code)
130
143
  text = subfield.value
131
144
  subfield_element.add_text(text)
132
145
  datafield_elem.add_element(subfield_element)
133
146
  end
134
-
147
+
135
148
  e.add_element datafield_elem
136
- elsif field.class == MARC::ControlField
149
+ elsif field.instance_of?(MARC::ControlField)
137
150
  control_element = REXML::Element.new("controlfield")
138
-
151
+
152
+ tag = field.tag
139
153
  # We need a marker for invalid tag values (we use 000)
140
- unless field.tag.match(ctrlFieldTag) or MARC::ControlField.control_tag?(ctrlFieldTag)
141
- field.tag = "00z"
142
- end
143
-
144
- control_element.add_attribute("tag", field.tag)
154
+ tag = "00z" unless tag.match(control_field_tag) || MARC::ControlField.control_tag?(tag)
155
+
156
+ control_element.add_attribute("tag", tag)
145
157
  text = field.value
146
158
  control_element.add_text(text)
147
159
  e.add_element(control_element)
148
160
  end
149
161
  end
150
-
162
+
151
163
  # return xml
152
- return e
164
+ e
153
165
  end
154
166
  end
155
167
  end
data/lib/marc.rb CHANGED
@@ -1,7 +1,7 @@
1
- #marc is a ruby library for reading and writing MAchine Readable Cataloging
2
- #(MARC). More information about MARC can be found at <http://www.loc.gov/marc>.
1
+ # marc is a ruby library for reading and writing MAchine Readable Cataloging
2
+ # (MARC). More information about MARC can be found at <http://www.loc.gov/marc>.
3
3
  #
4
- #USAGE
4
+ # USAGE
5
5
  #
6
6
  # require 'marc'
7
7
  #
@@ -11,7 +11,7 @@
11
11
  # puts record['245']['a']
12
12
  # end
13
13
  #
14
- # # creating a record
14
+ # # creating a record
15
15
  # record = MARC::Record.new()
16
16
  # record.add_field(MARC::DataField.new('100', '0', ' ', ['a', 'John Doe']))
17
17
  #
@@ -30,17 +30,19 @@
30
30
  # record = MARC::Record.new()
31
31
  # record.add_field(MARC::ControlField.new('FMT', 'Book')) # doesn't raise an error
32
32
 
33
-
34
- require File.dirname(__FILE__) + '/marc/version'
35
- require File.dirname(__FILE__) + '/marc/constants'
36
- require File.dirname(__FILE__) + '/marc/record'
37
- require File.dirname(__FILE__) + '/marc/datafield'
38
- require File.dirname(__FILE__) + '/marc/controlfield'
39
- require File.dirname(__FILE__) + '/marc/subfield'
40
- require File.dirname(__FILE__) + '/marc/reader'
41
- require File.dirname(__FILE__) + '/marc/writer'
42
- require File.dirname(__FILE__) + '/marc/exception'
43
- require File.dirname(__FILE__) + '/marc/xmlwriter'
44
- require File.dirname(__FILE__) + '/marc/xmlreader'
45
- require File.dirname(__FILE__) + '/marc/dublincore'
46
- require File.dirname(__FILE__) + '/marc/xml_parsers'
33
+ require_relative "marc/version"
34
+ require_relative "marc/constants"
35
+ require_relative "marc/record"
36
+ require_relative "marc/datafield"
37
+ require_relative "marc/controlfield"
38
+ require_relative "marc/subfield"
39
+ require_relative "marc/reader"
40
+ require_relative "marc/writer"
41
+ require_relative "marc/exception"
42
+ require_relative "marc/xmlwriter"
43
+ require_relative "marc/unsafe_xmlwriter"
44
+ require_relative "marc/xmlreader"
45
+ require_relative "marc/dublincore"
46
+ require_relative "marc/xml_parsers"
47
+ require_relative "marc/jsonl_reader"
48
+ require_relative "marc/jsonl_writer"
data/marc.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), "lib/marc/version")
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "marc"
5
+ s.version = MARC::VERSION
6
+ s.author = "Ed Summers"
7
+ s.email = "ehs@pobox.com"
8
+ s.homepage = "https://github.com/ruby-marc/ruby-marc/"
9
+ s.summary = "A ruby library for working with Machine Readable Cataloging"
10
+ s.license = "MIT"
11
+ s.required_ruby_version = ">= 1.8.6"
12
+ s.authors = ["Kevin Clarke", "Bill Dueber", "William Groppe", "Jonathan Rochkind", "Ross Singer", "Ed Summers", "Chris Beer"]
13
+
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "standard", "~>1.0"
20
+ s.add_dependency "scrub_rb", ">= 1.0.1", "< 2" # backport for ruby 2.1 String#scrub
21
+ s.add_dependency "unf" # unicode normalization
22
+ s.add_dependency "rexml" # rexml was unbundled from the stdlib in ruby 3
23
+ end
@@ -1,6 +1,6 @@
1
- require 'test/unit'
2
- require 'marc'
3
- require 'marc/marc8/map_to_unicode'
1
+ require "test/unit"
2
+ require "marc"
3
+ require "marc/marc8/map_to_unicode"
4
4
 
5
5
  class TestMarc8Mapping < Test::Unit::TestCase
6
6
  def test_codesets_just_exist