marc 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
- data/.github/workflows/ruby.yml +24 -0
- data/.gitignore +17 -0
- data/.standard.yml +1 -0
- data/{Changes → CHANGELOG.md} +102 -30
- data/Gemfile +15 -0
- data/README.md +239 -46
- data/Rakefile +14 -14
- data/bin/marc +14 -0
- data/bin/marc2xml +17 -0
- data/examples/xml2marc.rb +10 -0
- data/lib/marc/constants.rb +3 -3
- data/lib/marc/controlfield.rb +35 -23
- data/lib/marc/datafield.rb +70 -63
- data/lib/marc/dublincore.rb +59 -41
- data/lib/marc/exception.rb +9 -1
- data/lib/marc/jsonl_reader.rb +33 -0
- data/lib/marc/jsonl_writer.rb +44 -0
- data/lib/marc/marc8/map_to_unicode.rb +16417 -16420
- data/lib/marc/marc8/to_unicode.rb +80 -86
- data/lib/marc/reader.rb +117 -123
- data/lib/marc/record.rb +72 -62
- data/lib/marc/subfield.rb +12 -10
- data/lib/marc/unsafe_xmlwriter.rb +93 -0
- data/lib/marc/version.rb +1 -1
- data/lib/marc/writer.rb +27 -30
- data/lib/marc/xml_parsers.rb +222 -197
- data/lib/marc/xmlreader.rb +131 -114
- data/lib/marc/xmlwriter.rb +93 -82
- data/lib/marc.rb +20 -18
- data/marc.gemspec +23 -0
- data/test/marc8/tc_marc8_mapping.rb +3 -3
- data/test/marc8/tc_to_unicode.rb +28 -32
- data/test/messed_up_leader.xml +9 -0
- data/test/tc_controlfield.rb +37 -34
- data/test/tc_datafield.rb +65 -60
- data/test/tc_dublincore.rb +9 -11
- data/test/tc_hash.rb +10 -13
- data/test/tc_jsonl.rb +19 -0
- data/test/tc_marchash.rb +17 -21
- data/test/tc_parsers.rb +108 -144
- data/test/tc_reader.rb +35 -36
- data/test/tc_reader_char_encodings.rb +149 -169
- data/test/tc_record.rb +143 -148
- data/test/tc_subfield.rb +14 -13
- data/test/tc_unsafe_xml.rb +95 -0
- data/test/tc_writer.rb +101 -108
- data/test/tc_xml.rb +101 -94
- data/test/tc_xml_error_handling.rb +7 -8
- data/test/ts_marc.rb +8 -8
- metadata +80 -9
data/lib/marc/xmlreader.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
require File.dirname(__FILE__) +
|
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 =
|
38
|
-
USE_REXML =
|
39
|
-
USE_NOKOGIRI =
|
40
|
-
USE_JREXML =
|
41
|
-
USE_JSTAX =
|
42
|
-
USE_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?(
|
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[:
|
57
|
-
|
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
|
-
|
75
|
+
@@parser
|
60
76
|
end
|
77
|
+
|
61
78
|
case parser
|
62
|
-
when
|
63
|
-
when
|
64
|
-
when
|
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
|
68
|
-
when
|
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
|
72
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
98
|
-
parser
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
134
|
-
parser =
|
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
|
-
|
141
|
-
parser
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
data/lib/marc/xmlwriter.rb
CHANGED
@@ -1,156 +1,167 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
-
|
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.
|
24
|
-
@fh = File.new(file,"w")
|
25
|
-
elsif file.respond_to?(
|
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
|
-
|
33
|
-
|
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
|
-
subfieldChar = Regexp.new('[\dA-Za-z!"#$%&\'()*+,-./:;<=>?{}_^`~\[\]\\\]{1}')
|
65
|
-
ctrlFieldTag = Regexp.new('00[1-9A-Za-z]{1}')
|
66
|
-
|
67
95
|
# Right now, this writer handles input from the strict and
|
68
96
|
# lenient MARC readers. Because it can get 'loose' MARC in, it
|
69
97
|
# attempts to do some cleanup on data values that are not valid
|
70
98
|
# MARCXML.
|
71
|
-
|
99
|
+
|
72
100
|
# TODO? Perhaps the 'loose MARC' checks should be split out
|
73
101
|
# into a tolerant MARCXMLWriter allowing the main one to skip
|
74
102
|
# this extra work.
|
75
|
-
|
103
|
+
|
76
104
|
# TODO: At the very least there should be some logging
|
77
105
|
# to record our attempts to account for less than perfect MARC.
|
78
|
-
|
79
|
-
e = REXML::Element.new(
|
106
|
+
|
107
|
+
e = REXML::Element.new("record")
|
80
108
|
e.add_namespace(MARC_NS) if opts[:include_namespace]
|
81
109
|
|
82
|
-
|
83
|
-
record.leader
|
84
|
-
|
85
|
-
# MARCXML is particular about last four characters; ILSes aren't
|
86
|
-
if (record.leader[20..23] != "4500")
|
87
|
-
record.leader[20..23] = "4500"
|
88
|
-
end
|
110
|
+
leader_element = REXML::Element.new("leader")
|
111
|
+
leader_element.add_text(fix_leader(record.leader))
|
112
|
+
e.add_element(leader_element)
|
89
113
|
|
90
|
-
# MARCXML doesn't like a space here so we need a filler character: Z
|
91
|
-
if (record.leader[6..6] == " ")
|
92
|
-
record.leader[6..6] = "Z"
|
93
|
-
end
|
94
|
-
|
95
|
-
leader = REXML::Element.new("leader")
|
96
|
-
leader.add_text(record.leader)
|
97
|
-
e.add_element(leader)
|
98
|
-
|
99
114
|
record.each do |field|
|
100
|
-
if field.
|
115
|
+
if field.instance_of?(MARC::DataField)
|
101
116
|
datafield_elem = REXML::Element.new("datafield")
|
102
|
-
|
117
|
+
|
118
|
+
ind1 = field.indicator1
|
103
119
|
# If marc is leniently parsed, we may have some dirty data; using
|
104
120
|
# the 'z' ind1 value should help us locate these later to fix
|
105
|
-
if
|
106
|
-
|
107
|
-
end
|
108
|
-
|
121
|
+
ind1 = "z" if ind1.nil? || !ind1.match?(single_char)
|
122
|
+
ind2 = field.indicator2
|
109
123
|
# If marc is leniently parsed, we may have some dirty data; using
|
110
124
|
# the 'z' ind2 value should help us locate these later to fix
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
125
|
+
|
126
|
+
ind2 = "z" if field.indicator2.nil? || !ind2.match?(single_char)
|
127
|
+
|
115
128
|
datafield_elem.add_attributes({
|
116
|
-
"tag"=>field.tag,
|
117
|
-
"ind1"=>
|
118
|
-
"ind2"=>
|
129
|
+
"tag" => field.tag,
|
130
|
+
"ind1" => ind1,
|
131
|
+
"ind2" => ind2
|
119
132
|
})
|
120
133
|
|
121
|
-
|
134
|
+
field.subfields.each do |subfield|
|
122
135
|
subfield_element = REXML::Element.new("subfield")
|
123
|
-
|
136
|
+
|
137
|
+
code = subfield.code
|
124
138
|
# If marc is leniently parsed, we may have some dirty data; using
|
125
139
|
# the blank subfield code should help us locate these later to fix
|
126
|
-
if
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
subfield_element.add_attribute("code", subfield.code)
|
140
|
+
code = " " if subfield.code.match(subfield_char).nil?
|
141
|
+
|
142
|
+
subfield_element.add_attribute("code", code)
|
131
143
|
text = subfield.value
|
132
144
|
subfield_element.add_text(text)
|
133
145
|
datafield_elem.add_element(subfield_element)
|
134
146
|
end
|
135
|
-
|
147
|
+
|
136
148
|
e.add_element datafield_elem
|
137
|
-
elsif field.
|
149
|
+
elsif field.instance_of?(MARC::ControlField)
|
138
150
|
control_element = REXML::Element.new("controlfield")
|
139
|
-
|
151
|
+
|
152
|
+
tag = field.tag
|
140
153
|
# We need a marker for invalid tag values (we use 000)
|
141
|
-
unless
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
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)
|
146
157
|
text = field.value
|
147
158
|
control_element.add_text(text)
|
148
159
|
e.add_element(control_element)
|
149
160
|
end
|
150
161
|
end
|
151
|
-
|
162
|
+
|
152
163
|
# return xml
|
153
|
-
|
164
|
+
e
|
154
165
|
end
|
155
166
|
end
|
156
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|