xml-simple 1.0.11 → 1.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/xmlsimple.rb +144 -103
- metadata +51 -40
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 83fdfe764b21f66e184a245f9f908d2ed9678edd6d0734a33cac93030449c324
|
4
|
+
data.tar.gz: ecd9ac1ed12a0fb03f2b8fee924db65ec844dffda64f9553b76a626b2532635a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 652f1cbb3ef139e5450cdd2ad01fc4a22e23ec3174bd3d88c58b973f1c5c955673a365bbaa3e3c910ad60c190994e3e0e88fd4678af69d5981aadd2b0ecb019d
|
7
|
+
data.tar.gz: 40e7a0e3e6b0a42d178e8f6b782aca2e8a6d65ce87ab8aa06953d7dfe41b4ce115793018df2bab0b6238298d1c35ccdaa2fee9a0ea46ac927f2d2238d2f0146d
|
data/lib/xmlsimple.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# = XmlSimple
|
2
2
|
#
|
3
3
|
# Author:: Maik Schmidt <contact@maik-schmidt.de>
|
4
|
-
# Copyright:: Copyright (c) 2003-
|
4
|
+
# Copyright:: Copyright (c) 2003-2021 Maik Schmidt
|
5
5
|
# License:: Distributes under the same terms as Ruby.
|
6
6
|
#
|
7
7
|
require 'rexml/document'
|
@@ -11,7 +11,7 @@ require 'stringio'
|
|
11
11
|
class XmlSimple
|
12
12
|
include REXML
|
13
13
|
|
14
|
-
@@VERSION = '1.
|
14
|
+
@@VERSION = '1.1.9'
|
15
15
|
|
16
16
|
# A simple cache for XML documents that were already transformed
|
17
17
|
# by xml_in.
|
@@ -23,7 +23,7 @@ class XmlSimple
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# Saves a data structure into a file.
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# data::
|
28
28
|
# Data structure to be saved.
|
29
29
|
# filename::
|
@@ -121,12 +121,12 @@ class XmlSimple
|
|
121
121
|
# Create a "global" cache.
|
122
122
|
@@cache = Cache.new
|
123
123
|
|
124
|
-
# Creates and
|
125
|
-
#
|
124
|
+
# Creates and initializes a new XmlSimple object.
|
125
|
+
#
|
126
126
|
# defaults::
|
127
127
|
# Default values for options.
|
128
128
|
def initialize(defaults = nil)
|
129
|
-
unless defaults.nil? || defaults.
|
129
|
+
unless defaults.nil? || defaults.is_a?(Hash)
|
130
130
|
raise ArgumentError, "Options have to be a Hash."
|
131
131
|
end
|
132
132
|
@default_options = normalize_option_names(defaults, (KNOWN_OPTIONS['in'] + KNOWN_OPTIONS['out']).uniq)
|
@@ -143,7 +143,7 @@ class XmlSimple
|
|
143
143
|
# - filename: Tries to load and parse filename.
|
144
144
|
# - IO object: Reads from object until EOF is detected and parses result.
|
145
145
|
# - XML string: Parses string.
|
146
|
-
#
|
146
|
+
#
|
147
147
|
# options::
|
148
148
|
# Options to be used.
|
149
149
|
def xml_in(string = nil, options = nil)
|
@@ -151,7 +151,7 @@ class XmlSimple
|
|
151
151
|
|
152
152
|
# If no XML string or filename was supplied look for scriptname.xml.
|
153
153
|
if string.nil?
|
154
|
-
string = File::basename($0)
|
154
|
+
string = File::basename($0).dup
|
155
155
|
string.sub!(/\.[^.]+$/, '')
|
156
156
|
string += '.xml'
|
157
157
|
|
@@ -159,11 +159,11 @@ class XmlSimple
|
|
159
159
|
@options['searchpath'].unshift(directory) unless directory.nil?
|
160
160
|
end
|
161
161
|
|
162
|
-
if string.
|
162
|
+
if string.is_a?(String)
|
163
163
|
if string =~ /<.*?>/m
|
164
164
|
@doc = parse(string)
|
165
165
|
elsif string == '-'
|
166
|
-
@doc = parse($stdin.
|
166
|
+
@doc = parse($stdin.read)
|
167
167
|
else
|
168
168
|
filename = find_xml_file(string, @options['searchpath'])
|
169
169
|
|
@@ -182,13 +182,13 @@ class XmlSimple
|
|
182
182
|
return content if content
|
183
183
|
}
|
184
184
|
end
|
185
|
-
|
185
|
+
|
186
186
|
@doc = load_xml_file(filename)
|
187
187
|
end
|
188
|
-
elsif string.
|
189
|
-
@doc = parse(string.
|
188
|
+
elsif string.respond_to?(:read)
|
189
|
+
@doc = parse(string.read)
|
190
190
|
else
|
191
|
-
raise ArgumentError, "Could not parse object of type: <#{string.
|
191
|
+
raise ArgumentError, "Could not parse object of type: <#{string.class}>."
|
192
192
|
end
|
193
193
|
|
194
194
|
result = collapse(@doc.root)
|
@@ -202,7 +202,7 @@ class XmlSimple
|
|
202
202
|
xml_simple = XmlSimple.new
|
203
203
|
xml_simple.xml_in(string, options)
|
204
204
|
end
|
205
|
-
|
205
|
+
|
206
206
|
# Converts a data structure into an XML document.
|
207
207
|
#
|
208
208
|
# ref::
|
@@ -211,7 +211,7 @@ class XmlSimple
|
|
211
211
|
# Options to be used.
|
212
212
|
def xml_out(ref, options = nil)
|
213
213
|
handle_options('out', options)
|
214
|
-
if ref.
|
214
|
+
if ref.is_a?(Array)
|
215
215
|
ref = { @options['anonymoustag'] => ref }
|
216
216
|
end
|
217
217
|
|
@@ -222,7 +222,7 @@ class XmlSimple
|
|
222
222
|
@options['rootname'] = keys[0]
|
223
223
|
end
|
224
224
|
elsif @options['rootname'] == ''
|
225
|
-
if ref.
|
225
|
+
if ref.is_a?(Hash)
|
226
226
|
refsave = ref
|
227
227
|
ref = {}
|
228
228
|
refsave.each { |key, value|
|
@@ -258,21 +258,21 @@ class XmlSimple
|
|
258
258
|
xml_simple = XmlSimple.new
|
259
259
|
xml_simple.xml_out(hash, options)
|
260
260
|
end
|
261
|
-
|
261
|
+
|
262
262
|
private
|
263
263
|
|
264
264
|
# Declare options that are valid for xml_in and xml_out.
|
265
265
|
KNOWN_OPTIONS = {
|
266
266
|
'in' => %w(
|
267
|
-
keyattr keeproot forcecontent contentkey noattr
|
268
|
-
|
269
|
-
|
270
|
-
|
267
|
+
keyattr keeproot forcecontent contentkey noattr searchpath
|
268
|
+
forcearray suppressempty anonymoustag cache grouptags
|
269
|
+
normalisespace normalizespace variables varattr keytosymbol
|
270
|
+
attrtosymbol attrprefix conversions kebabtosnakecase
|
271
271
|
),
|
272
272
|
'out' => %w(
|
273
273
|
keyattr keeproot contentkey noattr rootname
|
274
274
|
xmldeclaration outputfile noescape suppressempty
|
275
|
-
anonymoustag indent grouptags noindent
|
275
|
+
anonymoustag indent grouptags noindent attrprefix selfclose
|
276
276
|
)
|
277
277
|
}
|
278
278
|
|
@@ -285,11 +285,13 @@ class XmlSimple
|
|
285
285
|
DEF_FORCE_ARRAY = true
|
286
286
|
DEF_INDENTATION = ' '
|
287
287
|
DEF_KEY_TO_SYMBOL = false
|
288
|
-
|
288
|
+
DEF_ATTR_TO_SYMBOL = false
|
289
|
+
DEF_KEBAB_TO_SNAKE = false
|
290
|
+
|
289
291
|
# Normalizes option names in a hash, i.e., turns all
|
290
292
|
# characters to lower case and removes all underscores.
|
291
|
-
# Additionally, this method checks
|
292
|
-
# was used and raises an according exception.
|
293
|
+
# Additionally, this method checks if an unknown option
|
294
|
+
# was used, and raises an according exception.
|
293
295
|
#
|
294
296
|
# options::
|
295
297
|
# Hash to be normalized.
|
@@ -299,18 +301,17 @@ class XmlSimple
|
|
299
301
|
return nil if options.nil?
|
300
302
|
result = Hash.new
|
301
303
|
options.each { |key, value|
|
302
|
-
lkey = key.downcase
|
303
|
-
lkey.gsub!(/_/, '')
|
304
|
+
lkey = key.to_s.downcase.gsub(/_/, '')
|
304
305
|
if !known_options.member?(lkey)
|
305
|
-
raise ArgumentError, "
|
306
|
+
raise ArgumentError, "Unrecognized option: #{lkey}."
|
306
307
|
end
|
307
308
|
result[lkey] = value
|
308
309
|
}
|
309
310
|
result
|
310
311
|
end
|
311
|
-
|
312
|
+
|
312
313
|
# Merges a set of options with the default options.
|
313
|
-
#
|
314
|
+
#
|
314
315
|
# direction::
|
315
316
|
# 'in': If options should be handled for xml_in.
|
316
317
|
# 'out': If options should be handled for xml_out.
|
@@ -319,7 +320,7 @@ class XmlSimple
|
|
319
320
|
def handle_options(direction, options)
|
320
321
|
@options = options || Hash.new
|
321
322
|
|
322
|
-
raise ArgumentError, "Options must be a Hash!" unless @options.
|
323
|
+
raise ArgumentError, "Options must be a Hash!" unless @options.is_a?(Hash)
|
323
324
|
|
324
325
|
unless KNOWN_OPTIONS.has_key?(direction)
|
325
326
|
raise ArgumentError, "Unknown direction: <#{direction}>."
|
@@ -354,6 +355,8 @@ class XmlSimple
|
|
354
355
|
|
355
356
|
@options['keytosymbol'] = DEF_KEY_TO_SYMBOL unless @options.has_key?('keytosymbol')
|
356
357
|
|
358
|
+
@options['attrtosymbol'] = DEF_ATTR_TO_SYMBOL unless @options.has_key?('attrtosymbol')
|
359
|
+
|
357
360
|
if @options.has_key?('contentkey')
|
358
361
|
if @options['contentkey'] =~ /^-(.*)$/
|
359
362
|
@options['contentkey'] = $1
|
@@ -369,7 +372,7 @@ class XmlSimple
|
|
369
372
|
@options['normalisespace'] = 0 if @options['normalisespace'].nil?
|
370
373
|
|
371
374
|
if @options.has_key?('searchpath')
|
372
|
-
unless @options['searchpath'].
|
375
|
+
unless @options['searchpath'].is_a?(Array)
|
373
376
|
@options['searchpath'] = [ @options['searchpath'] ]
|
374
377
|
end
|
375
378
|
else
|
@@ -394,13 +397,13 @@ class XmlSimple
|
|
394
397
|
if !scalar(@options['keyattr'])
|
395
398
|
# Convert keyattr => { elem => '+attr' }
|
396
399
|
# to keyattr => { elem => ['attr', '+'] }
|
397
|
-
if @options['keyattr'].
|
400
|
+
if @options['keyattr'].is_a?(Hash)
|
398
401
|
@options['keyattr'].each { |key, value|
|
399
402
|
if value =~ /^([-+])?(.*)$/
|
400
403
|
@options['keyattr'][key] = [$2, $1 ? $1 : '']
|
401
404
|
end
|
402
405
|
}
|
403
|
-
elsif !@options['keyattr'].
|
406
|
+
elsif !@options['keyattr'].is_a?(Array)
|
404
407
|
raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
|
405
408
|
end
|
406
409
|
else
|
@@ -411,17 +414,17 @@ class XmlSimple
|
|
411
414
|
end
|
412
415
|
|
413
416
|
if @options.has_key?('forcearray')
|
414
|
-
if @options['forcearray'].
|
417
|
+
if @options['forcearray'].is_a?(Regexp)
|
415
418
|
@options['forcearray'] = [ @options['forcearray'] ]
|
416
419
|
end
|
417
420
|
|
418
|
-
if @options['forcearray'].
|
421
|
+
if @options['forcearray'].is_a?(Array)
|
419
422
|
force_list = @options['forcearray']
|
420
423
|
unless force_list.empty?
|
421
424
|
@options['forcearray'] = {}
|
422
425
|
force_list.each { |tag|
|
423
|
-
if tag.
|
424
|
-
unless @options['forcearray']['_regex'].
|
426
|
+
if tag.is_a?(Regexp)
|
427
|
+
unless @options['forcearray']['_regex'].is_a?(Array)
|
425
428
|
@options['forcearray']['_regex'] = []
|
426
429
|
end
|
427
430
|
@options['forcearray']['_regex'] << tag
|
@@ -439,11 +442,11 @@ class XmlSimple
|
|
439
442
|
@options['forcearray'] = DEF_FORCE_ARRAY
|
440
443
|
end
|
441
444
|
|
442
|
-
if @options.has_key?('grouptags') && !@options['grouptags'].
|
445
|
+
if @options.has_key?('grouptags') && !@options['grouptags'].is_a?(Hash)
|
443
446
|
raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
|
444
447
|
end
|
445
448
|
|
446
|
-
if @options.has_key?('variables') && !@options['variables'].
|
449
|
+
if @options.has_key?('variables') && !@options['variables'].is_a?(Hash)
|
447
450
|
raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
|
448
451
|
end
|
449
452
|
|
@@ -452,6 +455,8 @@ class XmlSimple
|
|
452
455
|
elsif @options.has_key?('varattr')
|
453
456
|
@_var_values = {}
|
454
457
|
end
|
458
|
+
|
459
|
+
@options['kebabtosnakecase'] = DEF_KEBAB_TO_SNAKE unless @options.has_key?('kebabtosnakecase')
|
455
460
|
end
|
456
461
|
|
457
462
|
# Actually converts an XML document element into a data structure.
|
@@ -480,7 +485,7 @@ class XmlSimple
|
|
480
485
|
result[@options['contentkey']] = content
|
481
486
|
end
|
482
487
|
elsif element.has_text? # i.e. it has only text.
|
483
|
-
return collapse_text_node(result, element)
|
488
|
+
return collapse_text_node(result, element) # calls merge, which converts
|
484
489
|
end
|
485
490
|
|
486
491
|
# Turn Arrays into Hashes if key fields present.
|
@@ -489,18 +494,21 @@ class XmlSimple
|
|
489
494
|
# Disintermediate grouped tags.
|
490
495
|
if @options.has_key?('grouptags')
|
491
496
|
result.each { |key, value|
|
492
|
-
|
497
|
+
# In results, key should already be converted
|
498
|
+
raise("Unconverted key '#{key}' found. Should be '#{kebab_to_snake_case key}'.") if (key != kebab_to_snake_case(key))
|
499
|
+
next unless (value.is_a?(Hash) && (value.size == 1))
|
493
500
|
child_key, child_value = value.to_a[0]
|
501
|
+
child_key = kebab_to_snake_case child_key # todo test whether necessary
|
494
502
|
if @options['grouptags'][key] == child_key
|
495
503
|
result[key] = child_value
|
496
504
|
end
|
497
505
|
}
|
498
506
|
end
|
499
|
-
|
500
|
-
# Fold
|
501
|
-
if count == 1
|
507
|
+
|
508
|
+
# Fold Hashes containing a single anonymous Array up into just the Array.
|
509
|
+
if count == 1
|
502
510
|
anonymoustag = @options['anonymoustag']
|
503
|
-
if result.has_key?(anonymoustag) && result[anonymoustag].
|
511
|
+
if result.has_key?(anonymoustag) && result[anonymoustag].is_a?(Array)
|
504
512
|
return result[anonymoustag]
|
505
513
|
end
|
506
514
|
end
|
@@ -538,16 +546,17 @@ class XmlSimple
|
|
538
546
|
end
|
539
547
|
|
540
548
|
# Folds all arrays in a Hash.
|
541
|
-
#
|
549
|
+
#
|
542
550
|
# hash::
|
543
551
|
# Hash to be folded.
|
544
552
|
def fold_arrays(hash)
|
545
553
|
fold_amount = 0
|
546
554
|
keyattr = @options['keyattr']
|
547
|
-
if (keyattr.
|
555
|
+
if (keyattr.is_a?(Array) || keyattr.is_a?(Hash))
|
548
556
|
hash.each { |key, value|
|
549
|
-
|
550
|
-
|
557
|
+
key = kebab_to_snake_case key
|
558
|
+
if value.is_a?(Array)
|
559
|
+
if keyattr.is_a?(Array)
|
551
560
|
hash[key] = fold_array(value)
|
552
561
|
else
|
553
562
|
hash[key] = fold_array_by_name(key, value)
|
@@ -568,13 +577,13 @@ class XmlSimple
|
|
568
577
|
def fold_array(array)
|
569
578
|
hash = Hash.new
|
570
579
|
array.each { |x|
|
571
|
-
return array unless x.
|
580
|
+
return array unless x.is_a?(Hash)
|
572
581
|
key_matched = false
|
573
582
|
@options['keyattr'].each { |key|
|
574
583
|
if x.has_key?(key)
|
575
584
|
key_matched = true
|
576
585
|
value = x[key]
|
577
|
-
return array if value.
|
586
|
+
return array if value.is_a?(Hash) || value.is_a?(Array)
|
578
587
|
value = normalise_space(value) if @options['normalisespace'] == 1
|
579
588
|
x.delete(key)
|
580
589
|
hash[value] = x
|
@@ -586,7 +595,7 @@ class XmlSimple
|
|
586
595
|
hash = collapse_content(hash) if @options['collapseagain']
|
587
596
|
hash
|
588
597
|
end
|
589
|
-
|
598
|
+
|
590
599
|
# Folds an Array to a Hash, if possible. Folding happens
|
591
600
|
# according to the content of keyattr, which has to be
|
592
601
|
# a Hash.
|
@@ -601,9 +610,9 @@ class XmlSimple
|
|
601
610
|
|
602
611
|
hash = Hash.new
|
603
612
|
array.each { |x|
|
604
|
-
if x.
|
613
|
+
if x.is_a?(Hash) && x.has_key?(key)
|
605
614
|
value = x[key]
|
606
|
-
return array if value.
|
615
|
+
return array if value.is_a?(Hash) || value.is_a?(Array)
|
607
616
|
value = normalise_space(value) if @options['normalisespace'] == 1
|
608
617
|
hash[value] = x
|
609
618
|
hash[value]["-#{key}"] = hash[value][key] if flag == '-'
|
@@ -624,12 +633,12 @@ class XmlSimple
|
|
624
633
|
def collapse_content(hash)
|
625
634
|
content_key = @options['contentkey']
|
626
635
|
hash.each_value { |value|
|
627
|
-
return hash unless value.
|
636
|
+
return hash unless value.is_a?(Hash) && value.size == 1 && value.has_key?(content_key)
|
628
637
|
hash.each_key { |key| hash[key] = hash[key][content_key] }
|
629
638
|
}
|
630
639
|
hash
|
631
640
|
end
|
632
|
-
|
641
|
+
|
633
642
|
# Adds a new key/value pair to an existing Hash. If the key to be added
|
634
643
|
# does already exist and the existing value associated with key is not
|
635
644
|
# an Array, it will be converted into an Array. Then the new value is
|
@@ -642,37 +651,42 @@ class XmlSimple
|
|
642
651
|
# value::
|
643
652
|
# Value to be associated with key.
|
644
653
|
def merge(hash, key, value)
|
645
|
-
|
654
|
+
key = kebab_to_snake_case key
|
655
|
+
if value.is_a?(String)
|
646
656
|
value = normalise_space(value) if @options['normalisespace'] == 2
|
647
657
|
|
658
|
+
if conv = @options['conversions'] and conv = conv.find {|c,_| c.match(key)} and conv = conv.at(1)
|
659
|
+
value = conv.call(value)
|
660
|
+
end
|
661
|
+
|
648
662
|
# do variable substitutions
|
649
663
|
unless @_var_values.nil? || @_var_values.empty?
|
650
664
|
value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
|
651
665
|
end
|
652
|
-
|
666
|
+
|
653
667
|
# look for variable definitions
|
654
668
|
if @options.has_key?('varattr')
|
655
|
-
varattr = @options['varattr']
|
669
|
+
varattr = kebab_to_snake_case @options['varattr']
|
656
670
|
if hash.has_key?(varattr)
|
657
671
|
set_var(hash[varattr], value)
|
658
672
|
end
|
659
673
|
end
|
660
674
|
end
|
661
|
-
|
675
|
+
|
662
676
|
#patch for converting keys to symbols
|
663
677
|
if @options.has_key?('keytosymbol')
|
664
678
|
if @options['keytosymbol'] == true
|
665
679
|
key = key.to_s.downcase.to_sym
|
666
680
|
end
|
667
681
|
end
|
668
|
-
|
682
|
+
|
669
683
|
if hash.has_key?(key)
|
670
|
-
if hash[key].
|
684
|
+
if hash[key].is_a?(Array)
|
671
685
|
hash[key] << value
|
672
686
|
else
|
673
687
|
hash[key] = [ hash[key], value ]
|
674
688
|
end
|
675
|
-
elsif value.
|
689
|
+
elsif value.is_a?(Array) # Handle anonymous arrays.
|
676
690
|
hash[key] = [ value ]
|
677
691
|
else
|
678
692
|
if force_array?(key)
|
@@ -683,21 +697,21 @@ class XmlSimple
|
|
683
697
|
end
|
684
698
|
hash
|
685
699
|
end
|
686
|
-
|
700
|
+
|
687
701
|
# Checks, if the 'forcearray' option has to be used for
|
688
702
|
# a certain key.
|
689
703
|
def force_array?(key)
|
690
704
|
return false if key == @options['contentkey']
|
691
705
|
return true if @options['forcearray'] == true
|
692
706
|
forcearray = @options['forcearray']
|
693
|
-
if forcearray.
|
694
|
-
return true if forcearray.has_key?(key)
|
707
|
+
if forcearray.is_a?(Hash)
|
708
|
+
return true if forcearray.has_key?(key)
|
695
709
|
return false unless forcearray.has_key?('_regex')
|
696
710
|
forcearray['_regex'].each { |x| return true if key =~ x }
|
697
711
|
end
|
698
712
|
return false
|
699
713
|
end
|
700
|
-
|
714
|
+
|
701
715
|
# Converts the attributes array of a document node into a Hash.
|
702
716
|
# Returns an empty Hash, if node has no attributes.
|
703
717
|
#
|
@@ -705,21 +719,28 @@ class XmlSimple
|
|
705
719
|
# Document node to extract attributes from.
|
706
720
|
def get_attributes(node)
|
707
721
|
attributes = {}
|
708
|
-
|
722
|
+
if @options['attrprefix']
|
723
|
+
node.attributes.each { |n,v| attributes["@" + kebab_to_snake_case(n)] = v }
|
724
|
+
elsif @options.has_key?('attrtosymbol') and @options['attrtosymbol'] == true
|
725
|
+
#patch for converting attribute names to symbols
|
726
|
+
node.attributes.each { |n,v| attributes[kebab_to_snake_case(n).to_sym] = v }
|
727
|
+
else
|
728
|
+
node.attributes.each { |n,v| attributes[kebab_to_snake_case(n)] = v }
|
729
|
+
end
|
730
|
+
|
709
731
|
attributes
|
710
732
|
end
|
711
|
-
|
733
|
+
|
712
734
|
# Determines, if a document element has mixed content.
|
713
735
|
#
|
714
736
|
# element::
|
715
737
|
# Document element to be checked.
|
716
738
|
def has_mixed_content?(element)
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
false
|
739
|
+
element.has_text? &&
|
740
|
+
element.has_elements? &&
|
741
|
+
!element.texts.join('').strip.empty?
|
721
742
|
end
|
722
|
-
|
743
|
+
|
723
744
|
# Called when a variable definition is encountered in the XML.
|
724
745
|
# A variable definition looks like
|
725
746
|
# <element attrname="name">value</element>
|
@@ -737,7 +758,7 @@ class XmlSimple
|
|
737
758
|
return "${#{name}}"
|
738
759
|
end
|
739
760
|
end
|
740
|
-
|
761
|
+
|
741
762
|
# Recurses through a data structure building up and returning an
|
742
763
|
# XML representation of that structure as a string.
|
743
764
|
#
|
@@ -765,12 +786,12 @@ class XmlSimple
|
|
765
786
|
end
|
766
787
|
|
767
788
|
# Unfold hash to array if possible.
|
768
|
-
if ref.
|
789
|
+
if ref.is_a?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
|
769
790
|
ref = hash_to_array(name, ref)
|
770
791
|
end
|
771
792
|
|
772
793
|
result = []
|
773
|
-
if ref.
|
794
|
+
if ref.is_a?(Hash)
|
774
795
|
# Reintermediate grouped values if applicable.
|
775
796
|
if @options.has_key?('grouptags')
|
776
797
|
ref.each { |key, value|
|
@@ -779,7 +800,7 @@ class XmlSimple
|
|
779
800
|
end
|
780
801
|
}
|
781
802
|
end
|
782
|
-
|
803
|
+
|
783
804
|
nested = []
|
784
805
|
text_content = nil
|
785
806
|
if named
|
@@ -788,7 +809,7 @@ class XmlSimple
|
|
788
809
|
|
789
810
|
if !ref.empty?
|
790
811
|
ref.each { |key, value|
|
791
|
-
next if !key.nil? && key[0, 1] == '-'
|
812
|
+
next if !key.nil? && key.to_s[0, 1] == '-'
|
792
813
|
if value.nil?
|
793
814
|
unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
|
794
815
|
raise ArgumentError, "Use of uninitialized value!"
|
@@ -796,7 +817,13 @@ class XmlSimple
|
|
796
817
|
value = {}
|
797
818
|
end
|
798
819
|
|
799
|
-
|
820
|
+
# Check for the '@' attribute prefix to allow separation of attributes and elements
|
821
|
+
|
822
|
+
if (@options['noattr'] ||
|
823
|
+
(@options['attrprefix'] && !(key =~ /^@(.*)/)) ||
|
824
|
+
!scalar(value)
|
825
|
+
) &&
|
826
|
+
key != @options['contentkey']
|
800
827
|
nested << value_to_xml(value, key, indent + @options['indent'])
|
801
828
|
else
|
802
829
|
value = value.to_s
|
@@ -804,11 +831,11 @@ class XmlSimple
|
|
804
831
|
if key == @options['contentkey']
|
805
832
|
text_content = value
|
806
833
|
else
|
807
|
-
result << ' ' << key << '="' << value << '"'
|
834
|
+
result << ' ' << ($1||key) << '="' << value << '"'
|
808
835
|
end
|
809
836
|
end
|
810
837
|
}
|
811
|
-
|
838
|
+
elsif !@options['selfclose']
|
812
839
|
text_content = ''
|
813
840
|
end
|
814
841
|
|
@@ -831,13 +858,13 @@ class XmlSimple
|
|
831
858
|
else
|
832
859
|
result << ' />' << nl
|
833
860
|
end
|
834
|
-
elsif ref.
|
861
|
+
elsif ref.is_a?(Array)
|
835
862
|
ref.each { |value|
|
836
863
|
if scalar(value)
|
837
864
|
result << indent << '<' << name << '>'
|
838
865
|
result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
|
839
866
|
result << '</' << name << '>' << nl
|
840
|
-
elsif value.
|
867
|
+
elsif value.is_a?(Hash)
|
841
868
|
result << value_to_xml(value, name, indent)
|
842
869
|
else
|
843
870
|
result << indent << '<' << name << '>' << nl
|
@@ -852,31 +879,31 @@ class XmlSimple
|
|
852
879
|
@ancestors.pop if !scalar(ref)
|
853
880
|
result.join('')
|
854
881
|
end
|
855
|
-
|
882
|
+
|
856
883
|
# Checks, if a certain value is a "scalar" value. Whatever
|
857
884
|
# that will be in Ruby ... ;-)
|
858
|
-
#
|
885
|
+
#
|
859
886
|
# value::
|
860
887
|
# Value to be checked.
|
861
888
|
def scalar(value)
|
862
|
-
return false if value.
|
889
|
+
return false if value.is_a?(Hash) || value.is_a?(Array)
|
863
890
|
return true
|
864
891
|
end
|
865
892
|
|
866
893
|
# Attempts to unfold a hash of hashes into an array of hashes. Returns
|
867
894
|
# a reference to th array on success or the original hash, if unfolding
|
868
895
|
# is not possible.
|
869
|
-
#
|
896
|
+
#
|
870
897
|
# parent::
|
871
|
-
#
|
898
|
+
#
|
872
899
|
# hashref::
|
873
900
|
# Reference to the hash to be unfolded.
|
874
901
|
def hash_to_array(parent, hashref)
|
875
902
|
arrayref = []
|
876
903
|
hashref.each { |key, value|
|
877
|
-
return hashref unless value.
|
904
|
+
return hashref unless value.is_a?(Hash)
|
878
905
|
|
879
|
-
if @options['keyattr'].
|
906
|
+
if @options['keyattr'].is_a?(Hash)
|
880
907
|
return hashref unless @options['keyattr'].has_key?(parent)
|
881
908
|
arrayref << { @options['keyattr'][parent][0] => key }.update(value)
|
882
909
|
else
|
@@ -885,7 +912,7 @@ class XmlSimple
|
|
885
912
|
}
|
886
913
|
arrayref
|
887
914
|
end
|
888
|
-
|
915
|
+
|
889
916
|
# Replaces XML markup characters by their external entities.
|
890
917
|
#
|
891
918
|
# data::
|
@@ -893,7 +920,7 @@ class XmlSimple
|
|
893
920
|
def escape_value(data)
|
894
921
|
Text::normalize(data)
|
895
922
|
end
|
896
|
-
|
923
|
+
|
897
924
|
# Removes leading and trailing whitespace and sequences of
|
898
925
|
# whitespaces from a string.
|
899
926
|
#
|
@@ -918,7 +945,7 @@ class XmlSimple
|
|
918
945
|
return value.nil?
|
919
946
|
end
|
920
947
|
end
|
921
|
-
|
948
|
+
|
922
949
|
# Converts a document node into a String.
|
923
950
|
# If the node could not be converted into a String
|
924
951
|
# for any reason, default will be returned.
|
@@ -928,11 +955,11 @@ class XmlSimple
|
|
928
955
|
# default::
|
929
956
|
# Value to be returned, if node could not be converted.
|
930
957
|
def node_to_text(node, default = nil)
|
931
|
-
if node.
|
958
|
+
if node.is_a?(REXML::Element)
|
932
959
|
node.texts.map { |t| t.value }.join('')
|
933
|
-
elsif node.
|
960
|
+
elsif node.is_a?(REXML::Attribute)
|
934
961
|
node.value.nil? ? default : node.value.strip
|
935
|
-
elsif node.
|
962
|
+
elsif node.is_a?(REXML::Text)
|
936
963
|
node.value.strip
|
937
964
|
else
|
938
965
|
default
|
@@ -951,7 +978,7 @@ class XmlSimple
|
|
951
978
|
def parse(xml_string)
|
952
979
|
Document.new(xml_string)
|
953
980
|
end
|
954
|
-
|
981
|
+
|
955
982
|
# Searches in a list of paths for a certain file. Returns
|
956
983
|
# the full path to the file, if it could be found. Otherwise,
|
957
984
|
# an exception will be raised.
|
@@ -978,20 +1005,20 @@ class XmlSimple
|
|
978
1005
|
end
|
979
1006
|
raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
|
980
1007
|
end
|
981
|
-
|
1008
|
+
|
982
1009
|
# Loads and parses an XML configuration file.
|
983
1010
|
#
|
984
1011
|
# filename::
|
985
1012
|
# Name of the configuration file to be loaded.
|
986
1013
|
#
|
987
1014
|
# The following exceptions may be raised:
|
988
|
-
#
|
1015
|
+
#
|
989
1016
|
# Errno::ENOENT::
|
990
1017
|
# If the specified file does not exist.
|
991
1018
|
# REXML::ParseException::
|
992
1019
|
# If the specified file is not wellformed.
|
993
1020
|
def load_xml_file(filename)
|
994
|
-
parse(
|
1021
|
+
parse(IO::read(filename))
|
995
1022
|
end
|
996
1023
|
|
997
1024
|
# Caches the data belonging to a certain file.
|
@@ -1016,6 +1043,20 @@ class XmlSimple
|
|
1016
1043
|
}
|
1017
1044
|
end
|
1018
1045
|
end
|
1046
|
+
|
1047
|
+
# Substitutes underscores for hyphens if the KebabToSnakeCase option is selected. For when you don't
|
1048
|
+
# want to refer to keys by hash[:'my-key'] but instead as hash[:my_key]
|
1049
|
+
#
|
1050
|
+
# key::
|
1051
|
+
# Key to be converted.
|
1052
|
+
def kebab_to_snake_case(key)
|
1053
|
+
return key unless (@options['kebabtosnakecase'])
|
1054
|
+
|
1055
|
+
is_symbol = key.is_a? Symbol
|
1056
|
+
key = key.to_s.gsub(/-/, '_')
|
1057
|
+
key = key.to_sym if is_symbol
|
1058
|
+
key
|
1059
|
+
end
|
1019
1060
|
end
|
1020
1061
|
|
1021
1062
|
# vim:sw=2
|
metadata
CHANGED
@@ -1,46 +1,57 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.0
|
3
|
-
specification_version: 1
|
1
|
+
--- !ruby/object:Gem::Specification
|
4
2
|
name: xml-simple
|
5
|
-
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-03-12 00:00:00 +01:00
|
8
|
-
summary: A very simple API for XML processing.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: contact@maik-schmidt.de
|
12
|
-
homepage: http://xml-simple.rubyforge.org
|
13
|
-
rubyforge_project: xml-simple
|
14
|
-
description:
|
15
|
-
autorequire: xmlsimple
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: false
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.9
|
25
5
|
platform: ruby
|
26
|
-
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
|
-
authors:
|
6
|
+
authors:
|
30
7
|
- Maik Schmidt
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rexml
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email: contact@maik-schmidt.de
|
39
29
|
executables: []
|
40
|
-
|
41
30
|
extensions: []
|
42
|
-
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/xmlsimple.rb
|
34
|
+
homepage: https://github.com/maik/xml-simple
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
43
52
|
requirements: []
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
rubygems_version: 3.1.2
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: A simple API for XML processing.
|
57
|
+
test_files: []
|