udat 1.0.0 → 1.1.0

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