princelab/mspire

View on GitHub
lib/mspire/paramable.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'cv/param'
require 'mspire/user_param'
require 'mspire/cv/param'
require 'nokogiri'
require 'andand'

module Mspire
  module Paramable

    attr_accessor :cv_params
    attr_accessor :user_params
    attr_accessor :ref_param_groups

    def params
      cv_params + ref_param_groups.flat_map(&:params) + user_params 
    end

    def each_param(&block)
      return enum_for __method__ unless block
      cv_params.each(&block)
      ref_param_groups.flat_map(&:params).each(&block)
      user_params.each(&block)
      nil
    end

    def params?
      cv_params.size > 0 || 
        ref_param_groups.any? {|group| group.params.size > 0 } || 
        user_params.size > 0
    end

    def each_accessionable_param(&block)
      return enum_for __method__ unless block
      cv_params.each(&block)
      ref_param_groups.flat_map(&:params).each(&block)
      nil
    end

    def accessionable_params
      cv_params + ref_param_groups.flat_map(&:params)
    end

    # yields each current param.  If the return value is not false or nil,
    # it is deleted (i.e., any true value and it is deleted).  Then adds the
    # given parameter or makes a new one by accession number.
    def replace!(*describe_args, &block)
      reject!(&block).describe!(*describe_args)
    end

    # returns self
    def reject!(&block)
      cv_params.reject!(&block)
      ref_param_groups.each {|group| group.reject!(&block) }
      user_params.reject!(&block)
      self
    end

    def replace_many!(describe_many_arg, &block)
      reject!(&block).describe_many!(describe_many_arg)
    end

    #def params_by_name
    #  params.index_by &:name
    #end

    #def params_by_accession
    #  accessionable_params.index_by &:accession
    #end

    # returns the value if the param exists by that name.  Returns true if
    # the param exists but has no value. returns false if no param
    def fetch(name)
      param = each_param.find {|param| param.name == name}
      if param
        param.value || true
      else
        false
      end
    end

    # returns the value if the param exists with that accession.  Returns
    # true if the param exists but has no value. returns false if no param
    # with that accession.
    def fetch_by_accession(acc)
      param = accessionable_params.find {|v| v.accession == acc }
      if param
        param.value || true
      else
        false
      end
    end
    alias_method :fetch_by_acc, :fetch_by_accession

    def param?(name)
      params.any? {|param| param.name == name }
    end

    def initialize
      @cv_params = []
      @user_params = []
      @ref_param_groups = []
    end
    alias_method :params_init, :initialize

    def param_by_accession(acc)
      each_accessionable_param.find {|v| v.accession == acc }
    end
    alias_method :param_by_acc, :param_by_accession

    # takes an array of values, each of which is fed into describe!
    # returns self.
    def describe_many!(array)
      array.each do |arg|
        if arg.is_a?(Array)
          describe!(*arg)
        else
          describe!(arg)
        end
      end
      self
    end

    # reads the paramable nodes and returns self.  Use this if your element
    # does not have anything besides paramable elements.
    def describe_self_from_xml!(xml_node, ref_hash=nil)
      describe_from_xml!(xml_node, ref_hash)
      self
    end

    # takes a node with children that are cvParam, userParam or
    # referenceableParamGroupRef and a hash containing
    # referenceableParamGroup objects indexed by id.  The only time ref_hash
    # should be left nil is for the referenceableParamGroup itself.
    #
    # All param elements are required to appear before other elements, so
    # the code walks through each child node to see if it is a paramable
    # element.  The first child node that is not paramable is returned (or
    # nil if none)
    #
    # returns the next child node after the paramable elements or nil if none
    def describe_from_xml!(xml_node, ref_hash=nil)
      return nil unless (child_n = xml_node.child) 
      loop do
        array = 
          case child_n.name
          when 'referenceableParamGroupRef'
            @ref_param_groups << ref_hash[child_n[:ref]]
          when 'cvParam'
            @cv_params << Mspire::CV::Param[ child_n[:accession], child_n[:value] ]
          when 'userParam'
            @user_params << Mspire::UserParam.new(child_n[:name], child_n[:value], child_n[:type])
          else # assumes that the above precede any following children as per the spec
            break 
          end
        if (unit_acc = child_n[:unitAccession])
          array.last.unit = ::CV::Param.new(child_n[:unitCvRef], unit_acc, child_n[:unitName])
        end
        break unless child_n = child_n.next
      end
      child_n
    end

    # Expects arguments describing a single CV::Param, Mspire::UserParam, or
    # Mspire::Mzml::ReferenceableParamGroup
    #
    #     obj.describe! 'MS:1000130'  # a positive scan
    #     obj.describe! CV::Param['MS:1000130']  # same behavior
    #
    #     # base peak intensity, units=number of counts
    #     obj.describe! "MS:1000505", 1524.5865478515625, 'MS:1000131'
    #
    # returns self
    def describe!(*args)
      return self if args.first.nil?
      case (arg=args.first)
      when String
        @cv_params << Mspire::CV::Param[ *args ]
      when Mspire::Mzml::ReferenceableParamGroup
        @ref_param_groups << arg
      else
        if arg.is_a?(Mspire::UserParam)
          @user_params << arg
        else
          @cv_params << arg
        end
      end
      self
    end

    # iterates over @params and calls .to_xml on each object.
    def to_xml(xml)
      [:ref_param_groups, :cv_params, :user_params].each do |kind|
        self.send(kind).each do |obj|
          obj.to_xml(xml)
        end
      end
      xml
    end

  end
end