berkeley_library-tind 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +20 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/tind.iml +138 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.rubocop.yml +334 -0
  10. data/.ruby-version +1 -0
  11. data/.simplecov +8 -0
  12. data/.yardopts +1 -0
  13. data/CHANGES.md +58 -0
  14. data/Dockerfile +57 -0
  15. data/Gemfile +3 -0
  16. data/Jenkinsfile +18 -0
  17. data/LICENSE.md +21 -0
  18. data/README.md +73 -0
  19. data/Rakefile +20 -0
  20. data/berkeley_library-tind.gemspec +50 -0
  21. data/bin/tind-export +14 -0
  22. data/docker-compose.yml +15 -0
  23. data/lib/berkeley_library/tind.rb +3 -0
  24. data/lib/berkeley_library/tind/api.rb +1 -0
  25. data/lib/berkeley_library/tind/api/api.rb +132 -0
  26. data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
  27. data/lib/berkeley_library/tind/api/collection.rb +82 -0
  28. data/lib/berkeley_library/tind/api/date_range.rb +67 -0
  29. data/lib/berkeley_library/tind/api/format.rb +32 -0
  30. data/lib/berkeley_library/tind/api/search.rb +100 -0
  31. data/lib/berkeley_library/tind/config.rb +103 -0
  32. data/lib/berkeley_library/tind/export.rb +1 -0
  33. data/lib/berkeley_library/tind/export/column.rb +54 -0
  34. data/lib/berkeley_library/tind/export/column_group.rb +144 -0
  35. data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
  36. data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
  37. data/lib/berkeley_library/tind/export/config.rb +154 -0
  38. data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
  39. data/lib/berkeley_library/tind/export/export.rb +47 -0
  40. data/lib/berkeley_library/tind/export/export_command.rb +168 -0
  41. data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
  42. data/lib/berkeley_library/tind/export/export_format.rb +67 -0
  43. data/lib/berkeley_library/tind/export/exporter.rb +105 -0
  44. data/lib/berkeley_library/tind/export/filter.rb +52 -0
  45. data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
  46. data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
  47. data/lib/berkeley_library/tind/export/row.rb +24 -0
  48. data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
  49. data/lib/berkeley_library/tind/export/table.rb +175 -0
  50. data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
  51. data/lib/berkeley_library/tind/marc.rb +1 -0
  52. data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
  53. data/lib/berkeley_library/tind/module_info.rb +14 -0
  54. data/lib/berkeley_library/util/arrays.rb +178 -0
  55. data/lib/berkeley_library/util/logging.rb +1 -0
  56. data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
  57. data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
  58. data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
  59. data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
  60. data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
  61. data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
  62. data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
  63. data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
  64. data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
  65. data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
  66. data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
  67. data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
  68. data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
  69. data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
  70. data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
  71. data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
  72. data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
  73. data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
  74. data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
  75. data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
  76. data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
  77. data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
  78. data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
  79. data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
  80. data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
  81. data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
  82. data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
  83. data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
  84. data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
  85. data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
  86. data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
  87. data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
  88. data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
  89. data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
  90. data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
  91. data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
  92. data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
  93. data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
  94. data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
  95. data/lib/berkeley_library/util/paths.rb +111 -0
  96. data/lib/berkeley_library/util/stringios.rb +30 -0
  97. data/lib/berkeley_library/util/strings.rb +42 -0
  98. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  99. data/lib/berkeley_library/util/times.rb +22 -0
  100. data/lib/berkeley_library/util/uris.rb +44 -0
  101. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  102. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  103. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  104. data/rakelib/bundle.rake +8 -0
  105. data/rakelib/coverage.rake +11 -0
  106. data/rakelib/gem.rake +54 -0
  107. data/rakelib/rubocop.rake +18 -0
  108. data/rakelib/spec.rake +2 -0
  109. data/spec/.rubocop.yml +40 -0
  110. data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
  111. data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
  112. data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
  113. data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
  114. data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
  115. data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
  116. data/spec/berkeley_library/tind/config_spec.rb +86 -0
  117. data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
  118. data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
  119. data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
  120. data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
  121. data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
  122. data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
  123. data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
  124. data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
  125. data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
  126. data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
  127. data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
  128. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  129. data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
  130. data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
  131. data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
  132. data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
  133. data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
  134. data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
  135. data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
  136. data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
  137. data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
  138. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  139. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  140. data/spec/berkeley_library/util/strings_spec.rb +27 -0
  141. data/spec/berkeley_library/util/times_spec.rb +39 -0
  142. data/spec/berkeley_library/util/uris_spec.rb +118 -0
  143. data/spec/data/collection-names.txt +438 -0
  144. data/spec/data/collections.json +4827 -0
  145. data/spec/data/disjoint-records.xml +187 -0
  146. data/spec/data/record-184453.xml +58 -0
  147. data/spec/data/record-184458.xml +63 -0
  148. data/spec/data/record-187888.xml +78 -0
  149. data/spec/data/records-api-search-cjk-p1.xml +6381 -0
  150. data/spec/data/records-api-search-cjk-p2.xml +5 -0
  151. data/spec/data/records-api-search-p1.xml +4506 -0
  152. data/spec/data/records-api-search-p2.xml +4509 -0
  153. data/spec/data/records-api-search-p3.xml +4506 -0
  154. data/spec/data/records-api-search-p4.xml +4509 -0
  155. data/spec/data/records-api-search-p5.xml +4506 -0
  156. data/spec/data/records-api-search-p6.xml +2436 -0
  157. data/spec/data/records-api-search-p7.xml +5 -0
  158. data/spec/data/records-api-search.xml +234 -0
  159. data/spec/data/records-manual-search.xml +547 -0
  160. data/spec/spec_helper.rb +30 -0
  161. data/test/profile/table_from_records_profile.rb +46 -0
  162. metadata +585 -0
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ # noinspection RubyYardParamTypeMatch
6
+ module Export
7
+ describe Table do
8
+ describe Row do
9
+ describe :values do
10
+
11
+ it 'returns the values for the specified row' do
12
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
13
+ table = Table.from_records(records)
14
+
15
+ record = records[0]
16
+ expected_values = record.data_fields.map(&:subfields).flatten.map(&:value)
17
+
18
+ table << record
19
+ values = table.rows[0].values
20
+
21
+ # match_array gives a better error message than #eq() but doesn't enforce order
22
+ expect(values).to match_array(expected_values)
23
+ expect(values).to eq(expected_values)
24
+ end
25
+
26
+ it 'handles adding records with extra fields' do
27
+ records = %w[184453 184458].map { |n| MARC::XMLReader.read("spec/data/record-#{n}.xml", freeze: true).first }
28
+ table = Table.from_records(records, freeze: true)
29
+
30
+ vv_actual = (0..1).map { |row| table.rows[row].values }
31
+
32
+ vv_expected = records.map { |r| r.data_fields.map(&:subfields).flatten.map(&:value) }
33
+ vv_expected.each_with_index do |expected, index|
34
+ expect(vv_actual[index].compact).to eq(expected)
35
+ end
36
+
37
+ # 184458 has 260 & 269, 184453 doesn't
38
+ vv_184453 = vv_actual[0]
39
+ vv_184458 = vv_actual[1]
40
+ expect(vv_184453.size).to eq(vv_184458.size)
41
+ expect(vv_184453[3..4]).to contain_exactly(nil, nil)
42
+ end
43
+
44
+ it 'handles records with missing fields' do
45
+ records = %w[184458 184453].map { |n| MARC::XMLReader.read("spec/data/record-#{n}.xml", freeze: true).first }
46
+ table = Table.from_records(records, freeze: true)
47
+
48
+ vv_actual = (0..1).map { |row| table.rows[row].values }
49
+
50
+ vv_expected = records.map { |r| r.data_fields.map(&:subfields).flatten.map(&:value) }
51
+ vv_expected.each_with_index do |expected, index|
52
+ expect(vv_actual[index].compact).to eq(expected)
53
+ end
54
+
55
+ # 184458 has 260 & 269, 184453 doesn't
56
+ vv_184458 = vv_actual[0]
57
+ vv_184453 = vv_actual[1]
58
+ expect(vv_184453.size).to eq(vv_184458.size)
59
+ expect(vv_184453[3..4]).to contain_exactly(nil, nil)
60
+ end
61
+
62
+ it 'handles records with disjoint fields' do
63
+ records = MARC::XMLReader.read('spec/data/disjoint-records.xml', freeze: true).to_a
64
+ table = Table.from_records(records, freeze: true)
65
+
66
+ vv_actual = (0...records.size).map { |row| table.rows[row].values }
67
+
68
+ vv_expected = records.map { |r| r.data_fields.map(&:subfields).flatten.map(&:value) }
69
+ vv_expected.each_with_index do |expected, index|
70
+ expect(vv_actual[index]).not_to be_nil
71
+ expect(vv_actual[index].compact).to eq(expected)
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ describe :each_value do
78
+ it 'yields the values' do
79
+ records = MARC::XMLReader.read('spec/data/disjoint-records.xml', freeze: true).to_a
80
+ table = Table.from_records(records, freeze: true)
81
+
82
+ vv_actual = table.each_row.with_object([]) do |row, vv_all|
83
+ vv_row = [].tap do |vv|
84
+ row.each_value { |v| vv << v }
85
+ end
86
+ vv_all << vv_row
87
+ end
88
+
89
+ vv_expected = records.map { |r| r.data_fields.map(&:subfields).flatten.map(&:value) }
90
+ vv_expected.each_with_index do |expected, index|
91
+ expect(vv_actual[index]).not_to be_nil
92
+ expect(vv_actual[index].compact).to eq(expected)
93
+ end
94
+ end
95
+
96
+ it 'is indexable' do
97
+ records = MARC::XMLReader.read('spec/data/disjoint-records.xml', freeze: true).to_a
98
+ table = Table.from_records(records, freeze: true)
99
+
100
+ vv_actual = table.each_row.with_object([]) do |row, vv_all|
101
+ vv_row = [].tap do |vv|
102
+ row.each_value.with_index { |v, i| vv[i] = v }
103
+ end
104
+ vv_all << vv_row
105
+ end
106
+
107
+ vv_expected = records.map { |r| r.data_fields.map(&:subfields).flatten.map(&:value) }
108
+ vv_expected.each_with_index do |expected, index|
109
+ expect(vv_actual[index]).not_to be_nil
110
+ expect(vv_actual[index].compact).to eq(expected)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,322 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ # noinspection RubyYardParamTypeMatch
6
+ module Export
7
+ describe Table do
8
+
9
+ describe :<< do
10
+ let(:table) { Table.new }
11
+ let(:records) { MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a }
12
+
13
+ it 'adds one record' do
14
+ table << (record = records.first)
15
+ expect(table.marc_records).to contain_exactly(record)
16
+ end
17
+
18
+ it 'adds multiple records' do
19
+ records.each { |r| table << r }
20
+ expect(table.marc_records).to eq(records)
21
+ end
22
+
23
+ it 'logs the MARC record ID and data field in the event of a bad indicator' do
24
+ tag = '245'
25
+ ind_bad = '!'
26
+
27
+ record = records.first
28
+ record[tag].indicator1 = ind_bad
29
+ expect { table << record }.to raise_error(Export::ExportException) do |e|
30
+ expect(e.message).to include('184458')
31
+ expect(e.message).to include(tag)
32
+ expect(e.message).to include(ind_bad)
33
+ end
34
+ end
35
+ end
36
+
37
+ describe :headers do
38
+ it 'aligns with the correct values' do
39
+ records = MARC::XMLReader.read('spec/data/disjoint-records.xml', freeze: true).to_a
40
+ table = Table.from_records(records)
41
+
42
+ headers = table.headers
43
+ records.each_with_index do |record, row|
44
+ values = table.rows[row].values
45
+ expect(values).not_to be_nil, "No values found for row #{row}"
46
+
47
+ # uncomment this to debug:
48
+
49
+ # puts "#{row}) #{record['245']['rows']}: #{record['500']['rows']}"
50
+ # headers.each_with_index { |h, i| puts "\t#{h}\t#{values[i]}" }
51
+ # puts "\n" if row + 1 < records.size
52
+
53
+ aggregate_failures 'headers' do
54
+ last_col = -1
55
+ record.data_fields_by_tag.each do |tag, dff|
56
+ dff.each do |df|
57
+ ind1 = df.indicator1 == ' ' ? '_' : df.indicator1
58
+ ind2 = df.indicator2 == ' ' ? '_' : df.indicator2
59
+ df_prefix = "#{tag}#{ind1}#{ind2}"
60
+
61
+ expected_suffix = nil
62
+ df.subfields.each do |sf|
63
+ col = values.find_index(sf.value)
64
+ expect(col).to be > last_col
65
+ last_col = col
66
+
67
+ expected_prefix = "#{df_prefix}#{sf.code}"
68
+
69
+ header = headers[col]
70
+ expect(header).to start_with(expected_prefix)
71
+
72
+ header_suffix = header.scan(/(?<=-)[0-9]+$/).first.to_i
73
+ if expected_suffix
74
+ expect(header_suffix).to eq(expected_suffix), "#{row}, #{col}: (#{sf.value}) Expected #{expected_prefix}-#{expected_suffix}, was #{header}"
75
+ else
76
+ expected_suffix = header_suffix
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe :freeze do
87
+ it 'prevents adding new records' do
88
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
89
+ table = records[0...3].each_with_object(Table.new) { |r, t| t << r }
90
+ table.freeze
91
+ expect(table.row_count).to eq(3) # just to be sure
92
+ original_headers = table.headers.dup
93
+
94
+ # noinspection RubyModifiedFrozenObject
95
+ expect { table << records.last }.to raise_error(FrozenError)
96
+ expect(table.row_count).to eq(3)
97
+ expect(table.headers).to eq(original_headers)
98
+ end
99
+
100
+ it 'freezes the MARC records array' do
101
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
102
+ table = records.each_with_object(Table.new) { |r, t| t << r }
103
+ table.freeze
104
+ expect { table.marc_records << records.last }.to raise_error(FrozenError)
105
+ end
106
+
107
+ it 'freezes the columns' do
108
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
109
+ table = records.each_with_object(Table.new) { |r, t| t << r }
110
+ table.freeze
111
+
112
+ expect { table.columns << Object.new }.to raise_error(FrozenError)
113
+ end
114
+
115
+ it 'freezes the rows' do
116
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
117
+ table = records.each_with_object(Table.new) { |r, t| t << r }
118
+ table.freeze
119
+
120
+ expect { table.rows << Object.new }.to raise_error(FrozenError)
121
+ end
122
+
123
+ it 'returns self' do
124
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
125
+ table = records.each_with_object(Table.new) { |r, t| t << r }
126
+ expect(table.freeze).to be(table)
127
+ end
128
+ end
129
+
130
+ describe :from_records do
131
+ let(:records) { MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a }
132
+ it 'reads the records' do
133
+ table = Table.from_records(records)
134
+ expect(table.row_count).to eq(records.size)
135
+ end
136
+
137
+ it 'optionally freezes the table' do
138
+ table = Table.from_records(records, freeze: true)
139
+ expect(table.frozen?).to eq(true)
140
+ end
141
+
142
+ it 'parses the data fields into rows' do
143
+ table = Table.from_records(records, freeze: true)
144
+
145
+ aggregate_failures 'row parsing' do
146
+ records.each_with_index do |marc_record, row|
147
+ expected_headers = []
148
+ expected_values = []
149
+
150
+ marc_record.each_data_field.each do |df|
151
+ prefix = ColumnGroup.prefix_for(df)
152
+ df.subfields.each do |sf|
153
+ expected_headers << "#{prefix}#{sf.code}"
154
+ expected_values << sf.value
155
+ end
156
+ end
157
+
158
+ expected_index = 0
159
+ table.rows[row].values.each_with_index do |actual_value, index|
160
+ next unless actual_value
161
+
162
+ expected_header = expected_headers[expected_index]
163
+ actual_header = table.headers[index]
164
+ expect(actual_header).to start_with(expected_header)
165
+
166
+ expected_value = expected_values[expected_index]
167
+ expect(actual_value).to eq(expected_value)
168
+
169
+ expected_index += 1
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ describe :exportable_only do
176
+ it 'filters out non-exportable values' do
177
+ excluded_prefixes = (Filter::DO_NOT_EXPORT_FIELDS + Filter::DO_NOT_EXPORT_SUBFIELDS).map { |h| h.gsub(' ', '_') }
178
+ table = Table.from_records(records, freeze: true, exportable_only: true)
179
+
180
+ aggregate_failures 'row parsing' do
181
+ records.each_with_index do |marc_record, row|
182
+ expected_headers = []
183
+ expected_values = []
184
+
185
+ marc_record.each_data_field.each do |df|
186
+ prefix = ColumnGroup.prefix_for(df)
187
+ df.subfields.each do |sf|
188
+ header = "#{prefix}#{sf.code}"
189
+ next if excluded_prefixes.any? { |h| header.start_with?(h) }
190
+
191
+ expected_headers << header
192
+ expected_values << sf.value
193
+ end
194
+ end
195
+
196
+ expected_index = 0
197
+ table.rows[row].values.each_with_index do |actual_value, index|
198
+ next unless actual_value
199
+
200
+ expected_header = expected_headers[expected_index]
201
+ actual_header = table.headers[index]
202
+ expect(actual_header).to start_with(expected_header)
203
+
204
+ expected_value = expected_values[expected_index]
205
+ expect(actual_value).to eq(expected_value)
206
+
207
+ expected_index += 1
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ describe :rows do
216
+
217
+ it 'returns the rows' do
218
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
219
+ table = Table.from_records(records)
220
+ rows = table.rows
221
+ expect(rows.size).to eq(records.size)
222
+ expect(rows.all? { |r| r.is_a?(Row) }).to eq(true)
223
+ end
224
+
225
+ it 'is not cached when table is not frozen' do
226
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
227
+ some_records = records[0...3]
228
+ table = Table.from_records(some_records)
229
+ rows1 = table.rows
230
+ expect(rows1.size).to eq(some_records.size)
231
+ expect(rows1.all? { |r| r.is_a?(Row) }).to eq(true)
232
+
233
+ table << records.last
234
+ rows2 = table.rows
235
+ expect(rows2).not_to be(rows1)
236
+ expect(rows1.size).to eq(some_records.size) # just to be sure
237
+ expect(rows2.size).to eq(rows1.size + 1)
238
+ end
239
+
240
+ it 'is cached when table is frozen' do
241
+ records = MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a
242
+ some_records = records[0...3]
243
+ table = Table.from_records(some_records, freeze: true)
244
+ rows = table.rows
245
+ expect(rows.size).to eq(some_records.size)
246
+ expect(table.rows).to be(rows)
247
+ end
248
+
249
+ end
250
+
251
+ describe :each_row do
252
+ let(:records) { MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true).to_a }
253
+ let(:table) { Table.from_records(records) }
254
+
255
+ it 'yields each row' do
256
+ rows = []
257
+ table.each_row { |r| rows << r }
258
+ expect(rows.size).to eq(records.size)
259
+ expect(rows.all? { |r| r.is_a?(Row) }).to eq(true)
260
+ end
261
+
262
+ it 'returns an enumerator' do
263
+ enum = table.each_row
264
+ expect(enum).to be_an(Enumerator)
265
+ rows = enum.each_with_object([]) { |r, a| a << r }
266
+ expect(rows.size).to eq(records.size)
267
+ expect(rows.all? { |r| r.is_a?(Row) }).to eq(true)
268
+ end
269
+
270
+ it 'is indexable' do
271
+ rows = []
272
+ table.each_row.with_index do |row, i|
273
+ expect(rows[i]).to be_nil # just to be sure
274
+ rows[i] = row
275
+ end
276
+ expect(rows.size).to eq(records.size)
277
+ end
278
+ end
279
+
280
+ describe :to_csv do
281
+ let(:records) { MARC::XMLReader.read('spec/data/records-manual-search.xml', freeze: true) }
282
+ let(:table) { Table.from_records(records, freeze: true) }
283
+
284
+ it 'outputs CSV' do
285
+ csv_str = table.to_csv
286
+ expect(csv_str).to be_a(String)
287
+
288
+ CSV.parse(csv_str, headers: true).each_with_index do |csv_row, row|
289
+ expect(csv_row.headers).to eq(table.headers)
290
+ expect(csv_row.fields).to eq(table.rows[row].values)
291
+ end
292
+ end
293
+
294
+ it 'accepts an IO object' do
295
+ csv_str = StringIO.new.tap { |out| table.to_csv(out) }.string
296
+
297
+ aggregate_failures 'rows' do
298
+ CSV.parse(csv_str, headers: true).each_with_index do |csv_row, row|
299
+ expect(csv_row.headers).to eq(table.headers)
300
+ expect(csv_row.fields).to eq(table.rows[row].values)
301
+ end
302
+ end
303
+ end
304
+
305
+ it 'accepts a filename' do
306
+ Dir.mktmpdir(File.basename(__FILE__, '.rb')) do |dir|
307
+ out_path = File.join(dir, 'out.csv')
308
+ table.to_csv(out_path)
309
+ csv_str = File.read(out_path)
310
+
311
+ CSV.parse(csv_str, headers: true).each_with_index do |csv_row, row|
312
+ expect(csv_row.headers).to eq(table.headers)
313
+ expect(csv_row.fields).to eq(table.rows[row].values)
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module MARC
6
+ describe XMLReader do
7
+ let(:basename) { File.basename(__FILE__, '.rb') }
8
+
9
+ it 'reads MARC records' do
10
+ reader = XMLReader.new('spec/data/records-api-search.xml')
11
+ records = reader.to_a
12
+ expect(records.size).to eq(5)
13
+
14
+ record0 = records[0]
15
+ expect(record0).to be_a(::MARC::Record)
16
+ expect(record0['024']['a']).to eq('BANC PIC 1982.078:15--ALB')
17
+ end
18
+
19
+ describe :new do
20
+ it 'accepts a string path' do
21
+ path = 'spec/data/records-api-search.xml'
22
+ reader = XMLReader.new(path)
23
+ records = reader.to_a
24
+ expect(records.size).to eq(5)
25
+ end
26
+
27
+ it 'accepts a Pathname' do
28
+ path = Pathname.new('spec/data/records-api-search.xml')
29
+ reader = XMLReader.new(path)
30
+ records = reader.to_a
31
+ expect(records.size).to eq(5)
32
+ end
33
+
34
+ it 'accepts an XML string' do
35
+ xml = File.read('spec/data/records-api-search.xml')
36
+ reader = XMLReader.new(xml)
37
+ records = reader.to_a
38
+ expect(records.size).to eq(5)
39
+ end
40
+
41
+ it 'accepts an IO' do
42
+ File.open('spec/data/records-api-search.xml', 'rb') do |f|
43
+ reader = XMLReader.new(f)
44
+ records = reader.to_a
45
+ expect(records.size).to eq(5)
46
+ end
47
+ end
48
+
49
+ it 'raises ArgumentError if passed a nonexistent file' do
50
+ output_path = Dir.mktmpdir(basename) { |dir| File.join(dir, "#{basename}-missing.xml") }
51
+ expect(File.exist?(output_path)).to be_falsey # just to be sure
52
+ expect { XMLReader.new(output_path) }.to raise_error(ArgumentError)
53
+ end
54
+
55
+ it 'raises ArgumentError if passed a non-XML string' do
56
+ non_xml = File.read('spec/data/collections.json')
57
+ expect { XMLReader.new(non_xml) }.to raise_error(ArgumentError)
58
+ end
59
+
60
+ it 'raises ArgumentError if passed something random' do
61
+ non_xml = Object.new
62
+ # noinspection RubyYardParamTypeMatch
63
+ expect { XMLReader.new(non_xml) }.to raise_error(ArgumentError)
64
+ end
65
+ end
66
+
67
+ describe :total do
68
+ it 'parses <total/>' do
69
+ reader = XMLReader.new('spec/data/records-api-search.xml')
70
+ reader.take_while { true } # make sure we've parsed something
71
+ expect(reader.total).to eq(5)
72
+ end
73
+
74
+ it 'parses "Search-Engine-Total-Number-Of-Results"' do
75
+ reader = XMLReader.new('spec/data/records-manual-search.xml')
76
+ reader.take_while { true } # make sure we've parsed something
77
+ expect(reader.total).to eq(10)
78
+ end
79
+ end
80
+
81
+ describe :search_id do
82
+ it 'parses <search_id>' do
83
+ expected = 'DnF1ZXJ5VGhlbkZldG'
84
+ reader = XMLReader.new('spec/data/records-api-search-p1.xml')
85
+ reader.take_while { true } # make sure we've parsed something
86
+ expect(reader.search_id).to eq(expected)
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end