require 'rbbt/entity' module Link extend Entity def self.tsv_sort(v) value = v.last value = value.first if Array === value and value.length == 1 if String === value and value.match(/<a [^>]*>([^>]*)<\/a>/) val = $1 if val =~ /^\s*\d/ val.to_f else 1 end elsif Array === value value.length else 0 end end property :name => :single2array do if String === self and m=self.match(/<a [^>]*>([^>]*)<\/a>/) m[1] else self end end end module Change extend Entity self.format =(<<-EOF Change EOF ).split("\n") def <=>(other) self.scan(/\d+/).first.to_f <=> other.scan(/\d+/).first.to_f end def self.tsv_sort(v) value = v.last if Array === value value.first.scan(/\d+/).first.to_f else value.scan(/\d+/).first.to_f end end end module Location extend Entity self.format =(<<-EOF Location EOF ).split("\n") def <=>(other) self.to_s.split(":").first.to_f <=> other.to_s.split(":").first.to_f end def self.tsv_sort(v) value = v.last if Array === value value.first.to_s.split(":").first.to_f else value.to_s.split(":").first.to_f end end end module NumericValue extend Entity self.format =(<<-EOF p-value p.value P-value P.value p-values p.values P-values P.values score scores ratio ratios t.value t.values adjusted.p.value adjusted.p.values Rank rank Counts Ratio Size size Matches Quality EOF ).split("\n") def invalid? self == "NA" or self == "NaN" end def <=>(other) if Float === self super(other.to_f) else v1 = self.to_f v2 = other.to_f v1 <=> v2 end end def self.tsv_sort(v) value = v.last if Array === value value.first.to_f else value.to_f end end def to_s self.invalid? ? self : "%.5g" % self.to_f end end module RbbtRESTHelpers def tsv_rows_full(tsv) case tsv.type when :single tsv.collect{|id, value| [id, value]} when :list key_field = tsv.key_field tsv.collect{|id, values| new_values = [id].concat(values); begin NamedArray.setup(new_values, values.fields, id, values.entity_options, values.entity_templates); new_values.fields = [key_field].concat values.fields end if values.respond_to? :fields; new_values } when :flat key_field = tsv.key_field tsv.collect{|id, values| [id, values]} when :double key_field = tsv.key_field tsv.collect{|id, value_lists| new_value_lists = [id].concat(value_lists); begin NamedArray.setup(new_value_lists, value_lists.fields, id, value_lists.entity_options, value_lists.entity_templates); new_value_lists.fields = ([key_field].concat value_lists.fields) end if value_lists.respond_to? :fields; new_value_lists } end end def parse_page(page) num, size, field = page.split("~").values_at 0, 1, 2 field, size = size, nil if field.nil? field = "key" if field.nil? or field.empty? size = PAGE_SIZE if size.nil? or size.empty? [num, size, field] end def paginate(object, page = nil, just_keys = false) return object unless TSV === object and not page.nil? return object if page == "all" or page.nil? num, size, field = parse_page(page) if field and field[0] == "-"[0] field = field[1..-1] reverse = true else reverse = false end field = CGI.unescapeHTML(Entity::REST.restore_element(field)) if object.entity_templates[field] entity = object.entity_templates[field].annotation_types.last else entity = Entity.formats[field] end num = num.to_i size = size.to_i max = (object.size / size) + 1 num = max if num > max num = - max if num < - max object.with_unnamed do if entity and entity.respond_to? :tsv_sort object.page(num, size, field, false, reverse, &entity.method(:tsv_sort)) else object.page(num, size, field, false, reverse) end end end def tsv_process(tsv, filter = nil, column = nil) filter = @filter if filter.nil? column = @column if column.nil? if filter and filter.to_s != "false" filter.split(";;").each do |f| key, value = f.split("~") next if value.nil? or value.empty? value = Entity::REST.restore_element(value) #invert if value =~ /^!\s*(.*)/ value = $1 invert = true else invert = false end #name if value =~ /^:name:\s*(.*)/ value = $1 name = true else name = false end if value =~ /^:length:\s*(.*)/ value = $1 length = true else length = false end if name old_tsv = tsv tsv = tsv.reorder(:key, key).add_field "NAME" do |k,values| NamedArray === values ? values[key].name : values.name end key = "NAME" end if length old_tsv = tsv tsv = tsv.reorder(:key, key).add_field "LENGTH" do |k,values| NamedArray === values ? (values[key] ? values[key].length.to_s : "0") : values.length.to_s end key = "LENGTH" end case when value =~ /^([<>]=?)(.*)/ tsv = tsv.select(key, invert){|k| k = k.first if Array === k; (k.nil? or (String === k and k.empty?)) ? false : k.to_f.send($1, $2.to_f)} when value =~ /^\/(.+)\/.{0,2}\s*$/ tsv = tsv.select({key => Regexp.new($1)}, invert) when (value =~ /^\d+$/ and tsv.type == :double or tsv.type == :flat) tsv = tsv.select({key => value.to_i}, invert) else tsv = tsv.select({key => value}, invert) end tsv = old_tsv.select(tsv.keys) if name or length end end tsv = tsv.column(column) if column and not column.empty? tsv end def tsv_rows(tsv, page = nil, filter = nil, column = nil) tsv = tsv_process(tsv, filter, column) length = tsv.size page = @page if page.nil? if page.nil? or page.to_s == "false" [tsv_rows_full(tsv), length] else [tsv_rows_full(paginate(tsv, page)), length] end end def table_value(value, type = nil, options = {}) options = {} if options.nil? return value.list_link :length, options[:list_id] if Array === value and options[:list_id] and value.respond_to? :list_link entity_options = options[:entity_options] Misc.prepare_entity(value, type, entity_options) if Entity.formats[type] and not options[:unnamed] orig_value = value value = value.link if value.respond_to? :link and not options[:unnamed] if Array === value and value.length > 100 strip = value.length value = value[0..99] end res = case options[:span] when true, "true", :true Array === value ? value.collect{|v| "<span class='table_value'>#{v.to_s}</span>"} * ", " : "<span class='table_value'>#{value}</span>" when :long, "long" Array === value ? value.zip(orig_value).collect{|v,ov| "<span class='table_value long' title='#{URI.escape(ov.to_s)}'>#{v.to_s}</span>"} * " " : "<span class='table_value long' title='#{URI.escape(orig_value)}'>#{value}</span>" else Array === value ? value.collect{|v| v.to_s} * ", " : value end res = "<span class='table_value strip'>[#{ strip } entries, 100 shown]</span> " + res if strip res end def header(field, entity_type, entity_options = {}) @table_headers ||= {} @table_headers[field] = [entity_type, entity_options] end def filter(field, type = :string) @table_filters ||= {} @table_filters[field] = type end def self.save_tsv(tsv, path) Open.write(path, tsv.to_s) table_options = {:tsv_entity_options => tsv.entity_options} if tsv.entity_templates and tsv.entity_templates.any? table_options[:headers] ||= {} tsv.entity_templates.each do |field,template| next if template.nil? next if table_options[:headers].include? field info = template.info info.delete :format info.delete :annotation_types info.delete :annotated_array table_options[:headers][field] = [template.annotation_types.last.to_s, info] end end Open.write(path + '.table_options', table_options.to_yaml ) end def self.load_tsv(file) tsv = TSV.open(Open.open(file)) table_options = File.exists?(file + '.table_options') ? YAML.load_file(file + '.table_options') : {} tsv.entity_options = table_options[:tsv_entity_options] headers = table_options[:headers] headers.each{|field,p| tsv.entity_templates[field] = Misc.prepare_entity("TEMPLATE", p.first, (tsv.entity_options || {}).merge(p.last)) } unless headers.nil? [tsv, table_options] end def save_tsv(file) RbbtRESTHelpers.save_tsv(file) end def load_tsv(file) RbbtRESTHelpers.load_tsv(file) end def table(options = {}) options = {} if options.nil? tsv = yield table_code = options[:table_id] || (rand * 100000).to_i.to_s table_code = Entity::REST.clean_element(table_code) table_code.sub!(/[^\w]/,'_') if @step table_file = @step.file(table_code) if @step url = add_GET_param(@fullpath, "_fragment", File.basename(table_file)) url = remove_GET_param(url, "_update") url = remove_GET_param(url, "_layout") url = remove_GET_param(url, "_") end table_class = options[:table_class] || options[:class] || [] table_class = [table_class] unless Array === table_class table_class << 'wide responsive' if tsv.fields.length > 4 options[:url] = url options[:table_class] = table_class options[:tsv_entity_options] = tsv.entity_options if @table_headers and @table_headers.any? options[:headers] = @table_headers @table_headers = {} end if tsv.entity_templates and tsv.entity_templates.any? options[:headers] ||= {} tsv.entity_templates.each do |field,template| next if options[:headers].include? field next if template.nil? info = template.info info.delete :format info.delete :annotation_types info.delete :annotated_array options[:headers][field] = [template.annotation_types.last.to_s, info] end end if @table_filters and @table_filters.any? options[:filters] = @table_filters @table_filters = {} end if table_file Open.write table_file, tsv.to_s Open.write table_file + '.table_options', options.to_yaml if defined? options.any? total_size = tsv.size if options[:page].nil? and total_size > PAGE_SIZE * 1.2 @page = "1" else @page = options[:page] end tsv2html(table_file, options) else tsv2html(tsv, options) end end def tsv2html(file, default_table_options = {}) if TSV === file tsv, table_options = file, {} table_options[:unnamed] = tsv.unnamed else tsv, table_options = load_tsv(file) end content_type "text/html" rows, length = tsv_rows(tsv) table_options = default_table_options.merge(table_options) partial_render('partials/table', {:total_size => length, :rows => rows, :header => tsv.all_fields, :table_options => table_options}) end end