berkeley_library-tind 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.idea/inspectionProfiles/Project_Default.xml +18 -0
- data/.idea/tind.iml +91 -87
- data/.ruby-version +1 -1
- data/CHANGES.md +28 -1
- data/README.md +15 -1
- data/berkeley_library-tind.gemspec +2 -1
- data/lib/berkeley_library/tind/marc/xml_builder.rb +70 -0
- data/lib/berkeley_library/tind/marc/xml_reader.rb +32 -19
- data/lib/berkeley_library/tind/marc/xml_writer.rb +152 -0
- data/lib/berkeley_library/tind/module_info.rb +1 -1
- data/lib/berkeley_library/util/files.rb +39 -0
- data/spec/berkeley_library/tind/export/table_spec.rb +2 -0
- data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +42 -1
- data/spec/berkeley_library/tind/marc/xml_writer_spec.rb +194 -0
- data/spec/data/issue-4.xml +157 -0
- data/spec/data/new-records.xml +46 -0
- metadata +34 -5
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'marc_extensions'
|
3
|
+
require 'berkeley_library/tind/marc/xml_builder'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module TIND
|
7
|
+
module MARC
|
8
|
+
class XMLWriter
|
9
|
+
include BerkeleyLibrary::Util::Files
|
10
|
+
include BerkeleyLibrary::Logging
|
11
|
+
|
12
|
+
# ------------------------------------------------------------
|
13
|
+
# Constants
|
14
|
+
|
15
|
+
UTF_8 = Encoding::UTF_8.name
|
16
|
+
|
17
|
+
EMPTY_COLLECTION_DOC = Nokogiri::XML::Builder.new(encoding: UTF_8) do |xml|
|
18
|
+
xml.collection(xmlns: ::MARC::MARC_NS)
|
19
|
+
end.doc.freeze
|
20
|
+
|
21
|
+
COLLECTION_CLOSING_TAG = '</collection>'.freeze
|
22
|
+
|
23
|
+
DEFAULT_NOKOGIRI_OPTS = { encoding: UTF_8 }.freeze
|
24
|
+
|
25
|
+
# ------------------------------------------------------------
|
26
|
+
# Fields
|
27
|
+
|
28
|
+
attr_reader :out
|
29
|
+
attr_reader :nokogiri_options
|
30
|
+
|
31
|
+
# ------------------------------------------------------------
|
32
|
+
# Initializer
|
33
|
+
|
34
|
+
# Initializes a new {XMLWriter}.
|
35
|
+
#
|
36
|
+
# ```ruby
|
37
|
+
# File.open('marc.xml', 'wb') do |f|
|
38
|
+
# w = XMLWriter.new(f)
|
39
|
+
# marc_records.each { |r| w.write(r) }
|
40
|
+
# w.close
|
41
|
+
# end
|
42
|
+
# ```
|
43
|
+
#
|
44
|
+
# @param out [IO, String] an IO, or the name of a file
|
45
|
+
# @param nokogiri_options [Hash] Options passed to
|
46
|
+
# {https://nokogiri.org/rdoc/Nokogiri/XML/Node.html#method-i-write_to Nokogiri::XML::Node#write_to}
|
47
|
+
# Note that the `encoding` option is ignored, except insofar as
|
48
|
+
# passing an encoding other than UTF-8 will raise an `ArgumentError`.
|
49
|
+
# @raise ArgumentError if `out` is not an IO or a string, or is a string referencing
|
50
|
+
# a file path that cannot be opened for writing; or if an encoding other than UTF-8
|
51
|
+
# is specified in `nokogiri-options`
|
52
|
+
# @see #open
|
53
|
+
def initialize(out, **nokogiri_options)
|
54
|
+
@nokogiri_options = valid_nokogiri_options(nokogiri_options)
|
55
|
+
@out = ensure_io(out)
|
56
|
+
end
|
57
|
+
|
58
|
+
# ------------------------------------------------------------
|
59
|
+
# Class methods
|
60
|
+
|
61
|
+
class << self
|
62
|
+
|
63
|
+
# Opens a new {XMLWriter} with the specified output destination and
|
64
|
+
# Nokogiri options, writes the XML prolog and opening `<collection>`
|
65
|
+
# tag, yields the writer to write one or more MARC records, and closes
|
66
|
+
# the writer.
|
67
|
+
#
|
68
|
+
# ```ruby
|
69
|
+
# XMLWriter.open('marc.xml') do |w|
|
70
|
+
# marc_records.each { |r| w.write(r) }
|
71
|
+
# end
|
72
|
+
# ```
|
73
|
+
#
|
74
|
+
# Note that unlike initializing a writer with {#new} and closing it
|
75
|
+
# immediately, this will write an XML document with an empty
|
76
|
+
# `<collection></collection>` tag even if no records are written.
|
77
|
+
#
|
78
|
+
# @yieldparam writer [XMLWriter] the writer
|
79
|
+
# @see #new
|
80
|
+
# @see #close
|
81
|
+
def open(out, **nokogiri_options)
|
82
|
+
writer = new(out, **nokogiri_options)
|
83
|
+
writer.send(:ensure_open!)
|
84
|
+
yield writer if block_given?
|
85
|
+
writer.close
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# ------------------------------------------------------------
|
90
|
+
# Instance methods
|
91
|
+
|
92
|
+
# Writes the specified record to the underlying stream, writing the
|
93
|
+
# XML prolog and opening `<collection>` tag if they have not yet
|
94
|
+
# been written.
|
95
|
+
#
|
96
|
+
# @param record [::MARC::Record] the MARC record to write.
|
97
|
+
# @raise IOError if the underlying stream has already been closed.
|
98
|
+
def write(record)
|
99
|
+
ensure_open!
|
100
|
+
record_element = XMLBuilder.new(record).build
|
101
|
+
record_element.write_to(out, nokogiri_options)
|
102
|
+
out.write("\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Closes the underlying stream. If the XML prolog and opening `<collection>`
|
106
|
+
# tag have already been written, the closing `<collection/>` tag is written
|
107
|
+
# first.
|
108
|
+
def close
|
109
|
+
out.write(COLLECTION_CLOSING_TAG) if @open
|
110
|
+
out.close
|
111
|
+
end
|
112
|
+
|
113
|
+
# ------------------------------------------------------------
|
114
|
+
# Private
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def ensure_open!
|
119
|
+
return if @open
|
120
|
+
|
121
|
+
out.write(prolog_and_opening_tag)
|
122
|
+
@open = true
|
123
|
+
end
|
124
|
+
|
125
|
+
def prolog_and_opening_tag
|
126
|
+
StringIO.open do |tmp|
|
127
|
+
EMPTY_COLLECTION_DOC.write_to(tmp, nokogiri_options)
|
128
|
+
result = tmp.string
|
129
|
+
result.sub!(%r{/>\s*$}, ">\n")
|
130
|
+
result
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def ensure_io(file)
|
135
|
+
return file if writer_like?(file)
|
136
|
+
return File.open(file, 'wb') if parent_exists?(file)
|
137
|
+
|
138
|
+
raise ArgumentError, "Don't know how to write XML to #{file.inspect}: not an IO or file path"
|
139
|
+
end
|
140
|
+
|
141
|
+
def valid_nokogiri_options(opts)
|
142
|
+
if (encoding = opts.delete(:encoding)) && encoding != UTF_8
|
143
|
+
raise ArgumentError, "#{self.class.name} only supports #{UTF_8}; unable to use specified encoding #{encoding}"
|
144
|
+
end
|
145
|
+
|
146
|
+
DEFAULT_NOKOGIRI_OPTS.merge(opts)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -7,7 +7,7 @@ module BerkeleyLibrary
|
|
7
7
|
SUMMARY = 'TIND DA utilities for the UC Berkeley Library'.freeze
|
8
8
|
DESCRIPTION = 'UC Berkeley Library utility gem for working with the TIND DA digital archive.'.freeze
|
9
9
|
LICENSE = 'MIT'.freeze
|
10
|
-
VERSION = '0.
|
10
|
+
VERSION = '0.5.1'.freeze
|
11
11
|
HOMEPAGE = 'https://github.com/BerkeleyLibrary/tind'.freeze
|
12
12
|
end
|
13
13
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module BerkeleyLibrary
|
2
|
+
module Util
|
3
|
+
# TODO: Move this to `berkeley_library-util`
|
4
|
+
module Files
|
5
|
+
class << self
|
6
|
+
include Files
|
7
|
+
end
|
8
|
+
|
9
|
+
def file_exists?(path)
|
10
|
+
(path.respond_to?(:exist?) && path.exist?) ||
|
11
|
+
(path.respond_to?(:to_str) && File.exist?(path))
|
12
|
+
end
|
13
|
+
|
14
|
+
def parent_exists?(path)
|
15
|
+
path.respond_to?(:parent) && path.parent.exist? ||
|
16
|
+
path.respond_to?(:to_str) && Pathname.new(path).parent.exist?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns true if `obj` is close enough to an IO object for Nokogiri
|
20
|
+
# to parse as one.
|
21
|
+
#
|
22
|
+
# @param obj [Object] the object that might be an IO
|
23
|
+
# @see https://github.com/sparklemotion/nokogiri/blob/v1.11.1/lib/nokogiri/xml/sax/parser.rb#L81 Nokogiri::XML::SAX::Parser#parse
|
24
|
+
def reader_like?(obj)
|
25
|
+
obj.respond_to?(:read) && obj.respond_to?(:close)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if `obj` is close enough to an IO object for Nokogiri
|
29
|
+
# to write to.
|
30
|
+
#
|
31
|
+
# @param obj [Object] the object that might be an IO
|
32
|
+
def writer_like?(obj)
|
33
|
+
# TODO: is it possible/desirable to loosen this? how strict is libxml2?
|
34
|
+
obj.is_a?(IO) || obj.is_a?(StringIO)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -24,6 +24,8 @@ module BerkeleyLibrary
|
|
24
24
|
tag = '245'
|
25
25
|
ind_bad = '!'
|
26
26
|
|
27
|
+
records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: false).to_a
|
28
|
+
|
27
29
|
record = records.first
|
28
30
|
record[tag].indicator1 = ind_bad
|
29
31
|
expect { table << record }.to raise_error(Export::ExportException) do |e|
|
@@ -16,6 +16,25 @@ module BerkeleyLibrary
|
|
16
16
|
expect(record0['024']['a']).to eq('BANC PIC 1982.078:15--ALB')
|
17
17
|
end
|
18
18
|
|
19
|
+
describe 'freeze: true' do
|
20
|
+
it 'freezes the records' do
|
21
|
+
reader = XMLReader.new('spec/data/records-api-search.xml', freeze: true)
|
22
|
+
records = reader.to_a
|
23
|
+
expect(records).not_to be_empty # just to be sure
|
24
|
+
records.each { |record| expect(record).to be_frozen }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe :records_yielded do
|
29
|
+
it 'counts the records' do
|
30
|
+
reader = XMLReader.new('spec/data/records-api-search.xml')
|
31
|
+
reader.each_with_index do |_, i|
|
32
|
+
expect(reader.records_yielded).to eq(i)
|
33
|
+
end
|
34
|
+
expect(reader.records_yielded).to eq(5)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
19
38
|
describe :new do
|
20
39
|
it 'accepts a string path' do
|
21
40
|
path = 'spec/data/records-api-search.xml'
|
@@ -59,7 +78,7 @@ module BerkeleyLibrary
|
|
59
78
|
|
60
79
|
it 'raises ArgumentError if passed something random' do
|
61
80
|
non_xml = Object.new
|
62
|
-
# noinspection
|
81
|
+
# noinspection RubyMismatchedArgumentType
|
63
82
|
expect { XMLReader.new(non_xml) }.to raise_error(ArgumentError)
|
64
83
|
end
|
65
84
|
end
|
@@ -87,6 +106,28 @@ module BerkeleyLibrary
|
|
87
106
|
end
|
88
107
|
end
|
89
108
|
|
109
|
+
describe 'TIND peculiarities' do
|
110
|
+
attr_reader :record
|
111
|
+
|
112
|
+
before(:each) do
|
113
|
+
reader = XMLReader.new('spec/data/new-records.xml')
|
114
|
+
records = reader.to_a
|
115
|
+
expect(records.size).to eq(1) # just to be sure
|
116
|
+
@record = records.first
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'converts backslashes in control fields to spaces' do
|
120
|
+
cf_008 = record['008']
|
121
|
+
expect(cf_008).to be_a(::MARC::ControlField)
|
122
|
+
expect(cf_008.value).to eq('190409s2015 xx eng ')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'parses CF 000 as the leader' do
|
126
|
+
expect(record.leader).to eq('00287cam a2200313 4500')
|
127
|
+
expect(record['000']).to be_nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
90
131
|
end
|
91
132
|
end
|
92
133
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'equivalent-xml'
|
3
|
+
|
4
|
+
module BerkeleyLibrary
|
5
|
+
module TIND
|
6
|
+
module MARC
|
7
|
+
describe XMLWriter do
|
8
|
+
let(:input_path) { 'spec/data/new-records.xml' }
|
9
|
+
attr_reader :record
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
reader = XMLReader.new(input_path)
|
13
|
+
@record = reader.first
|
14
|
+
end
|
15
|
+
|
16
|
+
describe :open do
|
17
|
+
|
18
|
+
it 'writes a MARC record to a file as XML' do
|
19
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
20
|
+
output_path = File.join(dir, 'marc.xml')
|
21
|
+
XMLWriter.open(output_path) { |w| w.write(record) }
|
22
|
+
|
23
|
+
expected = File.open(input_path) { |f| Nokogiri::XML(f) }
|
24
|
+
actual = File.open(output_path) { |f| Nokogiri::XML(f) }
|
25
|
+
|
26
|
+
aggregate_failures do
|
27
|
+
EquivalentXml.equivalent?(expected, actual) do |n1, n2, result|
|
28
|
+
expect(n2.to_s).to eq(n1.to_s) unless result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'writes a MARC record to a StringIO' do
|
35
|
+
out = StringIO.new
|
36
|
+
XMLWriter.open(out) { |w| w.write(record) }
|
37
|
+
expected = File.open(input_path) { |f| Nokogiri::XML(f) }
|
38
|
+
actual = Nokogiri::XML(out.string)
|
39
|
+
aggregate_failures do
|
40
|
+
EquivalentXml.equivalent?(expected, actual) do |n1, n2, result|
|
41
|
+
expect(n2.to_s).to eq(n1.to_s) unless result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'accepts Nokogiri options' do
|
47
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
48
|
+
expected_path = File.join(dir, 'expected.xml')
|
49
|
+
XMLWriter.open(expected_path) { |w| w.write(record) }
|
50
|
+
|
51
|
+
actual_path = File.join(dir, 'actual.xml')
|
52
|
+
XMLWriter.open(actual_path, indent_text: "\t") { |w| w.write(record) }
|
53
|
+
|
54
|
+
expected = File.read(expected_path).gsub(%r{ (?= *<)(?!/)}, "\t")
|
55
|
+
actual = File.read(actual_path)
|
56
|
+
expect(actual).to eq(expected)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts an explicit UTF-8 argument' do
|
61
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
62
|
+
output_path = File.join(dir, 'marc.xml')
|
63
|
+
XMLWriter.open(output_path, encoding: 'UTF-8') { |w| w.write(record) }
|
64
|
+
|
65
|
+
expected = File.open(input_path) { |f| Nokogiri::XML(f) }
|
66
|
+
actual = File.open(output_path) { |f| Nokogiri::XML(f) }
|
67
|
+
|
68
|
+
aggregate_failures do
|
69
|
+
EquivalentXml.equivalent?(expected, actual) do |n1, n2, result|
|
70
|
+
expect(n2.to_s).to eq(n1.to_s) unless result
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'only writes UTF-8' do
|
77
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
78
|
+
output_path = File.join(dir, 'marc.xml')
|
79
|
+
expect { XMLWriter.open(output_path, encoding: 'UTF-16') }.to raise_error(ArgumentError)
|
80
|
+
expect(File.exist?(output_path)).to eq(false)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'rejects an invalid file path' do
|
85
|
+
bad_directory = Dir.mktmpdir(File.basename(__FILE__, '.rb')) { |dir| dir }
|
86
|
+
expect(File.directory?(bad_directory)).to eq(false)
|
87
|
+
output_path = File.join(bad_directory, 'marc.xml')
|
88
|
+
expect { XMLWriter.open(output_path) }.to raise_error(ArgumentError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'rejects a non-IO, non-String argument' do
|
92
|
+
invalid_target = Object.new
|
93
|
+
expect { XMLWriter.open(invalid_target) }.to raise_error(ArgumentError)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe :close do
|
98
|
+
it 'closes without writing the closing tag if nothing has been written' do
|
99
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
100
|
+
output_path = File.join(dir, 'marc.xml')
|
101
|
+
w = XMLWriter.new(output_path)
|
102
|
+
w.close
|
103
|
+
|
104
|
+
stat = File.stat(output_path)
|
105
|
+
expect(stat.size).to eq(0)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'writes the closing tag if the opening tag has been written' do
|
110
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
111
|
+
output_path = File.join(dir, 'marc.xml')
|
112
|
+
XMLWriter.open(output_path)
|
113
|
+
expect(File.exist?(output_path)).to eq(true)
|
114
|
+
|
115
|
+
doc = File.open(output_path) { |f| Nokogiri::XML(f) }
|
116
|
+
expect(doc.root.name).to eq('collection')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe :write do
|
122
|
+
it 'raises an IOError if the writer has already been closed' do
|
123
|
+
Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
|
124
|
+
output_path = File.join(dir, 'marc.xml')
|
125
|
+
w = XMLWriter.new(output_path)
|
126
|
+
w.close
|
127
|
+
|
128
|
+
expect { w.write(record) }.to raise_error(IOError)
|
129
|
+
|
130
|
+
stat = File.stat(output_path)
|
131
|
+
expect(stat.size).to eq(0)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'does not write a nil leader' do
|
136
|
+
record.leader = nil
|
137
|
+
marc_xml = StringIO.open do |out|
|
138
|
+
XMLWriter.open(out) { |w| w.write(record) }
|
139
|
+
out.string
|
140
|
+
end
|
141
|
+
expect(marc_xml).not_to include('leader')
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'does not write a blank leader' do
|
145
|
+
record.leader = ''
|
146
|
+
marc_xml = StringIO.open do |out|
|
147
|
+
XMLWriter.open(out) { |w| w.write(record) }
|
148
|
+
out.string
|
149
|
+
end
|
150
|
+
expect(marc_xml).not_to include('leader')
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'issue #4' do
|
154
|
+
let(:record_expected) { ::MARC::XMLReader.new('spec/data/issue-4.xml').first }
|
155
|
+
let(:record_actual) do
|
156
|
+
marc_xml = StringIO.open do |out|
|
157
|
+
XMLWriter.open(out) { |w| w.write(record_expected) }
|
158
|
+
out.string
|
159
|
+
end
|
160
|
+
|
161
|
+
::MARC::XMLReader.new(StringIO.new(marc_xml)).first
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'does not reorder fields' do
|
165
|
+
expected_tags = record_expected.fields.map(&:tag)
|
166
|
+
actual_tags = record_actual.fields.map(&:tag).reject { |t| t == '000' }
|
167
|
+
|
168
|
+
expect(actual_tags).to eq(expected_tags)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'supports FFT fields' do
|
172
|
+
df_expected = record_expected['FFT']
|
173
|
+
expect(df_expected).to be_a(::MARC::DataField) # just to be sure
|
174
|
+
|
175
|
+
df_actual = record_actual['FFT']
|
176
|
+
expect(df_actual).to be_a(::MARC::DataField)
|
177
|
+
%i[tag indicator1 indicator2].each do |attr|
|
178
|
+
v_actual = df_actual.send(attr)
|
179
|
+
v_expected = df_expected.send(attr)
|
180
|
+
expect(v_actual).to eq(v_expected)
|
181
|
+
end
|
182
|
+
|
183
|
+
df_expected.subfields.each_with_index do |sf_expected, i|
|
184
|
+
sf_actual = df_actual.subfields[i]
|
185
|
+
expect(sf_actual.code).to eq(sf_expected.code)
|
186
|
+
expect(sf_actual.value).to eq(sf_expected.value)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<collection xmlns="http://www.loc.gov/MARC21/slim" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd">
|
3
|
+
<record>
|
4
|
+
<leader> Z 22 4500</leader>
|
5
|
+
<datafield tag="245" ind1=" " ind2=" ">
|
6
|
+
<subfield code="a">Caricatures of Paul Bunyan</subfield>
|
7
|
+
</datafield>
|
8
|
+
<datafield tag="260" ind1=" " ind2=" ">
|
9
|
+
<subfield code="c">19--.</subfield>
|
10
|
+
</datafield>
|
11
|
+
<datafield tag="300" ind1=" " ind2=" ">
|
12
|
+
<subfield code="b">color</subfield>
|
13
|
+
<subfield code="c">10 1/2 x 12 1/2 inches.</subfield>
|
14
|
+
<subfield code="a">16 caricatures</subfield>
|
15
|
+
</datafield>
|
16
|
+
<datafield tag="500" ind1=" " ind2=" ">
|
17
|
+
<subfield code="a">Title supplied by cataloger.</subfield>
|
18
|
+
</datafield>
|
19
|
+
<datafield tag="500" ind1=" " ind2=" ">
|
20
|
+
<subfield code="a">Published or produced by the Mead Sales Company. Identified artists include Hutton, S.W. Wilcox, and Henry C. Pitz.</subfield>
|
21
|
+
</datafield>
|
22
|
+
<datafield tag="650" ind1=" " ind2=" ">
|
23
|
+
<subfield code="a">Bunyan, Paul (Legendary character) Pictorial works. </subfield>
|
24
|
+
</datafield>
|
25
|
+
<datafield tag="655" ind1=" " ind2=" ">
|
26
|
+
<subfield code="2">gmgpc</subfield>
|
27
|
+
<subfield code="a">Caricatures Color. </subfield>
|
28
|
+
</datafield>
|
29
|
+
<datafield tag="700" ind1="1" ind2=" ">
|
30
|
+
<subfield code="a">Pitz, Henry C. 1895-1976. (Henry Clarence),</subfield>
|
31
|
+
</datafield>
|
32
|
+
<datafield tag="710" ind1="2" ind2=" ">
|
33
|
+
<subfield code="a">Mead Sales Company.</subfield>
|
34
|
+
</datafield>
|
35
|
+
<datafield tag="903" ind1=" " ind2=" ">
|
36
|
+
<subfield code="b">c</subfield>
|
37
|
+
</datafield>
|
38
|
+
<datafield tag="041" ind1=" " ind2=" ">
|
39
|
+
<subfield code="a">eng</subfield>
|
40
|
+
</datafield>
|
41
|
+
<datafield tag="902" ind1=" " ind2=" ">
|
42
|
+
<subfield code="d">2022-03-23</subfield>
|
43
|
+
</datafield>
|
44
|
+
<datafield tag="336" ind1=" " ind2=" ">
|
45
|
+
<subfield code="a">Image</subfield>
|
46
|
+
</datafield>
|
47
|
+
<datafield tag="852" ind1=" " ind2=" ">
|
48
|
+
<subfield code="c">Bioscience, Natural Resources & Public Health Library</subfield>
|
49
|
+
</datafield>
|
50
|
+
<datafield tag="980" ind1=" " ind2=" ">
|
51
|
+
<subfield code="a">Forestry</subfield>
|
52
|
+
</datafield>
|
53
|
+
<datafield tag="982" ind1=" " ind2=" ">
|
54
|
+
<subfield code="a">Forestry</subfield>
|
55
|
+
<subfield code="b">Forestry</subfield>
|
56
|
+
</datafield>
|
57
|
+
<datafield tag="901" ind1=" " ind2=" ">
|
58
|
+
<subfield code="m">991065640639706532</subfield>
|
59
|
+
</datafield>
|
60
|
+
<datafield tag="856" ind1="4" ind2="1">
|
61
|
+
<subfield code="u">https://search.library.berkeley.edu/discovery/fulldisplay?context=L&vid=01UCS_BER:UCB&docid=alma991065640639706532</subfield>
|
62
|
+
<subfield code="y">View library catalog record.</subfield>
|
63
|
+
</datafield>
|
64
|
+
<datafield tag="998" ind1=" " ind2=" ">
|
65
|
+
<subfield code="a">fake-value</subfield>
|
66
|
+
</datafield>
|
67
|
+
<datafield tag="035" ind1=" " ind2=" ">
|
68
|
+
<subfield code="a">b142086125</subfield>
|
69
|
+
</datafield>
|
70
|
+
<datafield tag="FFT" ind1=" " ind2=" ">
|
71
|
+
<subfield code="d">001</subfield>
|
72
|
+
<subfield code="a">https://digitalassets.lib.berkeley.edu/forestry/ucb/images/b142086125_i180839998/b142086125_i180839998_001.jpg</subfield>
|
73
|
+
</datafield>
|
74
|
+
</record>
|
75
|
+
<record>
|
76
|
+
<leader> Z 22 4500</leader>
|
77
|
+
<datafield tag="245" ind1=" " ind2=" ">
|
78
|
+
<subfield code="a">Photographs from the University of California Forestry Club</subfield>
|
79
|
+
</datafield>
|
80
|
+
<datafield tag="260" ind1=" " ind2=" ">
|
81
|
+
<subfield code="c">1930-1939.</subfield>
|
82
|
+
</datafield>
|
83
|
+
<datafield tag="300" ind1=" " ind2=" ">
|
84
|
+
<subfield code="a">1 album (photographic prints)</subfield>
|
85
|
+
</datafield>
|
86
|
+
<datafield tag="500" ind1=" " ind2=" ">
|
87
|
+
<subfield code="a">Title supplied by cataloger.</subfield>
|
88
|
+
</datafield>
|
89
|
+
<datafield tag="500" ind1=" " ind2=" ">
|
90
|
+
<subfield code="a">Photographed and or compiled by the Forestry Club.</subfield>
|
91
|
+
</datafield>
|
92
|
+
<datafield tag="520" ind1=" " ind2=" ">
|
93
|
+
<subfield code="a">Album contains views of the University of California campus, Camp Califorest, and of the camp newspaper (Bull of the Woods).</subfield>
|
94
|
+
</datafield>
|
95
|
+
<datafield tag="610" ind1=" " ind2=" ">
|
96
|
+
<subfield code="a">University of California (1868-1952) Forestry Club Pictorial works. </subfield>
|
97
|
+
</datafield>
|
98
|
+
<datafield tag="610" ind1=" " ind2=" ">
|
99
|
+
<subfield code="a">University of California (1868-1952) Camp Califorest Pictorial works. </subfield>
|
100
|
+
</datafield>
|
101
|
+
<datafield tag="610" ind1=" " ind2=" ">
|
102
|
+
<subfield code="a">University of California (1868-1952) Pictorial works. </subfield>
|
103
|
+
</datafield>
|
104
|
+
<datafield tag="655" ind1=" " ind2=" ">
|
105
|
+
<subfield code="2">gmgpc</subfield>
|
106
|
+
<subfield code="a">Photographs.</subfield>
|
107
|
+
</datafield>
|
108
|
+
<datafield tag="710" ind1="2" ind2=" ">
|
109
|
+
<subfield code="a">University of California, Berkeley. Forestry students.</subfield>
|
110
|
+
</datafield>
|
111
|
+
<datafield tag="710" ind1="2" ind2=" ">
|
112
|
+
<subfield code="a">University of California (1868-1952) Forestry Club.</subfield>
|
113
|
+
</datafield>
|
114
|
+
<datafield tag="903" ind1=" " ind2=" ">
|
115
|
+
<subfield code="b">c</subfield>
|
116
|
+
</datafield>
|
117
|
+
<datafield tag="041" ind1=" " ind2=" ">
|
118
|
+
<subfield code="a">eng</subfield>
|
119
|
+
</datafield>
|
120
|
+
<datafield tag="269" ind1=" " ind2=" ">
|
121
|
+
<subfield code="a">1930</subfield>
|
122
|
+
</datafield>
|
123
|
+
<datafield tag="902" ind1=" " ind2=" ">
|
124
|
+
<subfield code="d">2022-03-23</subfield>
|
125
|
+
</datafield>
|
126
|
+
<datafield tag="336" ind1=" " ind2=" ">
|
127
|
+
<subfield code="a">Image</subfield>
|
128
|
+
</datafield>
|
129
|
+
<datafield tag="852" ind1=" " ind2=" ">
|
130
|
+
<subfield code="c">Bioscience, Natural Resources & Public Health Library</subfield>
|
131
|
+
</datafield>
|
132
|
+
<datafield tag="980" ind1=" " ind2=" ">
|
133
|
+
<subfield code="a">Forestry</subfield>
|
134
|
+
</datafield>
|
135
|
+
<datafield tag="982" ind1=" " ind2=" ">
|
136
|
+
<subfield code="a">Forestry</subfield>
|
137
|
+
<subfield code="b">Forestry</subfield>
|
138
|
+
</datafield>
|
139
|
+
<datafield tag="901" ind1=" " ind2=" ">
|
140
|
+
<subfield code="m">991065707389706532</subfield>
|
141
|
+
</datafield>
|
142
|
+
<datafield tag="856" ind1="4" ind2="1">
|
143
|
+
<subfield code="u">https://search.library.berkeley.edu/discovery/fulldisplay?context=L&vid=01UCS_BER:UCB&docid=alma991065707389706532</subfield>
|
144
|
+
<subfield code="y">View library catalog record.</subfield>
|
145
|
+
</datafield>
|
146
|
+
<datafield tag="998" ind1=" " ind2=" ">
|
147
|
+
<subfield code="a">fake-value</subfield>
|
148
|
+
</datafield>
|
149
|
+
<datafield tag="035" ind1=" " ind2=" ">
|
150
|
+
<subfield code="a">b142107827</subfield>
|
151
|
+
</datafield>
|
152
|
+
<datafield tag="FFT" ind1=" " ind2=" ">
|
153
|
+
<subfield code="d">002</subfield>
|
154
|
+
<subfield code="a">https://digitalassets.lib.berkeley.edu/forestry/ucb/images/b142086125_i180839998/b142086125_i180839998_002.jpg</subfield>
|
155
|
+
</datafield>
|
156
|
+
</record>
|
157
|
+
</collection>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!--
|
3
|
+
Source: "Batch Uploader: Caveats, common errors and example metadata files", docs.tind.io
|
4
|
+
-->
|
5
|
+
<collection xmlns="http://www.loc.gov/MARC21/slim">
|
6
|
+
<record>
|
7
|
+
|
8
|
+
<!-- The Leader is encoded in the `000` control field.
|
9
|
+
If you want to edit the leader in software such as
|
10
|
+
MarcEdit, you will need to change these fields to a
|
11
|
+
leader tag. Then, before import into the repository
|
12
|
+
you will need to change the fields back to controlfields
|
13
|
+
with tag `000`.
|
14
|
+
-->
|
15
|
+
<controlfield tag="000">00287cam\a2200313\\\4500</controlfield>
|
16
|
+
|
17
|
+
<!-- All whitespace in control fields need to be replaced with
|
18
|
+
backspaces.
|
19
|
+
-->
|
20
|
+
<controlfield tag="008">190409s2015\\\\xx\\\\\\\\\\\\\\\\\\eng\\</controlfield>
|
21
|
+
|
22
|
+
<!-- Regular fields are encoded in datafield elements. -->
|
23
|
+
<datafield tag="100" ind1="0" ind2=" ">
|
24
|
+
<subfield code="a">Aristotle</subfield>
|
25
|
+
<subfield code="0">580897</subfield>
|
26
|
+
</datafield>
|
27
|
+
|
28
|
+
<datafield tag="245" ind1="0" ind2="0">
|
29
|
+
<subfield code="a">Metaphysics</subfield>
|
30
|
+
<subfield code="c">Aristotle</subfield>
|
31
|
+
</datafield>
|
32
|
+
|
33
|
+
<datafield tag="260" ind1=" " ind2=" ">
|
34
|
+
<subfield code="a">Narnia</subfield>
|
35
|
+
<subfield code="b">Fictive Books</subfield>
|
36
|
+
<subfield code="c">2015</subfield>
|
37
|
+
</datafield>
|
38
|
+
|
39
|
+
<!-- Make sure to include a collection when uploading new
|
40
|
+
records, so that the record will be searchable.
|
41
|
+
-->
|
42
|
+
<datafield tag="980" ind1=" " ind2=" ">
|
43
|
+
<subfield code="a">BIB</subfield>
|
44
|
+
</datafield>
|
45
|
+
</record>
|
46
|
+
</collection>
|