libis-tools 0.9.6 → 0.9.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abe45e9ce73f0da4087c1cbfe554a9d8d62680c9
4
- data.tar.gz: 845d2bbee0b78df26160501a4a15bb532ac62e1a
3
+ metadata.gz: 1e717837dc7e2b3f0549cf1b6abf6ca1240f1344
4
+ data.tar.gz: 8310be003731268e303dd930279638d61b6a5edf
5
5
  SHA512:
6
- metadata.gz: afa5e9bfd7b2ea4c908046159b718e70868fa3160559f5ebe350c5e81bf7d601dae1f7aa168cea4f914249e561fbdb627e3d67a9db19ce4b3e3fbd6e104ab4a0
7
- data.tar.gz: d1f52ac47989cc3bfe353d14b15ef581c3121826a06fb1c9e240364c03a291a5ec64c66da656c9bc1c4de0825f2593f92053cdac6bdee58e7e565a75bb8c18ca
6
+ metadata.gz: 8fce8638e6fc55803704fda53975fedce79a0ed127c2c8e2cf7dd8b989e6d87af001a56205d1091865764c5fa99dc864dd473dd23df637b24e305f29d49d8df0
7
+ data.tar.gz: 8b8f8b6b0af625240709d68fd48ea75003c7fe8dbf460cdaecd4d67d6f8a2a19a9aad07daae54ab9602237769ec118807eeb6d92107b34c64d48fcb9af15ad48
@@ -34,8 +34,7 @@ module Libis
34
34
  #
35
35
  # @param [String,Hash] file_or_hash optional String or Hash argument to initialize the data.
36
36
  def initialize(file_or_hash = nil, opt = {})
37
- super({}, opt)
38
- self << file_or_hash
37
+ super _file_to_hash(file_or_hash), opt
39
38
  end
40
39
 
41
40
  # Load configuration parameters from a YAML file or Hash.
@@ -48,7 +47,21 @@ module Libis
48
47
  #
49
48
  # @param [String,Hash] file_or_hash optional String or Hash argument to initialize the data.
50
49
  def <<(file_or_hash)
51
- return self if file_or_hash.nil? || (file_or_hash.respond_to?(:empty?) && file_or_hash.empty?)
50
+ _file_to_hash(file_or_hash).each { |key, value| self[key] = value }
51
+ self
52
+ end
53
+
54
+ # Save configuration parameters in a YAML file.
55
+ #
56
+ # @param [String] file path of the YAML file to save the configuration to.
57
+ def >>(file)
58
+ File.open(file, 'w') { |f| f.write to_hash.to_yaml }
59
+ end
60
+
61
+ protected
62
+
63
+ def _file_to_hash(file_or_hash)
64
+ return {} if file_or_hash.nil? || (file_or_hash.respond_to?(:empty?) && file_or_hash.empty?)
52
65
  hash = case file_or_hash
53
66
  when Hash
54
67
  yield file_or_hash if block_given?
@@ -61,16 +74,8 @@ module Libis
61
74
  else
62
75
  {}
63
76
  end
64
- return self unless hash.is_a? Hash
65
- hash.each { |key, value| self[key] = value }
66
- self
67
- end
68
-
69
- # Save configuration parameters in a YAML file.
70
- #
71
- # @param [String] file path of the YAML file to save the configuration to.
72
- def >>(file)
73
- File.open(file, 'w') { |f| f.write to_hash.to_yaml }
77
+ hash = {} unless hash.is_a? Hash
78
+ hash
74
79
  end
75
80
 
76
81
  end
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+ require 'nori'
3
+ require 'libis/tools/assert'
4
+
5
+ module Libis
6
+ module Tools
7
+ module Metadata
8
+
9
+ class DublinCoreRecord < Libis::Tools::XmlDocument
10
+
11
+ DC_ELEMENTS = %w'contributor coverage creator date description format identifier language' +
12
+ %w'publisher relation rights source subject title type'
13
+ DCTERMS_ELEMENTS = %w'abstract accessRights accrualMethod accrualPeriodicity accrualPolicy alternative' +
14
+ %w'audience available bibliographicCitation conformsTo contributor coverage created creator date' +
15
+ %w'dateAccepted dateCopyrighted dateSubmitted description educationLevel extent format hasFormat' +
16
+ %w'hasPart hasVersion identifier instructionalMethod isFormatOf isPartOf isReferencedBy isReplacedBy' +
17
+ %w'isRequiredBy issued isVersionOf language license mediator medium modified provenance publisher' +
18
+ %w'references relation replaces requires rights rightsHolder source spatial subject tableOfContents' +
19
+ %w'temporal title type valid'
20
+
21
+ def initialize(doc = nil)
22
+ super()
23
+ xml_doc = case doc
24
+ when ::Libis::Tools::XmlDocument
25
+ doc
26
+ when String
27
+ # noinspection RubyResolve
28
+ File.exist?(doc) ? Libis::Tools::XmlDocument.load(doc) : Libis::Tools::XmlDocument.parse(doc)
29
+ when IO
30
+ Libis::Tools::XmlDocument.parse(doc.read)
31
+ when Hash
32
+ Libis::Tools::XmlDocument.from_hash(doc)
33
+ when NilClass
34
+ Libis::Tools::XmlDocument.new.build do |xml|
35
+ xml.record('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
36
+ 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
37
+ 'xmlns:dcterms' => 'http://purl.org/dc/terms/') {
38
+ yield xml
39
+ }
40
+ end
41
+ else
42
+ raise ArgumentError, "Invalid argument: #{doc.inspect}"
43
+ end
44
+ @document = xml_doc.document if xml_doc
45
+ end
46
+
47
+ def all
48
+ @all_records ||= get_all_records
49
+ end
50
+
51
+ def xpath(path)
52
+ m = /^([\/.]*\/)?(dc(terms)?:)?(.*)/.match(path.to_s)
53
+ return [] unless m[4]
54
+ path = (m[1] || '') + ('dc:' || m[2]) + m[4]
55
+ raise ArgumentError, 'XML document not valid.' if self.invalid?
56
+ @document.xpath(path.to_s)
57
+ end
58
+
59
+ def add_node(name, value = nil, parent = nil, attributes = {})
60
+ ns, tag = get_namespace(name.to_s)
61
+ (attributes[:namespaces] ||= {})[:node_ns] ||= ns if ns
62
+ super tag, value, parent, attributes
63
+ end
64
+
65
+ protected
66
+
67
+ def get_nodes(tag, parent = nil)
68
+ parent ||= root
69
+ m = /^([\/\.]*\/)?(dc(?:terms)?:)?(.*)/.match(tag.to_s)
70
+ return [] unless m[3]
71
+ path = (m[1] || '') + ('dc:' || m[2]) + m[3]
72
+ parent.xpath(path)
73
+ end
74
+
75
+ def get_namespace(tag)
76
+ m = /^((dc)?(terms)?(?:_|:)?)?([a-zA-Z_][-_.0-9a-zA-Z]+)(.*)/.match tag
77
+ ns = if m[1].nil?
78
+ if DC_ELEMENTS.include?(m[4])
79
+ :dc
80
+ else
81
+ DCTERMS_ELEMENTS.include?(m[4]) ? :dcterms : nil
82
+ end
83
+ elsif m[3].nil?
84
+ :dc
85
+ else
86
+ :dcterms
87
+ end
88
+ [ns, "#{m[4]}#{m[5]}"]
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,81 @@
1
+ # coding: utf-8
2
+
3
+ module Libis
4
+ module Tools
5
+ module Metadata
6
+
7
+ class FieldSpec
8
+
9
+ attr_accessor :parts
10
+ attr_accessor :prefix
11
+ attr_accessor :join
12
+ attr_accessor :postfix
13
+
14
+ def initialize(*parts)
15
+ @parts = []
16
+ self[*parts]
17
+ end
18
+
19
+ def add_options(options = {})
20
+ if options[:fix]
21
+ if options[:fix].size == 2
22
+ @prefix, @postfix = options[:fix].split('')
23
+ else
24
+ @prefix, @postfix = options[:fix].split('|')
25
+ end
26
+ end
27
+ @join = options[:join] if options[:join]
28
+ @prefix = FieldSpec::from(options[:prefix]) if options[:prefix]
29
+ @postfix = FieldSpec::from(options[:postfix]) if options[:postfix]
30
+ self
31
+ end
32
+
33
+ def add_default_options(options = {})
34
+ options.delete(:prefix) if @prefix
35
+ options.delete(:postfix) if @postfix
36
+ options.delete(:fix) if @prefix or @postfix
37
+ options.delete(:join) if @join
38
+ add_options options
39
+ end
40
+
41
+ def [](*parts)
42
+ options = parts.last.is_a?(Hash) ? parts.pop : {}
43
+ parts.each { |x| add x }
44
+ x = options.delete(:parts)
45
+ add x if x
46
+ add_options options
47
+ end
48
+
49
+ def self.from(*h)
50
+ FieldSpec.new(*h)
51
+ end
52
+
53
+ def to_s
54
+ @parts.delete_if { |x|
55
+ x.nil? or
56
+ (x.is_a? String and x.empty?) or
57
+ (x.is_a? FieldSpec and x.to_s.empty?)
58
+ }
59
+ result = @parts.join(@join)
60
+ unless result.empty?
61
+ result = (@prefix || '').to_s + result + (@postfix || '').to_s
62
+ end
63
+ result
64
+ end
65
+
66
+ def add(part)
67
+ case part
68
+ when Hash
69
+ @parts << FieldSpec::from(part)
70
+ when Array
71
+ part.each { |x| add x }
72
+ else
73
+ @parts << part
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ module Libis
4
+ module Tools
5
+ module Metadata
6
+
7
+ class FixField
8
+
9
+ attr_reader :tag
10
+ attr_reader :datas
11
+
12
+ def initialize(tag, datas)
13
+ @tag = tag
14
+ @datas = datas || ''
15
+ end
16
+
17
+ def dump
18
+ "#{@tag}:'#{@datas}'\n"
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+
3
+ require 'cgi'
4
+
5
+ require_relative 'marc_record'
6
+
7
+ module Libis
8
+ module Tools
9
+ module Metadata
10
+
11
+ class Marc21Record < Libis::Tools::Metadata::MarcRecord
12
+
13
+ private
14
+
15
+ def get_all_records
16
+
17
+ @all_records = Hash.new { |h, k| h[k] = [] }
18
+
19
+ @node.xpath('.//leader').each { |f|
20
+ @all_records['LDR'] << FixField.new('LDR', f.content)
21
+ }
22
+
23
+ @node.xpath('.//controlfield').each { |f|
24
+ tag = f['tag']
25
+ tag = '%03d' % tag.to_i if tag.size < 3
26
+ @all_records[tag] << FixField.new(tag, CGI::escapeHTML(f.content))
27
+ }
28
+
29
+ @node.xpath('.//datafield').each { |v|
30
+
31
+ tag = v['tag']
32
+ tag = '%03d' % tag.to_i if tag.size < 3
33
+
34
+ subfields = Hash.new { |h, k| h[k] = [] }
35
+ v.xpath('.//subfield').each { |s| subfields[s['code']] << CGI::escapeHTML(s.content) }
36
+
37
+ @all_records[tag] << VarField.new(tag, v['ind1'].to_s, v['ind2'].to_s, subfields)
38
+
39
+ }
40
+
41
+ @all_records
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,209 @@
1
+ # coding: utf-8
2
+
3
+ require 'set'
4
+ require 'cgi'
5
+
6
+ require 'libis/tools/xml_document'
7
+ require 'libis/tools/assert'
8
+
9
+ require_relative 'fix_field'
10
+ require_relative 'var_field'
11
+ require_relative 'field_spec'
12
+
13
+ module Libis
14
+ module Tools
15
+ module Metadata
16
+
17
+ # noinspection RubyTooManyMethodsInspection
18
+ class MarcRecord
19
+
20
+ def initialize(xml_node)
21
+ @node = xml_node
22
+ end
23
+
24
+ def to_raw
25
+ @node
26
+ end
27
+
28
+ def all
29
+ # noinspection RubyResolve
30
+ @all_records ||= get_all_records
31
+ end
32
+
33
+ def each
34
+ all.each do |k, v|
35
+ yield k, v
36
+ end
37
+ end
38
+
39
+ def all_tags(tag, subfields = '')
40
+ tag_, ind1, ind2 = tag =~ /^\d{3}/ ? [tag[0..2], tag[3], tag[4]] : [tag, nil, nil]
41
+ result = get_records(tag_, ind1, ind2, subfields)
42
+ return result unless block_given?
43
+ result.map { |record| yield record }
44
+ result.size > 0
45
+ end
46
+
47
+ def first_tag(t, subfields = '')
48
+ result = all_tags(t, subfields).first
49
+ return result unless block_given?
50
+ return false unless result
51
+ yield result
52
+ true
53
+ end
54
+
55
+ def each_tag(t, s = '')
56
+ all_tags(t, s).each do |record|
57
+ yield record
58
+ end
59
+ end
60
+
61
+ def all_fields(t, s)
62
+ r = all_tags(t, s).collect { |tag| tag.fields_array(s) }.flatten.compact
63
+ return r unless block_given?
64
+ r.map { |field| yield field }
65
+ r.size > 0
66
+ end
67
+
68
+ def first_field(t, s)
69
+ result = all_fields(t, s).first
70
+ return result unless block_given?
71
+ return false unless result
72
+ yield result
73
+ true
74
+ end
75
+
76
+
77
+ def each_field(t, s)
78
+ all_fields(t, s).each do |field|
79
+ yield field
80
+ end
81
+ end
82
+
83
+ def marc_dump
84
+ all.values.flatten.each_with_object([]) { |record, m| m << record.dump }.join
85
+ end
86
+
87
+ def save(filename)
88
+
89
+ doc = ::Libis::Tools::XmlDocument.new
90
+ doc.root = @node
91
+
92
+ return doc unless filename
93
+
94
+ doc.save filename, save_with: (::Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
95
+ ::Nokogiri::XML::Node::SaveOptions::AS_XML |
96
+ ::Nokogiri::XML::Node::SaveOptions::FORMAT
97
+ )
98
+
99
+ end
100
+
101
+ def self.load(filename)
102
+
103
+ doc = ::Libis::Tools::XmlDocument.open(filename)
104
+ self.new(doc.root)
105
+
106
+ end
107
+
108
+ def self.read(io)
109
+ io = StringIO.new(io) if io.is_a? String
110
+ doc = ::Libis::Tools::XmlDocument.parse(io)
111
+ self.new(doc.root)
112
+
113
+ end
114
+
115
+ def to_aseq
116
+ record = ''
117
+ doc_number = tag('001').datas
118
+
119
+ all.select { |t| t.is_a? FixField }.each { |t| record += "#{format('%09s', doc_number)} #{t.tag} L #{t.datas}\n" }
120
+ all.select { |t| t.is_a? VarField }.each { |t|
121
+ record += "#{format('%09s', doc_number)} #{t.tag}#{t.ind1}#{t.ind2} L "
122
+ t.keys.each { |k|
123
+ t.field_array(k).each { |f|
124
+ record += "$$#{k}#{CGI::unescapeHTML(f)}"
125
+ }
126
+ }
127
+ record += "\n"
128
+ }
129
+
130
+ record
131
+ end
132
+
133
+ protected
134
+
135
+ def element(*parts)
136
+ opts = options parts
137
+ field_spec(opts, *parts)
138
+ end
139
+
140
+ def list_s(*parts)
141
+ opts = options parts, join: ' '
142
+ field_spec(opts, *parts)
143
+ end
144
+
145
+ def list_c(*parts)
146
+ opts = options parts, join: ', '
147
+ field_spec(opts, *parts)
148
+ end
149
+
150
+ def list_d(*parts)
151
+ opts = options parts, join: ' - '
152
+ field_spec(opts, *parts)
153
+ end
154
+
155
+ def repeat(*parts)
156
+ opts = options parts, join: '; '
157
+ field_spec(opts, *parts)
158
+ end
159
+
160
+ def opt_r(*parts)
161
+ opts = options parts, fix: '()'
162
+ field_spec(opts, *parts)
163
+ end
164
+
165
+ def opt_s(*parts)
166
+ opts = options parts, fix: '[]'
167
+ field_spec(opts, *parts)
168
+ end
169
+
170
+ def odis_link(group, id, label)
171
+ "http://www.odis.be/lnk/#{group.downcase[0, 2]}_#{id}\##{label}"
172
+ end
173
+
174
+ private
175
+
176
+ def options(args, default = {})
177
+ default.merge(args.last.is_a?(::Hash) ? args.pop : {})
178
+ end
179
+
180
+ def field_spec(default_options, *parts)
181
+ FieldSpec.new(*parts).add_default_options(default_options).to_s
182
+ end
183
+
184
+ def get_records(tag, ind1 = '', ind2 = '', subfields = '')
185
+
186
+ ind1 ||= ''
187
+ ind2 ||= ''
188
+ subfields ||= ''
189
+
190
+ ind1.tr!('_', ' ')
191
+ ind1.tr!('#', '')
192
+
193
+ ind2.tr!('_', ' ')
194
+ ind2.tr!('#', '')
195
+
196
+ all[tag].select do |v|
197
+ v.is_a?(FixField) ||
198
+ ((ind1.empty? or v.ind1 == ind1) &&
199
+ (ind2.empty? or v.ind2 == ind2) &&
200
+ v.match_fieldspec?(subfields)
201
+ )
202
+ end
203
+
204
+ end
205
+
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,224 @@
1
+ # coding: utf-8
2
+
3
+ require 'libis/tools/assert'
4
+
5
+ module Libis
6
+ module Tools
7
+ module Metadata
8
+
9
+ class VarField
10
+
11
+ attr_reader :tag
12
+ attr_reader :ind1
13
+ attr_reader :ind2
14
+ attr_reader :subfield
15
+
16
+ def initialize(tag, ind1, ind2, subfield = {})
17
+ @tag = tag
18
+ @ind1 = ind1 || ' '
19
+ @ind2 = ind2 || ' '
20
+ @subfield = subfield || {}
21
+ end
22
+
23
+ # dump the contents
24
+ #
25
+ # @return [String] debug output to inspect the contents of the VarField
26
+ def dump
27
+ output = "#{@tag}:#{@ind1}:#{@ind2}:\n"
28
+ @subfield.each { |s, t| output += "\t#{s}:#{t}\n" }
29
+ output
30
+ end
31
+
32
+ # dump the contents
33
+ #
34
+ # @return [String] debug output to inspect the contents of the VarField - Single line version
35
+ def dump_line
36
+ output = "#{@tag}:#{@ind1}:#{@ind2}:"
37
+ @subfield.each { |s, t| output += "$#{s}#{t}" }
38
+ output
39
+ end
40
+
41
+ # list the subfield codes
42
+ #
43
+ # @return [Array] a list of all subfield codes
44
+ def keys
45
+ @subfield.keys
46
+ end
47
+
48
+ # get the first (or only) subfield value for the given code
49
+ #
50
+ # @return [String] the first or only entry of a subfield or nil if not present
51
+ # @param s [Character] the subfield code
52
+ def field(s)
53
+ field_array(s).first
54
+ end
55
+
56
+ # get a list of all subfield values for a given code
57
+ #
58
+ # @return [Array] all the entries of a repeatable subfield
59
+ # @param s [Character] the subfield code
60
+ def field_array(s)
61
+ assert(s.is_a?(String) && (s =~ /^[\da-z]$/) == 0, 'method expects a lower case alphanumerical char')
62
+ @subfield.has_key?(s) ? @subfield[s].dup : []
63
+ end
64
+
65
+ # get a list of the first subfield value for all the codes in the given string
66
+ #
67
+ # @return [Array] list of the first or only entries of all subfield codes in the input string
68
+ # @param s [String] subfield code specification (see match_fieldspec?)
69
+ #
70
+ # The subfield codes are cleaned and sorted first (see fieldspec_to_sorted_array)
71
+ def fields(s)
72
+ assert(s.is_a?(String), 'method expects a string')
73
+ return [] unless (match_array = match_fieldspec?(s))
74
+ fieldspec_to_array(match_array.join(' ')).collect { |i| send(:field, i) }.flatten.compact
75
+ end
76
+
77
+ # get a list of all the subfield values for all the codes in the given string
78
+ #
79
+ # @return [Array] list of the all the entries of all subfield codes in the input string
80
+ # @param s [String] subfield code specification (see match_fieldspec?)
81
+ #
82
+ # The subfield codes are cleaned and sorted first (see fieldspec_to_sorted_array)
83
+
84
+ def fields_array(s)
85
+ assert(s.is_a?(String), 'method expects a string')
86
+ return [] unless (match_array = match_fieldspec?(s))
87
+ fieldspec_to_array(match_array.join(' ')).collect { |i| send(:field_array, i) }.flatten.compact
88
+ end
89
+
90
+ # check if the current VarField matches the given field specification.
91
+ #
92
+ # @return [String] The matching part(s) of the specification or nil if no match
93
+ # @param fieldspec [String] field specification: sequence of alternative set of subfield codes that should-shouldn't be present
94
+ #
95
+ # The fieldspec consists of groups of characters. At least one of these groups should match for the test to succeed
96
+ # Within the group sets of codes may be divided by a hyphen (-). The first set of codes must all be present;
97
+ # the second set of codes must all <b>not</b> be present. Either set may be empty.
98
+ #
99
+ # Examples:
100
+ # 'ab' matches '$a...$b...' => ['ab']
101
+ # '$a...$b...$c...' => ['ab']
102
+ # but not '$a...' => nil # ($b missing)
103
+ # '$b...' => nil # ($a missing)
104
+ # 'a b' matches '$a...' => ['a']
105
+ # '$b...' => ['b']
106
+ # '$a...$b...' => ['a', 'b']
107
+ # '$a...$b...$c...' => ['a', 'b']
108
+ # but not '$c...' => nil # ($a or $b must be present)
109
+ # 'abc-d' matches '$a..,$b...$c...' => ['abc-d']
110
+ # '$a..,$b...$c...$e...' => ['abc-d']
111
+ # but not '$a...$b...$e...' => nil # ($c missing)
112
+ # '$a...$b...$c...$d...' => nil # ($d should not be present)
113
+ # 'a-b b-a' matches '$a...' => ['a-b']
114
+ # '$a...$c...' => ['a-b']
115
+ # '$b...' => ['b-a']
116
+ # '$b...$c...' => ['b-a']
117
+ # but not '$a...$b...' => nil
118
+ # 'a-b c-d' matches '$a...' => ['a-b']
119
+ # '$a...$c...' => ['a-b', 'c-d']
120
+ # '$a...$b...$c...' => ['c-d']
121
+ # '$b...$c...' => ['c-d']
122
+ # but not '$a...$b...' => nil
123
+ # '$c...$d...' => nil
124
+ # '$b...$c...$d...' => nil
125
+ # '$a...$b...$c...$d...' => nil
126
+ def match_fieldspec?(fieldspec)
127
+ return [] if fieldspec.empty?
128
+ result = fieldspec.split.collect { |fs|
129
+ fa = fs.split '-'
130
+ assert(fa.size <= 2, 'more than one "-" is not allowed in a fieldspec')
131
+ must_match = (fa[0] || '').split ''
132
+ must_not_match = (fa[1] || '').split ''
133
+ next unless (must_match == (must_match & keys)) && (must_not_match & keys).empty?
134
+ fs
135
+ }.compact
136
+ return nil if result.empty?
137
+ result
138
+ end
139
+
140
+ private
141
+
142
+ # @return [Array] cleaned up version of the input string
143
+ # @param fieldspec [String] subfield code specification
144
+ # cleans the subfield code specification and splits it into an array of characters
145
+ # Duplicates will be removed from the array and the order will be untouched.
146
+ def fieldspec_to_array(fieldspec)
147
+
148
+ # note that we remove the '-xxx' part as it is only required for matching
149
+ fieldspec.gsub(/ |-\w*/, '').split('').uniq
150
+ end
151
+
152
+ def sort_helper(x)
153
+ # make sure that everything below 'A' is higher than 'z'
154
+ # note that this only works for numbers, but that is fine in our case.
155
+ x < 'A' ? (x.to_i + 123).chr : x
156
+ end
157
+
158
+ # implementation for methods for retrieving subfield values
159
+ #
160
+ # The methods start with a single character: the operation
161
+ # 'f' for retrieving only the first occurence of the subfield
162
+ # 'a' for retrieving all the subfield values for each of the given subfields
163
+ # if omitted, 'f' is assumed
164
+ #
165
+ # Then a '_' acts as a subdivider between the operation and the subfield(s). It must always be present, even
166
+ # if the operation is omitted.
167
+ #
168
+ # The last past is a sequence of subfield codes that should be used for selecting the values. The order in which the
169
+ # subfields are listed is respected in the resulting array of values.
170
+ #
171
+ # Examples:
172
+ #
173
+ # t = VarField.new('100', '', '',
174
+ # { 'a' => %w'Name NickName',
175
+ # 'b' => %w'LastName MaidenName',
176
+ # 'c' => %w'eMail',
177
+ # '1' => %w'Age',
178
+ # '9' => %w'Score'})
179
+ #
180
+ # # >> 100##$aName$aNickName$bLastName$bMaidenName$ceMail$1Age$9Score <<
181
+ #
182
+ # t._1ab => ['Age', 'Name', 'LastName']
183
+ # # equivalent to: t.f_1av or t.fields('1ab')
184
+ #
185
+ # t.a_9ab => ['Score', 'Name', 'NickName', 'LastName', 'MaidenName']
186
+ # # equivalent to: t.fields_array('9ab')
187
+ #
188
+ # Note that it is not possible to use a fieldspec for the sequence of subfield codes. Spaces and '-' are not allowed
189
+ # in method calls. If you want this, use the #field(s) and #field(s)_array methods.
190
+ #
191
+ def method_missing(name, *args)
192
+ operation, subfields = name.to_s.split('_')
193
+ assert(subfields.size > 0, 'need to specify at least one subfield')
194
+ operation = 'f' if operation.empty?
195
+ # convert subfield list to fieldspec
196
+ subfields = subfields.split('').join(' ')
197
+ case operation
198
+ when 'f'
199
+ if subfields.size > 1
200
+ operation = :fields
201
+ else
202
+ operation = :field
203
+ end
204
+ when 'a'
205
+ if subfields.size > 1
206
+ operation = :fields_array
207
+ else
208
+ operation = :field_array
209
+ end
210
+ else
211
+ throw "Unknown method invocation: '#{name}' with: #{args}"
212
+ end
213
+ send(operation, subfields)
214
+ end
215
+
216
+ def to_ary
217
+ nil
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+ end
224
+ end
@@ -1,5 +1,5 @@
1
1
  module Libis
2
2
  module Tools
3
- VERSION = '0.9.6'
3
+ VERSION = '0.9.7'
4
4
  end
5
5
  end
@@ -280,13 +280,19 @@ module Libis
280
280
  # </jkr:books>
281
281
  # </patron>
282
282
  #
283
- # @param [String] name tag for the new node
284
- # @param [String] value optional content for new node; empty if nil
285
- # @param [Node] parent optional parent node for new node; root if nil; xml document if root is not defined
286
- # @param [Hash] attributes a Hash containing tag-value pairs for each attribute; the special key ':namespaces'
287
- # contains a Hash of namespace definitions as in {#add_namespaces}
283
+ # @param [Array] args arguments being:
284
+ # - tag for the new node
285
+ # - optional content for new node; empty if nil or not present
286
+ # - optional parent node for new node; root if nil or not present; xml document if root is not defined
287
+ # - a Hash containing tag-value pairs for each attribute; the special key ':namespaces'
288
+ # contains a Hash of namespace definitions as in {#add_namespaces}
288
289
  # @return [Nokogiri::XML::Node] the new node
289
- def add_node(name, value = nil, parent = nil, attributes = {})
290
+ def add_node(*args)
291
+ attributes = {}
292
+ attributes = args.pop if args.last.is_a? Hash
293
+ name, value, parent = *args
294
+
295
+ return nil if name.nil?
290
296
 
291
297
  node = Nokogiri::XML::Node.new name.to_s, @document
292
298
  node.content = value
@@ -386,7 +392,7 @@ module Libis
386
392
  end
387
393
 
388
394
  node.namespace_scopes.each do |ns|
389
- node.namespace = ns if ns.prefix == node_ns
395
+ node.namespace = ns if ns.prefix == node_ns.to_s
390
396
  end if node_ns
391
397
 
392
398
  node.default_namespace = default_ns if default_ns
@@ -430,12 +436,24 @@ module Libis
430
436
  # xml_doc.value('//email') # => "harry.potter@hogwarts.edu"
431
437
  #
432
438
  # @param [String] path the name or XPath term to search the node(s)
439
+ # @param [Node] parent parent node; document if nil
433
440
  # @return [String] content or nil if not found
434
- def value(path)
435
- xpath(path).first.content rescue nil
441
+ def value(path, parent = nil)
442
+ parent ||= document
443
+ parent.xpath(path).first.content rescue nil
436
444
  end
437
445
 
438
- alias_method :[], :value
446
+ # Return the content of the first element found.
447
+ #
448
+ # Example:
449
+ #
450
+ # xml_doc['email'] # => "harry.potter@hogwarts.edu"
451
+ #
452
+ # @param [String] path the name or XPath term to search the node(s)
453
+ # @return [String] content or nil if not found
454
+ def [](path)
455
+ xpath(path).first.content rescue nil
456
+ end
439
457
 
440
458
  # Return the content of all elements found.
441
459
  # Example:
@@ -548,8 +566,6 @@ module Libis
548
566
  node
549
567
  end
550
568
 
551
- protected
552
-
553
569
  # Get the first node matching the tag. The node will be seached with XPath search term = "//#{tag}".
554
570
  #
555
571
  # @param [String] tag XML tag to look for; XPath syntax is allowed
data/lib/libis/tools.rb CHANGED
@@ -4,7 +4,9 @@ module Libis
4
4
  autoload :Checksum, 'libis/tools/checksum'
5
5
  autoload :Command, 'libis/tools/command'
6
6
  autoload :Config, 'libis/tools/config'
7
+ autoload :ConfigFile, 'libis/tools/config_file'
7
8
  autoload :DCRecord, 'libis/tools/dc_record'
9
+ autoload :DeepStruct, 'libis/tools/deep_struct'
8
10
  autoload :Logger, 'libis/tools/logger'
9
11
  autoload :MetsFile, 'libis/tools/mets_file'
10
12
  autoload :Parameter, 'libis/tools/parameter'
@@ -30,6 +30,45 @@ describe ::Libis::Tools::ConfigFile do
30
30
  expect(subject.to_hash).to eq hash
31
31
  end
32
32
 
33
+ it 'loads a hash' do
34
+ subject << hash
35
+ expect(subject.to_hash).to eq hash
36
+ end
37
+
38
+ it 'allows to change sub-hash' do
39
+ subject << hash
40
+ # noinspection RubyResolve
41
+ subject.b.v = 1
42
+ hash[:b]['v'] = 1
43
+ expect(subject.to_hash).to eq hash
44
+ end
45
+
46
+ it 'allows to change hash in array' do
47
+ subject << hash
48
+ # noinspection RubyResolve
49
+ subject.c[0][0].a[0].v = 1
50
+ hash[:c][0][0][:a][0]['v'] = 1
51
+ expect(subject.to_hash).to eq hash
52
+ end
53
+
54
+ end
55
+
56
+ context 'initialization with hash' do
57
+ subject { ::Libis::Tools::ConfigFile.new hash }
58
+
59
+ it 'has hash' do
60
+ expect(subject.to_hash).to eq hash
61
+ end
62
+
63
+ end
64
+
65
+ context 'initialization with file' do
66
+ subject { ::Libis::Tools::ConfigFile.new test_file }
67
+
68
+ it 'has hash' do
69
+ expect(subject.to_hash).to eq hash
70
+ end
71
+
33
72
  end
34
73
 
35
74
  end
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+ require_relative '../spec_helper'
3
+ require 'libis/tools/metadata/dublin_core_record'
4
+
5
+ require 'rspec/matchers'
6
+ require 'equivalent-xml'
7
+
8
+ describe 'DublinCoreRecord' do
9
+
10
+ let(:header) { '<?xml version="1.0" encoding="utf-8"?>' }
11
+
12
+ subject(:dc) { Libis::Tools::Metadata::DublinCoreRecord.new(data) {} }
13
+
14
+ def dc_xml(tag, value = '', attributes = {})
15
+ "<dc:#{tag}#{attributes.sort.each{|k,v| " #{k}=\"#{v}\""}.join}>#{value}</dc:#{tag}>"
16
+ end
17
+
18
+ def dcterms_xml(tag, value = '', attributes = {})
19
+ "<dcterms:#{tag}#{attributes.sort.each{|k,v| " #{k}=\"#{v}\""}.join}>#{value}</dcterms:#{tag}>"
20
+ end
21
+
22
+ def match_xml(doc1, doc2)
23
+ xml1 = doc1.is_a?(::Libis::Tools::XmlDocument) ? doc1.document : ::Nokogiri::XML(doc1.to_s)
24
+ xml2 = doc2.is_a?(::Libis::Tools::XmlDocument) ? doc2.document : ::Nokogiri::XML(doc2.to_s)
25
+ # noinspection RubyResolve
26
+ expect(xml1).to be_equivalent_to(xml2).respecting_element_order
27
+ end
28
+
29
+ context 'Empty record' do
30
+ let(:data) { nil }
31
+ let(:root) { <<STR.chomp
32
+ <record \
33
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
34
+ xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/"
35
+ STR
36
+ }
37
+ let(:record_start) { root + '>' }
38
+ let(:record_end) { '</record>' }
39
+ let(:empty_record) { header + root + '/>' }
40
+
41
+
42
+ it 'contains emtpy record' do
43
+ match_xml dc.document, empty_record
44
+ end
45
+
46
+ it 'add dc:title' do
47
+ dc.title = 'abc'
48
+ match_xml dc.document, header + record_start + dc_xml('title', 'abc') + record_end
49
+ end
50
+
51
+ it 'add dc:date' do
52
+ dc.date = '2001'
53
+ dc.dcdate = '2002'
54
+ dc.dc_date = '2003'
55
+ match_xml dc.document,
56
+ header +
57
+ record_start +
58
+ dc_xml('date', '2001') +
59
+ dc_xml('date', '2002') +
60
+ dc_xml('date', '2003') +
61
+ record_end
62
+ end
63
+
64
+ it 'add dcterms:date' do
65
+ dc.termsdate = '2001'
66
+ dc.dctermsdate = '2002'
67
+ dc.terms_date = '2003'
68
+ dc.dcterms_date = '2004'
69
+ match_xml dc.document,
70
+ header +
71
+ record_start +
72
+ dcterms_xml('date', '2001') +
73
+ dcterms_xml('date', '2002') +
74
+ dcterms_xml('date', '2003') +
75
+ dcterms_xml('date', '2004') +
76
+ record_end
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -199,7 +199,7 @@ describe 'XML Document' do
199
199
 
200
200
  xml_doc.add_node :patron
201
201
  xml_doc.add_node :name, 'Harry Potter'
202
- books = xml_doc.add_node :books, nil, nil, namespaces: { jkr: 'http://JKRowling.com' , node_ns: 'jkr' }
202
+ books = xml_doc.add_node :books, namespaces: { jkr: 'http://JKRowling.com' , node_ns: 'jkr' }
203
203
  xml_doc.add_node :book, nil, books,
204
204
  title: 'Quidditch Through the Ages', author: 'Kennilworthy Whisp', due_date: '1992-4-23',
205
205
  namespaces: {node_ns: 'jkr'}
@@ -362,7 +362,7 @@ describe 'XML Document' do
362
362
 
363
363
  end
364
364
 
365
- it 'should work' do
365
+ it 'allows to parse xml string, save and reload' do
366
366
  xml_doc = ::Libis::Tools::XmlDocument.parse(<<-END.align_left)
367
367
  <patron>
368
368
  <name>Harry Potter</name>
@@ -381,6 +381,10 @@ describe 'XML Document' do
381
381
 
382
382
  match_xml xml_doc.document, @xml_template
383
383
 
384
+ end
385
+
386
+ it 'supports build to create XML document' do
387
+
384
388
  xml_doc = ::Libis::Tools::XmlDocument.build do
385
389
  # noinspection RubyResolve
386
390
  patron {
@@ -394,6 +398,10 @@ describe 'XML Document' do
394
398
 
395
399
  match_xml xml_doc.document, @xml_template
396
400
 
401
+ end
402
+
403
+ it 'supports different ways to create nodes' do
404
+
397
405
  xml_doc = ::Libis::Tools::XmlDocument.new
398
406
  xml_doc.add_node :patron
399
407
  xml_doc.name = 'Harry Potter'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libis-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kris Dekeyser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-21 00:00:00.000000000 Z
11
+ date: 2015-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -194,10 +194,16 @@ files:
194
194
  - lib/libis/tools/extend/string.rb
195
195
  - lib/libis/tools/extend/struct.rb
196
196
  - lib/libis/tools/logger.rb
197
+ - lib/libis/tools/metadata/dublin_core_record.rb
198
+ - lib/libis/tools/metadata/field_spec.rb
199
+ - lib/libis/tools/metadata/fix_field.rb
200
+ - lib/libis/tools/metadata/marc21_record.rb
201
+ - lib/libis/tools/metadata/marc_record.rb
202
+ - lib/libis/tools/metadata/sharepoint_mapping.rb
203
+ - lib/libis/tools/metadata/sharepoint_record.rb
204
+ - lib/libis/tools/metadata/var_field.rb
197
205
  - lib/libis/tools/mets_file.rb
198
206
  - lib/libis/tools/parameter.rb
199
- - lib/libis/tools/sharepoint_mapping.rb
200
- - lib/libis/tools/sharepoint_record.rb
201
207
  - lib/libis/tools/version.rb
202
208
  - lib/libis/tools/xml_document.rb
203
209
  - libis-tools.gemspec
@@ -212,6 +218,7 @@ files:
212
218
  - spec/data/test_config.yml
213
219
  - spec/deep_struct_spec.rb
214
220
  - spec/logger_spec.rb
221
+ - spec/metadata/dublin_core_spec.rb
215
222
  - spec/parameter_container_spec.rb
216
223
  - spec/parameter_spec.rb
217
224
  - spec/spec_helper.rb
@@ -256,6 +263,7 @@ test_files:
256
263
  - spec/data/test_config.yml
257
264
  - spec/deep_struct_spec.rb
258
265
  - spec/logger_spec.rb
266
+ - spec/metadata/dublin_core_spec.rb
259
267
  - spec/parameter_container_spec.rb
260
268
  - spec/parameter_spec.rb
261
269
  - spec/spec_helper.rb