libis-tools 0.9.20 → 0.9.21

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -233
  3. data/Rakefile +5 -0
  4. data/lib/libis/tools.rb +1 -0
  5. data/lib/libis/tools/assert.rb +11 -0
  6. data/lib/libis/tools/checksum.rb +22 -5
  7. data/lib/libis/tools/command.rb +24 -3
  8. data/lib/libis/tools/config.rb +61 -33
  9. data/lib/libis/tools/config_file.rb +0 -1
  10. data/lib/libis/tools/deep_struct.rb +10 -2
  11. data/lib/libis/tools/extend/empty.rb +2 -2
  12. data/lib/libis/tools/extend/hash.rb +37 -18
  13. data/lib/libis/tools/extend/kernel.rb +9 -0
  14. data/lib/libis/tools/extend/string.rb +17 -8
  15. data/lib/libis/tools/logger.rb +95 -44
  16. data/lib/libis/tools/metadata.rb +5 -1
  17. data/lib/libis/tools/metadata/dublin_core_record.rb +22 -4
  18. data/lib/libis/tools/metadata/field_format.rb +49 -9
  19. data/lib/libis/tools/metadata/fix_field.rb +5 -0
  20. data/lib/libis/tools/metadata/mapper.rb +2 -1
  21. data/lib/libis/tools/metadata/mappers/flandrica.rb +8 -1
  22. data/lib/libis/tools/metadata/mappers/kuleuven.rb +6 -2
  23. data/lib/libis/tools/metadata/marc21_record.rb +1 -0
  24. data/lib/libis/tools/metadata/marc_record.rb +31 -12
  25. data/lib/libis/tools/metadata/parser/basic_parser.rb +2 -0
  26. data/lib/libis/tools/metadata/parser/dublin_core_parser.rb +2 -1
  27. data/lib/libis/tools/metadata/parser/marc21_parser.rb +2 -1
  28. data/lib/libis/tools/metadata/parser/marc_format_parser.rb +2 -1
  29. data/lib/libis/tools/metadata/parser/marc_rules.rb +2 -1
  30. data/lib/libis/tools/metadata/parser/marc_select_parser.rb +2 -1
  31. data/lib/libis/tools/metadata/parser/patch.rb +1 -0
  32. data/lib/libis/tools/metadata/parser/subfield_criteria_parser.rb +2 -1
  33. data/lib/libis/tools/metadata/sharepoint_mapping.rb +1 -0
  34. data/lib/libis/tools/metadata/sharepoint_record.rb +2 -0
  35. data/lib/libis/tools/metadata/var_field.rb +8 -0
  36. data/lib/libis/tools/mets_dnx.rb +61 -0
  37. data/lib/libis/tools/mets_file.rb +87 -604
  38. data/lib/libis/tools/mets_objects.rb +534 -0
  39. data/lib/libis/tools/parameter.rb +144 -21
  40. data/lib/libis/tools/thread_safe.rb +31 -0
  41. data/lib/libis/tools/version.rb +1 -1
  42. data/lib/libis/tools/xml_document.rb +18 -24
  43. data/libis-tools.gemspec +6 -2
  44. data/spec/config_spec.rb +3 -4
  45. data/spec/logger_spec.rb +13 -30
  46. data/spec/mets_file_spec.rb +17 -17
  47. metadata +53 -7
@@ -0,0 +1,534 @@
1
+ module Libis
2
+ module Tools
3
+ # noinspection RubyResolve
4
+ class MetsFile
5
+
6
+ # Generic module that provides code shortcuts for the {Representation}, {Div} and {File} classes.
7
+ module IdContainer
8
+ extend ::Libis::Tools::ThreadSafe
9
+
10
+ # Take a hash and set class instance attributes.
11
+ #
12
+ #
13
+ # @param [Hash] hash Hash with <attribute_name>, <attribute_value> pairs.
14
+ def set_from_hash(hash)
15
+ hash.each { |key, value| send "#{key}=", value if respond_to?(key) }
16
+ end
17
+
18
+ # Assigns a unique id to a class instance.
19
+ #
20
+ # A class variable is used to keep track of the id sequence and is incremented as needed. Thread-safe operation.
21
+ def id
22
+ return @id if @id
23
+ self.mutex.synchronize do
24
+ @id = self.class.instance_variable_get('@id') || 1
25
+ self.class.instance_variable_set('@id', @id + 1)
26
+ @id
27
+ end
28
+ end
29
+
30
+ # Convert structure to String. Can be used for debugging to show what is stored.
31
+ def to_s
32
+ "#{self.class}:\n" +
33
+ self.instance_variables.map do |var|
34
+ v = self.instance_variable_get(var)
35
+ v = "#{v.class}-#{v.id}" if v.is_a? IdContainer
36
+ v = v.map do |x|
37
+ x.is_a?(IdContainer) ? "#{x.class}-#{x.id}" : x.to_s
38
+ end.join(',') if v.is_a? Array
39
+ " - #{var.to_s.gsub(/^@/, '')}: #{v}"
40
+ end.join("\n")
41
+ end
42
+
43
+ end
44
+
45
+ # Container class for creating a representation in the METS.
46
+ class Representation
47
+ include IdContainer
48
+
49
+ # The currently allowed attributes on this class. The attributes will typically be used in {DnxSection}s.
50
+ attr_accessor :label, :preservation_type, :usage_type, :representation_code, :entity_type, :access_right_id,
51
+ :user_a, :user_b, :user_c,
52
+ :group_id, :priority, :order,
53
+ :digital_original, :content, :context, :hardware, :carrier, :original_name,
54
+ :preservation_levels, :env_dependencies, :hardware_ids, :software_ids,
55
+ :hardware_infos, :software_infos, :relationship_infos, :environments,
56
+ :dc_record, :source_metadata
57
+
58
+ # The id that will be used in the XML file to reference this representation.
59
+ def xml_id
60
+ "rep#{id}"
61
+ end
62
+
63
+ # This method creates the appropriate {DnxSection}s based on what attributes are filled in.
64
+ def amd
65
+ dnx = {}
66
+ tech_data = []
67
+ # General characteristics
68
+ data = {
69
+ preservationType: preservation_type,
70
+ usageType: usage_type,
71
+ DigitalOriginal: digital_original,
72
+ label: label,
73
+ representationEntityType: entity_type,
74
+ contentType: content,
75
+ contextType: context,
76
+ hardwareUsed: hardware,
77
+ physicalCarrierMedia: carrier,
78
+ deliveryPriority: priority,
79
+ orderingSequence: order,
80
+ RepresentationCode: representation_code,
81
+ RepresentationOriginalName: original_name,
82
+ UserDefinedA: user_a,
83
+ UserDefinedB: user_b,
84
+ UserDefinedC: user_c,
85
+ }.cleanup
86
+ tech_data << GeneralRepCharacteristics.new(data) unless data.empty?
87
+ # Object characteristics
88
+ data = {
89
+ groupID: group_id
90
+ }.cleanup
91
+ tech_data << ObjectCharacteristics.new(data) unless data.empty?
92
+ # Preservation level
93
+ if preservation_levels
94
+ data_list = []
95
+ preservation_levels.each do |preservation_level|
96
+ data = {
97
+ preservationLevelValue: preservation_level[:value],
98
+ preservationLevelRole: preservation_level[:role],
99
+ preservationLevelRationale: preservation_level[:rationale],
100
+ preservationLevelDateAssigned: preservation_level[:date],
101
+ }.cleanup
102
+ data_list << OpenStruct.new(data) unless data.empty?
103
+ end
104
+ tech_data << PreservationLevel.new(array: data_list) unless data_list.empty?
105
+ end
106
+ # Dependencies
107
+ if env_dependencies
108
+ data_list = []
109
+ env_dependencies.each do |dependency|
110
+ data = {
111
+ dependencyName: dependency[:name],
112
+ dependencyIdentifierType1: dependency[:type1],
113
+ dependencyIdentifierValue1: dependency[:value1],
114
+ dependencyIdentifierType2: dependency[:type2],
115
+ dependencyIdentifierValue2: dependency[:value2],
116
+ dependencyIdentifierType3: dependency[:type3],
117
+ dependencyIdentifierValue3: dependency[:value3],
118
+ }.cleanup
119
+ data_list << OpenStruct.new(data) unless data.empty?
120
+ end
121
+ tech_data << EnvironmentDependencies.new(array: data_list) unless data_list.empty?
122
+ end
123
+ # Hardware registry id
124
+ if hardware_ids
125
+ data_list = []
126
+ hardware_ids.each do |id|
127
+ data = {
128
+ registryId: id
129
+ }.cleanup
130
+ data_list << OpenStruct.new(data) unless data.empty?
131
+ end
132
+ tech_data << EnvHardwareRegistry.new(array: data_list) unless data_list.empty?
133
+ end
134
+ # Software registry id
135
+ if software_ids
136
+ data_list = []
137
+ software_ids.each do |id|
138
+ data = {
139
+ registryId: id
140
+ }.cleanup
141
+ data_list << OpenStruct.new(data) unless data.empty?
142
+ end
143
+ tech_data << EnvSoftwareRegistry.new(array: data_list) unless data_list.empty?
144
+ end
145
+ # Hardware
146
+ if hardware_infos
147
+ data_list = []
148
+ hardware_infos.each do |hardware|
149
+ data = {
150
+ hardwareName: hardware[:name],
151
+ hardwareType: hardware[:type],
152
+ hardwareOtherInformation: hardware[:info],
153
+ }.cleanup
154
+ data_list << OpenStruct.new(data) unless data.empty?
155
+ end
156
+ tech_data << EnvironmentHardware.new(array: data_list) unless data_list.empty?
157
+ end
158
+ # Software
159
+ if software_infos
160
+ data_list = []
161
+ software_infos.each do |software|
162
+ data = {
163
+ softwareName: software[:name],
164
+ softwareVersion: software[:version],
165
+ softwareType: software[:type],
166
+ softwareOtherInformation: software[:info],
167
+ softwareDependancy: software[:dependency],
168
+ }.cleanup
169
+ data_list << OpenStruct.new(data) unless data.empty?
170
+ end
171
+ tech_data << EnvironmentSoftware.new(array: data_list) unless data_list.empty?
172
+ end
173
+ # Relationship
174
+ if relationship_infos
175
+ data_list = []
176
+ relationship_infos.each do |relationship|
177
+ data = {
178
+ relationshipType: relationship[:type],
179
+ relationshipSubType: relationship[:subtype],
180
+ relatedObjectIdentifierType1: relationship[:type1],
181
+ relatedObjectIdentifierValue1: relationship[:id1],
182
+ relatedObjectSequence1: relationship[:seq1],
183
+ relatedObjectIdentifierType2: relationship[:type2],
184
+ relatedObjectIdentifierValue2: relationship[:id2],
185
+ relatedObjectSequence2: relationship[:seq2],
186
+ relatedObjectIdentifierType3: relationship[:type3],
187
+ relatedObjectIdentifierValue3: relationship[:id3],
188
+ relatedObjectSequence3: relationship[:seq3],
189
+ }.cleanup
190
+ data_list << OpenStruct.new(data) unless data.empty?
191
+ end
192
+ tech_data << RelationShip.new(array: data_list) unless data_list.empty?
193
+ end
194
+ # Environment
195
+ if environments
196
+ data_list = []
197
+ environments.each do |environment|
198
+ data = {
199
+ environmentCharacteristic: environment[:characteristic],
200
+ environmentPurpose: environment[:purpose],
201
+ environmentNote: environment[:note],
202
+ }.cleanup
203
+ data_list << OpenStruct.new(data) unless data.empty?
204
+ end
205
+ tech_data << Environment.new(array: data_list) unless data_list.empty?
206
+ end
207
+ # Finally assemble technical section
208
+ dnx[:tech] = tech_data unless tech_data.empty?
209
+ # Rights section
210
+ rights_data = []
211
+ data = {
212
+ policyId: access_right_id
213
+ }.cleanup
214
+ rights_data << AccessRightsPolicy.new(data) unless data.empty?
215
+ dnx[:rights] = rights_data unless rights_data.empty?
216
+ # Source metadata
217
+ if source_metadata
218
+ source_metadata.each_with_index do |metadata, i|
219
+ dnx["source-#{metadata[:type].to_s.upcase}-#{i}"] = metadata[:data]
220
+ end
221
+ end
222
+ dnx
223
+ end
224
+
225
+ end
226
+
227
+ # Container class for creating a file in the METS.
228
+ class File
229
+ include IdContainer
230
+
231
+ # The currently allowed attributes on this class. The attributes will typically be used in {DnxSection}s.
232
+ attr_accessor :label, :note, :location, :target_location, :mimetype, :puid, :size, :entity_type,
233
+ :creation_date, :modification_date, :composition_level, :group_id,
234
+ :checksum_MD5, :checksum_SHA1, :checksum_SHA256,:checksum_SHA384,:checksum_SHA512,
235
+ :fixity_type, :fixity_value,
236
+ :preservation_levels, :inhibitors, :env_dependencies, :hardware_ids, :software_ids,
237
+ :signatures, :hardware_infos, :software_infos, :relationship_infos, :environments, :applications,
238
+ :dc_record, :source_metadata, :representation
239
+
240
+ # The id that will be used in the XML file to reference this file.
241
+ def xml_id
242
+ "fid#{id}"
243
+ end
244
+
245
+ # The id that will be used for the group in the XML file.
246
+ def make_group_id
247
+ "grp#{group_id rescue id}"
248
+ end
249
+
250
+ # The file's name as it was originally.
251
+ def orig_name
252
+ ::File.basename(location)
253
+ end
254
+
255
+ # The file's original directory.
256
+ def orig_path
257
+ ::File.dirname(location)
258
+ end
259
+
260
+ # The full path where the file is copied
261
+ def target
262
+ if target_location.nil?
263
+ return "#{xml_id}#{::File.extname(location)}"
264
+ end
265
+ target_location
266
+ end
267
+
268
+ # This method creates the appropriate {DnxSection}s based on what attributes are filled in.
269
+ def amd
270
+ dnx = {}
271
+ tech_data = []
272
+ # General File charateristics
273
+ data = {
274
+ label: label,
275
+ note: note,
276
+ fileCreationDate: creation_date,
277
+ fileModificationDate: modification_date,
278
+ FileEntityType: entity_type,
279
+ compositionLevel: composition_level,
280
+ # fileLocationType: 'FILE',
281
+ # fileLocation: '',
282
+ fileOriginalName: orig_name,
283
+ fileOriginalPath: orig_path,
284
+ fileOriginalID: location,
285
+ fileExtension: ::File.extname(orig_name),
286
+ fileMIMEType: mimetype,
287
+ fileSizeBytes: size,
288
+ formatLibraryId: puid
289
+ }.cleanup
290
+ tech_data << GeneralFileCharacteristics.new(data) unless data.empty?
291
+ # Fixity
292
+ %w'MD5 SHA1 SHA256 SHA384 SHA512'.each do |checksum_type|
293
+ if (checksum = self.send("checksum_#{checksum_type}"))
294
+ data = {
295
+ fixityType: checksum_type,
296
+ fixityValue: checksum,
297
+ }.cleanup
298
+ tech_data << FileFixity.new(data) unless data.empty?
299
+ end
300
+ end
301
+ # Object characteristics
302
+ data = {
303
+ groupID: make_group_id
304
+ }.cleanup
305
+ tech_data << ObjectCharacteristics.new(data) unless data.empty?
306
+ # Preservation level
307
+ if preservation_levels
308
+ data_list = []
309
+ preservation_levels.each do |preservation_level|
310
+ data = {
311
+ preservationLevelValue: preservation_level[:value],
312
+ preservationLevelRole: preservation_level[:role],
313
+ preservationLevelRationale: preservation_level[:rationale],
314
+ preservationLevelDateAssigned: preservation_level[:date],
315
+ }.cleanup
316
+ data_list << OpenStruct.new(data) unless data.empty?
317
+ end
318
+ tech_data << PreservationLevel.new(array: data_list) unless data_list.empty?
319
+ end
320
+ # Inhibitor
321
+ if inhibitors
322
+ data_list = []
323
+ inhibitors.each do |inhibitor|
324
+ data = {
325
+ inhibitorType: inhibitor[:type],
326
+ inhibitorTarget: inhibitor[:target],
327
+ inhibitorKey: inhibitor[:key],
328
+ }.cleanup
329
+ data_list << OpenStruct.new(data) unless data.empty?
330
+ end
331
+ tech_data << Inhibitors.new(array: data_list) unless data_list.empty?
332
+ end
333
+ # Dependencies
334
+ if env_dependencies
335
+ data_list = []
336
+ env_dependencies.each do |dependency|
337
+ data = {
338
+ dependencyName: dependency[:name],
339
+ dependencyIdentifierType1: dependency[:type1],
340
+ dependencyIdentifierValue1: dependency[:value1],
341
+ dependencyIdentifierType2: dependency[:type2],
342
+ dependencyIdentifierValue2: dependency[:value2],
343
+ dependencyIdentifierType3: dependency[:type3],
344
+ dependencyIdentifierValue3: dependency[:value3],
345
+ }.cleanup
346
+ data_list << OpenStruct.new(data) unless data.empty?
347
+ end
348
+ tech_data << EnvironmentDependencies.new(array: data_list) unless data_list.empty?
349
+ end
350
+ # Hardware registry id
351
+ if hardware_ids
352
+ data_list = []
353
+ hardware_ids.each do |id|
354
+ data = {
355
+ registryId: id
356
+ }.cleanup
357
+ data_list << OpenStruct.new(data) unless data.empty?
358
+ end
359
+ tech_data << EnvHardwareRegistry.new(array: data_list) unless data_list.empty?
360
+ end
361
+ # Software registry id
362
+ if software_ids
363
+ data_list = []
364
+ software_ids.each do |id|
365
+ data = {
366
+ registryId: id
367
+ }.cleanup
368
+ data_list << OpenStruct.new(data) unless data.empty?
369
+ end
370
+ tech_data << EnvSoftwareRegistry.new(array: data_list) unless data_list.empty?
371
+ end
372
+ # Singatures
373
+ if signatures
374
+ data_list = []
375
+ signatures.each do |signature|
376
+ data = {
377
+ signatureInformationEncoding: signature[:encoding],
378
+ signer: signature[:signer],
379
+ signatureMethod: signature[:method],
380
+ signatureValue: signature[:value],
381
+ signatureValidationRules: signature[:rules],
382
+ signatureProperties: signature[:properties],
383
+ }.cleanup
384
+ data_list << OpenStruct.new(data) unless data.empty?
385
+ end
386
+ tech_data << SignatureInformation.new(array: data_list) unless data_list.empty?
387
+ end
388
+ # Hardware
389
+ if hardware_infos
390
+ data_list = []
391
+ hardware_infos.each do |hardware|
392
+ data = {
393
+ hardwareName: hardware[:name],
394
+ hardwareType: hardware[:type],
395
+ hardwareOtherInformation: hardware[:info],
396
+ }.cleanup
397
+ data_list << OpenStruct.new(data) unless data.empty?
398
+ end
399
+ tech_data << EnvironmentHardware.new(array: data_list) unless data_list.empty?
400
+ end
401
+ # Software
402
+ if software_infos
403
+ data_list = []
404
+ software_infos.each do |software|
405
+ data = {
406
+ softwareName: software[:name],
407
+ softwareVersion: software[:version],
408
+ softwareType: software[:type],
409
+ softwareOtherInformation: software[:info],
410
+ softwareDependancy: software[:dependency],
411
+ }.cleanup
412
+ data_list << OpenStruct.new(data) unless data.empty?
413
+ end
414
+ tech_data << EnvironmentSoftware.new(array: data_list) unless data_list.empty?
415
+ end
416
+ # Relationship
417
+ if relationship_infos
418
+ data_list = []
419
+ relationship_infos.each do |relationship|
420
+ data = {
421
+ relationshipType: relationship[:type],
422
+ relationshipSubType: relationship[:subtype],
423
+ relatedObjectIdentifierType1: relationship[:type1],
424
+ relatedObjectIdentifierValue1: relationship[:id1],
425
+ relatedObjectSequence1: relationship[:seq1],
426
+ relatedObjectIdentifierType2: relationship[:type2],
427
+ relatedObjectIdentifierValue2: relationship[:id2],
428
+ relatedObjectSequence2: relationship[:seq2],
429
+ relatedObjectIdentifierType3: relationship[:type3],
430
+ relatedObjectIdentifierValue3: relationship[:id3],
431
+ relatedObjectSequence3: relationship[:seq3],
432
+ }.cleanup
433
+ data_list << OpenStruct.new(data) unless data.empty?
434
+ end
435
+ tech_data << RelationShip.new(array: data_list) unless data_list.empty?
436
+ end
437
+ # Environment
438
+ if environments
439
+ data_list = []
440
+ environments.each do |environment|
441
+ data = {
442
+ environmentCharacteristic: environment[:characteristic],
443
+ environmentPurpose: environment[:purpose],
444
+ environmentNote: environment[:note],
445
+ }.cleanup
446
+ data_list << OpenStruct.new(data) unless data.empty?
447
+ end
448
+ tech_data << Environment.new(array: data_list) unless data_list.empty?
449
+ end
450
+ # Application
451
+ if applications
452
+ data_list = []
453
+ applications.each do |application|
454
+ data = {
455
+ creatingApplicationName: application[:name],
456
+ creatingApplicationVersion: application[:version],
457
+ dateCreatedByApplication: application[:date],
458
+ }.cleanup
459
+ data_list << OpenStruct.new(data) unless data.empty?
460
+ end
461
+ tech_data << CreatingApplication.new(array: data_list) unless data_list.empty?
462
+ end
463
+ # Finally assemble technical section
464
+ dnx[:tech] = tech_data unless tech_data.empty?
465
+ dnx
466
+ end
467
+
468
+ end
469
+
470
+ # Container class for creating a division in the METS.
471
+ class Div
472
+ include IdContainer
473
+
474
+ attr_accessor :label
475
+
476
+ # The id that will be used in the XML file to reference this division.
477
+ def xml_id
478
+ "div-#{id}"
479
+ end
480
+
481
+ # All items stored in the current division
482
+ def children
483
+ files + divs
484
+ end
485
+
486
+ # All file items stored in the current division
487
+ def files
488
+ mutex.synchronise do
489
+ @files ||= Array.new
490
+ end
491
+ end
492
+
493
+ # All division items stored in the current division
494
+ def divs
495
+ mutex.synchronize do
496
+ @divs ||= Array.new
497
+ end
498
+ end
499
+
500
+ # Add an item ({File} or {Div}) to the current division
501
+ def <<(obj)
502
+ mutex.synchronize do
503
+ case obj
504
+ when File
505
+ files << obj
506
+ when Div
507
+ divs << obj
508
+ else
509
+ raise RuntimeError, "Child object type not supported: #{obj.class}"
510
+ end
511
+ end
512
+ end
513
+
514
+ end
515
+
516
+ # Container class for creating a structmap in the METS.
517
+ class Map
518
+ include IdContainer
519
+
520
+ # The representation this structmap is for
521
+ attr_accessor :representation
522
+ # The top division in the structmap
523
+ attr_accessor :div
524
+
525
+ # The id that will be used in the XML file to reference this structmap.
526
+ def xml_id
527
+ "smap-#{id}"
528
+ end
529
+
530
+ end
531
+
532
+ end
533
+ end
534
+ end