class MiGA::Taxonomy

Taxonomic classifications in MiGA.
#

def <<(value)

described in #initialize.
Add +value+ to the hierarchy, that can be an Array, a String, or a Hash, as
#
def <<(value)
  case value
  when Hash
    value.each do |r, n|
      next if n.nil? || n == ''
      @ranks[self.class.normalize_rank(r)] = n.tr('_', ' ')
    end
  when Array
    value.each { |v| self << v }
  when String
    self << Hash[*value.split(':', 2)]
  else
    raise 'Unsupported class: ' + value.class.name
  end
end

def KNOWN_RANKS()

def KNOWN_RANKS()
  @@KNOWN_RANKS
end

def LONG_RANKS()

def LONG_RANKS()
  @@LONG_RANKS
end

def [](rank)

Get +rank+ value.
#
def [](rank)
  @ranks[rank.to_sym]
end

def add_alternative(tax, replace = true)

the alternative (or master) is replaced instead if +replace+ is true.
Add an alternative taxonomy. If the namespace matches an existing namespace,
#
def add_alternative(tax, replace = true)
  return if tax.nil?
  raise 'Unsupported taxonomy class.' unless tax.is_a? MiGA::Taxonomy
  alt_ns = alternative(tax.namespace)
  if !replace || tax.namespace.nil? || alt_ns.nil?
    @alt << tax
  else
    alt_ns.reset(tax.to_s)
  end
end

def alternative(which = nil)

In the latter two cases it can be nil.
String), including the master taxonomy.
- Otherwise, returns the first taxonomy with namespace +which+ (coerced as
(starting with 0, the master taxonomy).
- If +which+ is Integer, returns the indexed taxonomy
(not including the master taxonomy).
- If +which+ is nil (default), returns all alternative taxonomies as Array
Get the alternative taxonomies.
#
def alternative(which = nil)
  case which
  when nil
    @alt
  when Integer
    ([self] + @alt)[which]
  else
    ([self] + @alt).find { |i| i.namespace.to_s == which.to_s }
  end
end

def delete_alternative

Removes (and returns) all alternative taxonomies.
#
def delete_alternative
  alt = @alt.dup
  @alt = []
  alt
end

def domain

Domain of the taxonomy (a String) or +nil+
#
def domain
  self[:d]
end

def dup

Generate a duplicate of the current object
#
def dup
  self.class.new(to_s, nil, alternative.map(&:dup))
end

def highest(force_ranks = false)

even if undefined.
If +force_ranks+ is true, it always returns the value for domain (d)
Get the most general rank as a two-entry Array (rank and value).
#
def highest(force_ranks = false)
  sorted_ranks(force_ranks).first
end

def in?(taxon)

only has one informative rank. The evaluation is case-insensitive.
Evaluates if the loaded taxonomy includes +taxon+. It assumes that +taxon+
#
def in?(taxon)
  r = taxon.ranks.keys.first
  return false if self[r].nil?
  self[r].casecmp(taxon[r]).zero?
end

def initialize(str, ranks = nil, alt = [])

allowed).
String, Array, or Hash entries as defined above (except +ranks+ are not
value pairs is also supported. If +alt+ is passed, it must be an Array of
order as ther ranks in +ranks+. Alternatively, +str+ as a Hash with rank =>
either a rank:value pair (if +ranks+ is nil), or just values in the same
space-delimited entries, the array is a vector of entries. Each entry can be
Create MiGA::Taxonomy from String or Array +str+. The string is a series of
#
def initialize(str, ranks = nil, alt = [])
  reset(str, ranks)
  @alt = (alt || []).map { |i| Taxonomy.new(i) }
end

def initialize_by_ranks(str, ranks)

def initialize_by_ranks(str, ranks)
  ranks = ranks.split(/\s+/) unless ranks.is_a? Array
  str = str.split(/\s+/) unless str.is_a? Array
  unless ranks.size == str.size
    raise "Unequal number of ranks and names: #{ranks} => #{str}"
  end
  str.each_with_index { |i, k| self << "#{ranks[k]}:#{i}" }
end

def initialize_by_str(str)

def initialize_by_str(str)
  case str
  when Array, Hash
    self << str
  else
    "#{str} ".scan(/([A-Za-z]+):([^:]*)( )/) { |r, n, _| self << { r => n } }
  end
end

def json_create(o)

Initialize from JSON-derived Hash +o+
#
def json_create(o)
  new(o['str'], nil, o['alt'])
end

def lowest(force_ranks = false)

even if undefined.
If +force_ranks+ is true, it always returns the value for dataset (ds)
Get the most specific rank as a two-entry Array (rank and value).
#
def lowest(force_ranks = false)
  sorted_ranks(force_ranks).last
end

def namespace

Namespace of the taxonomy (a String) or +nil+.
#
def namespace
  self[:ns]
end

def normalize_rank(rank)

Returns cannonical rank (Symbol) for the +rank+ String
#
def normalize_rank(rank)
  return unless rank
  return rank.to_sym if @@_KNOWN_RANKS_H[rank.to_sym]
  rank = rank.to_s.downcase
  return if rank == 'no rank'
  rank = @@RANK_SYNONYMS[rank] unless @@RANK_SYNONYMS[rank].nil?
  rank = rank.to_sym
  return unless @@_KNOWN_RANKS_H[rank]
  rank
end

def reset(str, ranks = nil)

See #initialize for +str+ and +ranks+.
Reset ranks (including namespace) while leaving alternatives untouched.
#
def reset(str, ranks = nil)
  @ranks = {}
  if ranks.nil?
    initialize_by_str(str)
  else
    initialize_by_ranks(str, ranks)
  end
end

def sorted_ranks(force_ranks = false, with_namespace = false)

If +with_namespace+ is true, it includes also the namespace.
If +force_ranks+ is true, it returns all standard ranks even if undefined.
Sorted list of ranks, as an Array of two-entry Arrays (rank and value).
#
def sorted_ranks(force_ranks = false, with_namespace = false)
  @@KNOWN_RANKS.map do |r|
    next if
      (r == :ns && !with_namespace) || (ranks[r].nil? && !force_ranks)
    [r, ranks[r]]
  end.compact
end

def to_json(*a)

Generate JSON-formated String representing the taxonomy.
#
def to_json(*a)
  hsh = { JSON.create_id => self.class.name, 'str' => to_s }
  hsh['alt'] = alternative.map(&:to_s) unless alternative.empty?
  hsh.to_json(*a)
end

def to_s(force_ranks = false)

it returns all the standard ranks even if undefined.
Generate cannonical String for the taxonomy. If +force_ranks+ is true,
#
def to_s(force_ranks = false)
  sorted_ranks(force_ranks, true)
    .map { |r| "#{r[0]}:#{(r[1] || '').gsub(/[\s:]/, '_')}" }.join(' ')
end