libis-tools 0.9.20 → 0.9.21

Sign up to get free protection for your applications and to get access to all the features.
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