Class: SDL4R::Tag

Inherits:
Object
  • Object
show all
Defined in:
lib/sdl4r/tag.rb

Overview

SDL documents are made of Tags.

See the README for a longer explanation on SDL documents.

Do not assume that methods returning sets (Hash, Array, etc) of children/values/attributes/etc in this class returns copies or implementations. It can be one or the other depending on the method. The implementations are designed to be correct and somewhat efficient, not too protect the Tag internal state from ill-use of the returned values.

Authors

Daniel Leuck, Philippe Vosges

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(namespace, name = nil, &block) ⇒ Tag

Creates an empty tag in the given namespace. If the namespace is nil it will be coerced to an empty String.

tag = Tag.new("name")
tag = Tag.new("namespace", "name")

tag = Tag.new("fruit") do
  add_value 2
  new_child("orange") do
    set_attribute("quantity", 2)
  end
end

which builds the following SDL structure

fruit 2 {
  orange quantity=2
}

If you provide a block that takes an argument, you will write the same example, as follows:

tag = Tag.new("fruit") do |t|
  t.add_value 2
  t.new_child("orange") do
    set_attribute("quantity", 2)
  end
end

In this case, the current context is not the new Tag anymore but the context of your code.

Raises

ArgumentError if the name is not a legal SDL identifier (see SDL4R#validate_identifier) or the namespace is non-blank and is not a legal SDL identifier.

Raises:

  • (ArgumentError)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/sdl4r/tag.rb', line 106

def initialize(namespace, name = nil, &block)
  namespace, name = to_nns namespace, name

  raise ArgumentError, "tag namespace must be a String" unless namespace.is_a? String
  raise ArgumentError, "tag name must be a String" unless name.is_a? String

  SDL4R.validate_identifier(namespace) unless namespace.empty?
  @namespace = namespace
  
  name = name.to_s.strip
  raise ArgumentError, "Tag name cannot be nil or empty" if name.empty?
  SDL4R.validate_identifier(name)
  @name = name
  
  @children = []
  @values = []
  
  # a Hash of Hash : {namespace => {name => value}}
  # The default namespace is represented by an empty string.
  @attributesByNamespace = {}

  if block_given?
    if block.arity > 0
      block[self]
    else
      instance_eval(&block)
    end
  end
end

Instance Attribute Details

#nameObject

the name of this Tag



47
48
49
# File 'lib/sdl4r/tag.rb', line 47

def name
  @name
end

#namespaceObject

the namespace of this Tag or an empty string when there is no namespace (i.e. default namespace).



52
53
54
# File 'lib/sdl4r/tag.rb', line 52

def namespace
  @namespace
end

Instance Method Details

#<<(o) ⇒ Object

Adds the given object as a child if it is a Tag, as an attribute if it is a Hash => value (supports namespaces), or as a value otherwise. If it is an Enumerable (e.g. Array), each of its elements is added to this Tag via this operator. If any of its elements is itself an Enumerable, then an anonymous tag is created and the Enumerable is passed to it via this operator (see the examples below).

tag << Tag.new("child")
tag << 123                          # new integer value
tag << "islamabad"                  # new string value
tag << { "metric:length" => 1027 }  # new attribute (with namespace)
tag << [nil, 456, "abc"]            # several values added

tag = Tag.new("tag")
tag << [[1, 2, 3], [4, 5, 6]]       # tag {
                                    #   1 2 3
                                    #   4 5 6
                                    # }

Of course, despite the fact that String is an Enumerable, it is considered as the type of values.

Returns self.

Use other accessors (#add_child, #add_value, #attributes, etc) for a stricter and less “magical” behavior.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/sdl4r/tag.rb', line 198

def <<(o)
  if o.is_a?(Tag)
    add_child(o)
  elsif o.is_a?(Hash)
    o.each_pair { |key, value|
      namespace, key = key.split(/:/) if key.match(/:/)
      namespace ||= ""
      set_attribute(namespace, key, value)
    }
  elsif o.is_a? String
    add_value(o)
  elsif o.is_a? Enumerable
    o.each { |item|
      if item.is_a? Enumerable and not item.is_a? String
        anonymous = new_child(ANONYMOUS_TAG_NAME)
        anonymous << item
      else
        self << item
      end
    }
  else
    add_value(o)
  end
  return self
end

#add_child(child) ⇒ Object

Add a child to this Tag.

child

The child to add

Returns the added child.



167
168
169
170
# File 'lib/sdl4r/tag.rb', line 167

def add_child(child)
  @children.push(child)
  return child
end

#add_value(v) ⇒ Object

Adds a value to this Tag. See SDL4R#coerce_or_fail to know about the allowable types.

v

The value to add

Raises an ArgumentError if the value is not a legal SDL type



440
441
442
443
# File 'lib/sdl4r/tag.rb', line 440

def add_value(v)
  @values.push(SDL4R::coerce_or_fail(v))
  return nil
end

#attribute(namespace, key = nil) ⇒ Object

attribute(key)

attribute(namespace, key)

Returns the attribute of the specified namespace of specified key or nil if not found.



546
547
548
549
550
# File 'lib/sdl4r/tag.rb', line 546

def attribute(namespace, key = nil)
  namespace, key = to_nns namespace, key
  attributes = @attributesByNamespace[namespace]
  return attributes.nil? ? nil : attributes[key]
end

#attributes(namespace = nil, &block) ⇒ Object

Returns a Hash of the attributes of the specified namespace (default is all) or enumerates them.

tag.attributes # => { "length" => 123, "width" = 25.4, "orig:color" => "gray" }
tag.attributes("orig") do |namespace, key, value|
  p "#{namespace}:#{key} = #{value}"
end
namespace

namespace of the returned attributes. If nil, all attributes are returned with qualified names (e.g. “meat:color”). If “”, attributes of the default namespace are returned.



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'lib/sdl4r/tag.rb', line 584

def attributes(namespace = nil, &block) # :yields: namespace, key, value
  if block_given?
    each_attribute(namespace, &block)
    
  else
    if namespace.nil?
      hash = {}

      each_attribute do | namespace, key, value |
        qualified_name = namespace.empty? ? key : namespace + ':' + key
        hash[qualified_name] = value
      end

      return hash

    else
      return @attributesByNamespace[namespace]
    end
  end
end

#attributes=(attribute_hash) ⇒ Object

Sets all the attributes of the default namespace for this Tag in one operation.

See #set_attributes.



687
688
689
# File 'lib/sdl4r/tag.rb', line 687

def attributes=(attribute_hash)
  set_attributes(attribute_hash)
end

#child(recursive = false, name = nil) ⇒ Object

child

child(name)
child(recursive, name)

Get the first child with the given name, optionally using a recursive search.

name

the name of the child Tag. If nil, the first child is returned (nil if there are

no children at all).

Returns the first child tag having the given name or nil if no such child exists



344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/sdl4r/tag.rb', line 344

def child(recursive = false, name = nil)
  if name.nil?
    name = recursive
    recursive = false
  end
  
  unless name
    return @children.first
  else
    each_child(recursive, name) { |child| return child }
  end
end

#child_countObject

Returns the number of children Tag.



265
266
267
# File 'lib/sdl4r/tag.rb', line 265

def child_count
  @children.size
end

#children(recursive = false, namespace = nil, name = :DEFAULT, &block) ⇒ Object

children(recursive)

children(recursive, name)
children(recursive, namespace, name)

children(recursive) { |child| ... }
children(recursive, name) { |child| ... }
children(recursive, namespace, name) { |child| ... }

Returns an Array of the children Tags of this Tag or enumerates them.

recursive

if true children and all descendants will be returned. False by default.

name

if not nil, only children having this name will be returned. Nil by default.

namespace

use nil for all namespaces and “” for the default one. Nil by default.

tag.children # => array of the children
tag.children(true) { |descendant| ... }

tag.children(false, "name") # => children of name "name"
tag.children(false, "ns", nil) # => children of namespace "ns"


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/sdl4r/tag.rb', line 289

def children(recursive = false, namespace = nil, name = :DEFAULT, &block) # :yields: child
  if name == :DEFAULT
    name = namespace
    namespace = nil
  end

  if block_given?
    each_child(recursive, namespace, name, &block)
    return nil
    
  else
    unless recursive or name or namespace
      return @children
      
    else
      result = []
      each_child(recursive, namespace, name) { |child|
        result << child
      }
      return result
    end
  end
end

#children_to_string(line_prefix = "", s = "") ⇒ Object

Returns a string representation of the children tags.

linePrefix

A prefix to insert before every line.

s

a String that receives the string representation

TODO: break up long lines using the backslash



861
862
863
864
865
866
867
# File 'lib/sdl4r/tag.rb', line 861

def children_to_string(line_prefix = "", s = "")
  @children.each do |child|
    s << child.to_string(line_prefix) << $/
  end
  
  return s
end

#children_values(name = nil) ⇒ Object

Returns the values of all the children with the given name. If the child has more than one value, all the values will be added as an array. If the child has no value, nil will be added. The search is not recursive.

name

if nil, all children are considered (nil by default).



318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/sdl4r/tag.rb', line 318

def children_values(name = nil)
  children_values = []
  each_child(false, name) { |child|
    case child.values.size
    when 0
      children_values << nil
    when 1
      children_values << child.value
    else
      children_values << child.values
    end
  }
  return children_values
end

#clear_attributes(namespace = nil) ⇒ Object

Clears the attributes of the specified namespace or all the attributes if namespace is nil.



624
625
626
627
628
629
630
# File 'lib/sdl4r/tag.rb', line 624

def clear_attributes(namespace = nil)
  if namespace.nil?
    @attributesByNamespace.clear
  else
    @attributesByNamespace.delete(namespace)
  end
end

#clear_childrenObject

Removes all children.



236
237
238
239
# File 'lib/sdl4r/tag.rb', line 236

def clear_children
  @children = []
  nil
end

#clear_valuesObject

Removes all values.



468
469
470
471
# File 'lib/sdl4r/tag.rb', line 468

def clear_values
  @values = []
  nil
end

#eql?(o) ⇒ Boolean Also known as: ==

Returns true if this tag (including all of its values, attributes, and children) is equivalent to the given tag.

Returns true if the tags are equivalet

Returns:

  • (Boolean)


874
875
876
877
# File 'lib/sdl4r/tag.rb', line 874

def eql?(o)
  # this is safe because to_string() dumps the full state
  return o.is_a?(Tag) && o.to_string == to_string;
end

#has_attribute?(namespace = nil, key = nil) ⇒ Boolean

Indicates whether there is at least an attribute in this Tag.

has_attribute?

Indicates whether there is the specified attribute exists in this Tag.

has_attribute?(key)
has_attribute?(namespace, key)

Returns:

  • (Boolean)


559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/sdl4r/tag.rb', line 559

def has_attribute?(namespace = nil, key = nil)
  namespace, key = to_nns namespace, key

  if namespace or key
    attributes = @attributesByNamespace[namespace]
    return attributes.nil? ? false : attributes.has_key?(key)

  else
    attributes { return true }
    return false
  end
end

#has_child?(name) ⇒ Boolean

Indicates whether the child Tag of given name exists.

name

name of the searched child Tag

Returns:

  • (Boolean)


361
362
363
# File 'lib/sdl4r/tag.rb', line 361

def has_child?(name)
  !child(name).nil?
end

#has_children?Boolean

Indicates whether there are children Tag.

Returns:

  • (Boolean)


367
368
369
# File 'lib/sdl4r/tag.rb', line 367

def has_children?
  !@children.empty?
end

#has_value?(v) ⇒ Boolean

Returns true if v is a value of this Tag’s.

Returns:

  • (Boolean)


447
448
449
# File 'lib/sdl4r/tag.rb', line 447

def has_value?(v)
  @values.include?(v)
end

#hashObject

Returns The hash (based on the output from toString())



882
883
884
# File 'lib/sdl4r/tag.rb', line 882

def hash
  return to_string.hash
end

#new_child(*args, &block) ⇒ Object

Creates a new child tag. Can take a block so that you can write something like:

car = Tag.new("car") do
  new_child("wheels") do
    self << 4
  end
end

The context of execution of the given block is the child instance. If you provide a block that takes a parameter (see below), the context is the context of your code:

car = Tag.new("car") do |child|
  child.new_child("wheels") do |grandchild|
    grandchild << 4
  end
end

Returns the created child Tag.



157
158
159
# File 'lib/sdl4r/tag.rb', line 157

def new_child(*args, &block)
  return add_child Tag.new(*args, &block)
end

#read(input) ⇒ Object

Adds all the tags specified in the given IO, String, Pathname or URI to this Tag.

Returns this Tag after adding all the children read from input.



717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/sdl4r/tag.rb', line 717

def read(input)
  if input.is_a? String
    read_from_io(true) { StringIO.new(input) }

  elsif input.is_a? Pathname
    read_from_io(true) { input.open("r:UTF-8") }

  elsif input.is_a? URI
    read_from_io(true) { input.open }

  else
    read_from_io(false) { input }
  end
  
  return self
end

#read_from_io(close_io) ⇒ Object

Reads and parses the io returned by the specified block and closes this io if close_io is true.



736
737
738
739
740
741
742
743
744
745
746
747
748
749
# File 'lib/sdl4r/tag.rb', line 736

def read_from_io(close_io)
  io = yield

  begin
    Parser.new(io).parse.each do |tag|
      add_child(tag)
    end

  ensure
    if close_io
      io.close rescue IOError
    end
  end
end

#remove_attribute(namespace, key = nil) ⇒ Object

remove_attribute(key)

remove_attribute(namespace, key)

Removes the attribute, whose name and namespace are specified.

key

name of the removed atribute

namespace

namespace of the removed attribute (equal to “”, default namespace, by default)

Returns the value of the removed attribute or nil if it didn’t exist.



615
616
617
618
619
# File 'lib/sdl4r/tag.rb', line 615

def remove_attribute(namespace, key = nil)
  namespace, key = to_nns namespace, key
  attributes = @attributesByNamespace[namespace]
  return attributes.nil? ? nil : attributes.delete(key)
end

#remove_child(child) ⇒ Object

Remove a child from this Tag

child

the child to remove

Returns true if the child exists and is removed



230
231
232
# File 'lib/sdl4r/tag.rb', line 230

def remove_child(child)
  return !@children.delete(child).nil?
end

#remove_value(v) ⇒ Object

Removes the first occurence of the specified value from this Tag.

v

The value to remove

Returns true If the value exists and is removed



457
458
459
460
461
462
463
464
# File 'lib/sdl4r/tag.rb', line 457

def remove_value(v)
  index = @values.index(v)
  if index
    return !@values.delete_at(index).nil?
  else
    return false
  end
end

#set_attribute(namespace, key, value = :default) ⇒ Object

set_attribute(key, value)

set_attribute(namespace, key, value)

Set an attribute in the given namespace for this tag. The allowable attribute value types are the same as those allowed for #add_value.

namespace

The namespace for this attribute

key

The attribute key

value

The attribute value

Raises ArgumentError if the key is not a legal SDL identifier (see SDL4R#validate_identifier), or the namespace is non-blank and is not a legal SDL identifier, or thevalue is not a legal SDL type

Raises:

  • (ArgumentError)


516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/sdl4r/tag.rb', line 516

def set_attribute(namespace, key, value = :default)
  if value == :default
    value = key
    key = namespace
    namespace = ""
  end

  raise ArgumentError, "attribute namespace must be a String" unless namespace.is_a? String
  raise ArgumentError, "attribute key must be a String" unless key.is_a? String
  raise ArgumentError, "attribute key cannot be empty" if key.empty?

  SDL4R.validate_identifier(namespace) unless namespace.empty?
  SDL4R.validate_identifier(key)

  attributes = @attributesByNamespace[namespace]
  
  if attributes.nil?
    attributes = {}
    @attributesByNamespace[namespace] = attributes
  end
  
  attributes[key] = SDL4R.coerce_or_fail(value)
end

#set_attributes(namespace, attribute_hash = nil) ⇒ Object

set_attributes(attribute_hash)

set_attributes(namespace, attribute_hash)

Sets the attributes specified by a Hash in the given namespace in one operation. The previous attributes of the specified namespace are removed. See #set_attribute for allowable attribute value types.

attributes

a Hash where keys are attribute keys

namespace

“” (default namespace) by default

Raises an ArgumentError if any key in the map is not a legal SDL identifier (see SDL4R#validate_identifier), or any value is not a legal SDL type.

Raises:

  • (ArgumentError)


664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/sdl4r/tag.rb', line 664

def set_attributes(namespace, attribute_hash = nil)
  if attribute_hash.nil?
    attribute_hash = namespace
    namespace = ""
  end
  
  raise ArgumentError, "namespace can't be nil" if namespace.nil?
  raise ArgumentError, "attribute_hash should be a Hash" unless attribute_hash.is_a? Hash

  namespace_attributes = @attributesByNamespace[namespace]
  namespace_attributes.clear if namespace_attributes
  
  attribute_hash.each_pair do |key, value|
      # Calling set_attribute() is required to ensure validations
      set_attribute(namespace, key, value)
  end
end

#to_child_hashObject

Returns a new Hash where the children’s names as keys and their values as the key’s value. Example:

child1 "toto"
child2 2

would give

{ "child1" => "toto", "child2" => 2 }


406
407
408
409
410
# File 'lib/sdl4r/tag.rb', line 406

def to_child_hash
  hash = {}
  children { |child| hash[child.name] = child.value }
  return hash
end

#to_child_string_hashObject

Returns a new Hash where the children’s names as keys and their values as the key’s value. Values are converted to Strings. nil values become empty Strings. Example:

child1 "toto"
child2 2
child3 null

would give

{ "child1" => "toto", "child2" => "2", "child3" => "" }


424
425
426
427
428
429
430
431
432
# File 'lib/sdl4r/tag.rb', line 424

def to_child_string_hash
  hash = {}
  children do |child|
    # FIXME: it is quite hard to be sure whether we should mimic the Java version
    # as there might be a lot of values that don't translate nicely to Strings.
    hash[child.name] = child.value.to_s
  end
  return hash
end

#to_sObject

Get a String representation of this SDL Tag. This method returns a complete description of the Tag’s state using SDL (i.e. the output can be parsed by #read)

Returns A string representation of this tag using SDL



795
796
797
# File 'lib/sdl4r/tag.rb', line 795

def to_s
  to_string
end

#to_string(line_prefix = "", indent = "\t") ⇒ Object

linePrefix

A prefix to insert before every line.

Returns A string representation of this tag using SDL

TODO: break up long lines using the backslash



804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
# File 'lib/sdl4r/tag.rb', line 804

def to_string(line_prefix = "", indent = "\t")
  line_prefix = "" if line_prefix.nil?
  s = ""
  s << line_prefix
  
  if name == ANONYMOUS_TAG_NAME && namespace.empty?
    skip_value_space = true
  else
    skip_value_space = false
    s << "#{namespace}:" unless namespace.empty?
    s << name
  end

  # output values
  values do |value|
    if skip_value_space
      skip_value_space = false
    else
      s << " "
    end
    s << SDL4R.format(value, true, line_prefix, indent)
  end

  # output attributes
  unless @attributesByNamespace.empty?
    all_attributes_hash = attributes
    all_attributes_array = all_attributes_hash.sort { |a, b|
      namespace1, name1 = a[0].split(':')
      namespace1, name1 = "", namespace1 if name1.nil?
      namespace2, name2 = b[0].split(':')
      namespace2, name2 = "", namespace2 if name2.nil?

      diff = namespace1 <=> namespace2
      diff == 0 ? name1 <=> name2 : diff
    }
    all_attributes_array.each do |attribute_name, attribute_value|
      s << " " << attribute_name << '=' << SDL4R.format(attribute_value, true)
    end
  end

  # output children
  unless @children.empty?
    s << " {#{$/}"
    children_to_string(line_prefix + indent, s)
    s << line_prefix << ?}
  end

  return s
end

#to_xml_string(options = {}) ⇒ Object

Returns a string containing an XML representation of this tag. Values will be represented using _val0, _val1, etc.

options

a hash of the options

options:

:line_prefix

a text prefixing each line (default: “”)

:uri_by_namespace

a Hash giving the URIs for the namespaces

:indent

text specifying one indentation (default: “t”)

:eol

end of line expression (default: “n”)

:omit_null_attributes

if true, null/nil attributes are not exported (default: false). Otherwise, they are exported as follows:

tag attr="null"


902
903
904
905
906
907
908
909
910
911
# File 'lib/sdl4r/tag.rb', line 902

def to_xml_string(options = {})
  options = {
    :uri_by_namespace => nil,
    :indent => "\t",
    :line_prefix => "",
    :eol => "\n",
    :omit_null_attributes => false
  }.merge(options)
  _to_xml_string(options[:line_prefix], options)
end

#valueObject

A convenience method that returns the first value.



259
260
261
# File 'lib/sdl4r/tag.rb', line 259

def value
  @values[0]
end

#value=(value) ⇒ Object

A convenience method that sets the first value in the value list. See # #add_value for legal types.

value

The value to be set.

Raises

ArgumentError

if the value is not a legal SDL type



251
252
253
254
# File 'lib/sdl4r/tag.rb', line 251

def value=(value)
  @values[0] = SDL4R.coerce_or_fail(value)
  nil
end

#valuesObject

Returns an Array of the values of this Tag or enumerates them.

tag.values # => [123, "spices"]
tag.values { |value| puts value }


478
479
480
481
482
483
484
485
# File 'lib/sdl4r/tag.rb', line 478

def values # :yields: value
  if block_given?
    @values.each { |v| yield v }
    nil
  else
    return @values
  end
end

#values=(someValues) ⇒ Object

Set the values for this tag. See #add_value for legal value types.

values

The new values

Raises an ArgumentError if the collection contains any values which are not legal SDL types.



493
494
495
496
497
498
499
500
# File 'lib/sdl4r/tag.rb', line 493

def values=(someValues)
  @values.clear()
  someValues.to_a.each { |v|
    # this is required to ensure validation of types
    add_value(v)
  }
  nil
end

#write(output, include_root = false) ⇒ Object

Write this tag out to the given IO or StringIO or String (optionally clipping the root.) Returns output.

output

an IO or StringIO or a String to write to

include_root

if true this tag will be written out as the root element, if false only the

children will be written. False by default.


759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
# File 'lib/sdl4r/tag.rb', line 759

def write(output, include_root = false)
  if output.is_a? String
    io = StringIO.new(output)
    close_io = true # indicates we close the IO ourselves
  elsif output.is_a? IO or output.is_a? StringIO
    io = output
    close_io = false # let the caller close the IO
  else
    raise ArgumentError, "'output' should be a String or an IO but was #{output.class}"
  end
  
  if include_root
    io << to_s
  else
    first = true
    children do |child|
      if first
        first = false
      else
        io << $/
      end
      io << child.to_s
    end
  end
  
  io.close() if close_io

  output
end