udat 1.0.0 → 1.1.0

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 (2) hide show
  1. data/lib/udat.rb +179 -74
  2. metadata +2 -2
data/lib/udat.rb CHANGED
@@ -125,9 +125,13 @@ class Udat
125
125
 
126
126
  # Returns an escaped version of a string, where backslashes are preceding
127
127
  # certain reserved characters.
128
- def escape_string(string)
128
+ def self.escape_string(string)
129
129
  string.to_s.gsub /([<>\[\]|\\])/, "\\\\\\1"
130
130
  end
131
+ # Calls Udat.escape_string.
132
+ def escape_string(string)
133
+ self.class.escape_string(string)
134
+ end
131
135
  private :escape_string
132
136
 
133
137
  # Tag (i.e. type of the content).
@@ -179,13 +183,14 @@ class Udat
179
183
  def hash
180
184
  to_s.hash
181
185
  end
182
- # Returns true, if class, tag and content are matching another object.
186
+ # Returns true, if class, tag and content (including it's order in case
187
+ # of a UdatCollection) are matching another object.
183
188
  def eql?(other)
184
189
  self.class == other.class and
185
190
  self.tag == other.tag and
186
191
  self.to_s == other.to_s
187
192
  end
188
- # Same as Udat#eql?.
193
+ # Same as Udat#eql?, but behaves differently in UdatCollection.
189
194
  def ==(other)
190
195
  self.eql? other
191
196
  end
@@ -325,6 +330,56 @@ end
325
330
  # methods of this class to Udat objects using Object#to_udat.
326
331
  class UdatCollection < Udat
327
332
 
333
+ # Internally used wrapper class for Udat objects, to lookup
334
+ # UdatCollection's in Hash'es, while ignoring the order of their content.
335
+ class UdatUnorderedWrapper
336
+ def initialize(content)
337
+ @content = content
338
+ end
339
+ attr_accessor :content
340
+ def hash
341
+ if content.kind_of? UdatCollection
342
+ hash = 0
343
+ content.key_value_pairs.each do |key, value|
344
+ hash += key.hash
345
+ hash += value.hash
346
+ end
347
+ return hash
348
+ else
349
+ return content.hash
350
+ end
351
+ end
352
+ def eql?(other)
353
+ return false unless self.class == other.class
354
+ if self.content.class == other.content.class and
355
+ self.content.tag == other.content.tag
356
+ if self.content.kind_of? UdatCollection
357
+ own_pairs = self.content.key_value_pairs
358
+ other_pairs = other.content.key_value_pairs
359
+ own_pairs.each do |own_pair|
360
+ catch :found do
361
+ other_pairs.each_index do |index|
362
+ if own_pair.eql? other_pairs[index]
363
+ other_pairs.delete_at index
364
+ throw :found
365
+ end
366
+ end
367
+ return false
368
+ end
369
+ end
370
+ return true
371
+ else
372
+ return self.content.eql?(other.content)
373
+ end
374
+ else
375
+ return false
376
+ end
377
+ end
378
+ def ==(other)
379
+ self.eql? other
380
+ end
381
+ end
382
+
328
383
  public_class_method :new
329
384
  # Creates a new Udat object with a given tag (which may be nil) and a
330
385
  # given content, represented by a nested Array structure. See the source
@@ -333,7 +388,8 @@ class UdatCollection < Udat
333
388
  def initialize(tag, content)
334
389
  super tag
335
390
  @ordered = true
336
- clear
391
+ @entries = []
392
+ require_index_hashes
337
393
  unless content.nil?
338
394
  if content.respond_to? :to_ary
339
395
  content = content.to_ary
@@ -345,51 +401,97 @@ class UdatCollection < Udat
345
401
  end
346
402
 
347
403
  # Deletes the internal index hashes.
348
- def index_void!
404
+ # They are automatically reconstructed once a lookup is done.
405
+ # This method should be called by the user, if contained Udat objects
406
+ # have changed, AFTER being added to this collection (similar to
407
+ # Hash#rehash).
408
+ def rehash
349
409
  synchronize do
350
- @key_to_value = nil
351
- @value_to_key = nil
410
+ @key_indicies = nil
411
+ @value_indicies = nil
412
+ @unordered_key_indicies = nil
413
+ @unordered_value_indicies = nil
414
+ @all_unordered_key_indicies = nil
415
+ @all_unordered_value_indicies = nil
352
416
  end
353
417
  return self
354
418
  end
355
- # Checks, if the internal index hashes are existent
356
- # (and therefore up to date).
357
- def index_void?
419
+
420
+ # Returns true, if the internal index hashes are not existent.
421
+ def index_hashes_void?
358
422
  synchronize do
359
- return @key_to_value.nil?
360
- end
423
+ return @key_indicies.nil?
424
+ end
425
+ end
426
+ private :index_hashes_void?
427
+ # Registers a key value pair in the index.
428
+ def add_to_index_hashes(index, key, value)
429
+ @key_indicies[key] ||= []
430
+ @key_indicies[key] << index
431
+ @value_indicies[value] ||= []
432
+ @value_indicies[value] << index
433
+ if key.kind_of? UdatCollection and key.unordered?
434
+ @unordered_key_indicies[UdatUnorderedWrapper.new(key)] ||= []
435
+ @unordered_key_indicies[UdatUnorderedWrapper.new(key)] << index
436
+ end
437
+ if value.kind_of? UdatCollection and value.unordered?
438
+ @unordered_value_indicies[UdatUnorderedWrapper.new(value)] ||= []
439
+ @unordered_value_indicies[UdatUnorderedWrapper.new(value)] << index
440
+ end
441
+ @all_unordered_key_indicies[UdatUnorderedWrapper.new(key)] ||= []
442
+ @all_unordered_key_indicies[UdatUnorderedWrapper.new(key)] << index
443
+ @all_unordered_value_indicies[UdatUnorderedWrapper.new(value)] ||= []
444
+ @all_unordered_value_indicies[UdatUnorderedWrapper.new(value)] << index
445
+ return self
361
446
  end
447
+ private :add_to_index_hashes
362
448
  # Creates index hashes, if they are not existent.
363
- def require_index
449
+ def require_index_hashes
364
450
  synchronize do
365
- if index_void?
366
- @key_to_value = {}
367
- @value_to_key = {}
368
- @entries.each do |key, value|
369
- unless key.nil?
370
- @key_to_value[key] = value unless @key_to_value.has_key? key
371
- @value_to_key[value] = key unless @value_to_key.has_key? value
372
- end
451
+ if index_hashes_void?
452
+ @key_indicies = {}
453
+ @value_indicies = {}
454
+ @unordered_key_indicies = {}
455
+ @unordered_value_indicies = {}
456
+ @all_unordered_key_indicies = {}
457
+ @all_unordered_value_indicies = {}
458
+ @entries.each_index do |index|
459
+ key_value_pair = @entries[index]
460
+ key = key_value_pair[0]
461
+ value = key_value_pair[1]
462
+ add_to_index_hashes(index, key, value) unless key.nil?
373
463
  end
374
464
  end
375
465
  end
376
466
  return self
377
467
  end
378
- private :index_void!, :index_void?, :require_index
468
+ private :require_index_hashes
379
469
 
380
470
  # Empties the collection.
381
471
  def clear
382
- @entries = []
383
- @key_to_value = {}
384
- @value_to_key = {}
472
+ synchronize do
473
+ @entries.clear
474
+ rehash
475
+ require_index_hashes
476
+ end
385
477
  return self
386
478
  end
479
+ # Deletes the entry at the given numeric index.
480
+ # Returns the value, or nil if the index is out of bounds.
481
+ def delete_at(index)
482
+ index = index.to_int
483
+ synchronize do
484
+ key_value_pair = @entries.delete_at(index)
485
+ rehash
486
+ return key_value_pair ? key_value_pair[0] : nil
487
+ end
488
+ end
387
489
  # Deletes all entries with a given key.
388
490
  def delete_key(key)
389
491
  key = key.to_udat
390
492
  synchronize do
391
493
  @entries.delete_if { |e_key, e_value| e_key == key }
392
- index_void!
494
+ rehash
393
495
  end
394
496
  return self
395
497
  end
@@ -398,7 +500,7 @@ class UdatCollection < Udat
398
500
  value = value.to_udat
399
501
  synchronize do
400
502
  @entries.delete_if { |e_key, e_value| e_value == value }
401
- index_void!
503
+ rehash
402
504
  end
403
505
  return self
404
506
  end
@@ -408,11 +510,9 @@ class UdatCollection < Udat
408
510
  key = key.to_udat
409
511
  value = value.to_udat
410
512
  synchronize do
513
+ index = @entries.length
411
514
  @entries << [key, value]
412
- unless index_void?
413
- @key_to_value[key] = value unless @key_to_value.has_key? key
414
- @value_to_key[value] = key unless @value_to_key.has_key? value
415
- end
515
+ add_to_index_hashes(index, key, value) unless index_hashes_void?
416
516
  end
417
517
  return self
418
518
  end
@@ -450,7 +550,7 @@ class UdatCollection < Udat
450
550
  value = value.to_ary
451
551
  unless value.length == 2
452
552
  raise ArgumentError,
453
- "#{value.length} array elements found, while 2 were expected."
553
+ "#{value.length} array elements found where 2 were expected."
454
554
  end
455
555
  return append_pair(value[0], value[1])
456
556
  else
@@ -480,20 +580,52 @@ class UdatCollection < Udat
480
580
  return entry ? entry[1] : nil
481
581
  end
482
582
  end
483
- # Returns the first value having a given key.
484
- def value_by_key(key)
583
+ # Returns an Array of values having a given key.
584
+ def values_by_key(key)
585
+ key = key.to_udat
485
586
  synchronize do
486
- require_index
487
- return @key_to_value[key.to_udat]
587
+ require_index_hashes
588
+ if key.kind_of? UdatCollection and key.unordered?
589
+ indicies = @all_unordered_key_indicies[
590
+ UdatUnorderedWrapper.new(key)
591
+ ] || []
592
+ else
593
+ indicies = (@key_indicies[key] || []) + (
594
+ @unordered_key_indicies[UdatUnorderedWrapper.new(key)] || []
595
+ )
596
+ indicies.uniq!
597
+ indicies.sort!
598
+ end
599
+ return indicies.collect { |index| @entries[index][1] }
488
600
  end
489
601
  end
490
- # Returns the first key having a given value.
491
- def key_by_value(value)
602
+ # Returns an Array of keys having a given value.
603
+ def keys_by_value(value)
604
+ value = value.to_udat
492
605
  synchronize do
493
- require_index
494
- @value_to_key[value.to_udat]
606
+ require_index_hashes
607
+ if value.kind_of? UdatCollection and value.unordered?
608
+ indicies = @all_unordered_value_indicies[
609
+ UdatUnorderedWrapper.new(value)
610
+ ] || []
611
+ else
612
+ indicies = (value_indicies[key] || []) + (
613
+ @unordered_value_indicies[UdatUnorderedWrapper.new(value)] || []
614
+ )
615
+ indicies.uniq!
616
+ indicies.sort!
617
+ end
618
+ return indicies.collect { |index| @entries[index][0] }
495
619
  end
496
620
  end
621
+ # Returns the last value having a given key.
622
+ def value_by_key(key)
623
+ values_by_key(key).last
624
+ end
625
+ # Returns the last key having a given value.
626
+ def key_by_value(value)
627
+ keys_by_value(value).last
628
+ end
497
629
 
498
630
  # Behaves differently depending on the type of the argument.
499
631
  # If the argument is an Integer the method behaves same as
@@ -675,7 +807,6 @@ class UdatCollection < Udat
675
807
  # ignored for comparisons.
676
808
  def ordered=(ordered)
677
809
  synchronize do
678
- index_void!
679
810
  if ordered == false
680
811
  @ordered = false
681
812
  else
@@ -704,40 +835,13 @@ class UdatCollection < Udat
704
835
  end
705
836
  end
706
837
 
707
- # Returns a hash key used by ruby's Hash'es.
708
- def hash
709
- hash = 0
710
- @entries.each do |key, value|
711
- hash += key.hash
712
- hash += value.hash
713
- end
714
- return hash
715
- end
716
- # Returns true, if class, tag and content are matching another object.
717
- # The order of the content is ignored, if this or the other object has
718
- # the 'ordered' attribute set to false.
719
- def eql?(other)
720
- if self.class == other.class and self.tag == other.tag
721
- if self.ordered? and other.ordered?
722
- return self.to_s == other.to_s
723
- else
724
- own_pairs = self.key_value_pairs
725
- other_pairs = other.key_value_pairs
726
- own_pairs.each do |own_pair|
727
- catch :found do
728
- other_pairs.each_index do |index|
729
- if own_pair.eql? other_pairs[index]
730
- other_pairs.delete_at index
731
- throw :found
732
- end
733
- end
734
- return false
735
- end
736
- end
737
- return true
738
- end
838
+ def ==(other)
839
+ return false unless self.class == other.class
840
+ if self.unordered? or other.unordered?
841
+ return UdatUnorderedWrapper.new(self) ==
842
+ UdatUnorderedWrapper.new(other)
739
843
  else
740
- return false
844
+ return super
741
845
  end
742
846
  end
743
847
 
@@ -758,6 +862,7 @@ class UdatScalar < Udat
758
862
 
759
863
  # Content of the scalar (a String).
760
864
  attr_reader :content
865
+ # Sets the content. The content is casted to a string.
761
866
  def content=(content)
762
867
  @content = content.to_s
763
868
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: udat
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.0
7
- date: 2007-05-22 00:00:00 +00:00
6
+ version: 1.1.0
7
+ date: 2007-05-23 00:00:00 +00:00
8
8
  summary: Parser and generator for UDAT documents, a generic data format similar to XML or YAML.
9
9
  require_paths:
10
10
  - lib/