简体   繁体   中英

Creating user defined primitive type from binary data with BinData?

I have a group of files which I have to download from a legacy Cobol system each night. I convert these files from binary data files into MySql tables.

I wrote a Ruby program to do this using BinData for the individual file structures. There are several fields in each of the files which contain packed decimal data (Cobol COMP-3). The following code works in reading one of the binary files and I wrote code to convert the field amt1 to a floating point decimal field.

The problem with this code is that for each packed field I must repeat the code for field conversion and even worse hard code the number of decimal places for each field into the code (see the commented code in program).

Example of code:

require 'bindata'
require 'bigdecimal'

class WangRec < BinData::Record
  string  :cust_no,         :read_length => 6
  string  :doc_date,        :read_length => 6
  string  :doc_no,          :read_length => 6
  string  :doc_type,        :read_length => 1
  string  :apply_to_no,     :read_length => 6
  string  :cust_no_alt,     :read_length => 6
  string  :apply_to_no_alt, :read_length => 6
  string  :doc_due_date,    :read_length => 6
  string  :amt1,            :read_length => 6 
  string  :amt2,            :read_length => 5
  string  :ref,             :read_length => 30
  string  :slsmn1,          :read_length => 3
  string  :slsmn2,          :read_length => 3
  string  :slsmn3,          :read_length => 3
  string  :amt3,            :read_length => 5
end

def packed(packed_field, dec_pos) 
  unpkd_field = packed_field.unpack('H12')
  num, sign = unpkd_field[0][0..-2], unpkd_field[-1]
  unless sign == 'f'
    amt = num.insert(0, '-')
  end

  if dec_pos > 0
    dec_amt = amt.insert((dec_pos + 1) * -1, '.')
  end

  return dec_amt.to_f 

end

wang_aropnfile = File.open('../data/wangdata/AROPNFIL.bin', 'rb')

count = 0

while !wang_aropnfile.eof?
  rec = WangRec.read(wang_aropnfile)

# The following line of code would have to be repeated for each
# packed field along with the decimal places
  amt1 = packed(rec.amt1, 2)

  puts "#{rec.cust_no} #{rec.doc_type} #{rec.doc_date} #{amt1}"

  count += 1
end

puts count

How do I create my own data type primitive called pkddec , which takes a read_length and dec_pos parameter and create a class PackedDecimal << BinData ::Primitive ?

Actually I cannot take credit for answering my question but give a BIG thanks to Dion Mendel the creator of "BinData" for answering this question in the support on Ruby Forge. I submitted the question late last night at close to midnight Chicago time and awoke this morning to find the answer from Dion Mendel which had been answered about 3 hours later. I wanted to share his answer with the community and show the working code.

require 'bindata'
require 'bigdecimal'

class PkdDec < BinData::Primitive
  mandatory_parameter :length
  default_parameter   :dec_pos => 0

  string  :str, :read_length => :length

  def get
    str_length = eval_parameter(:length)
    dec_pos    = eval_parameter(:dec_pos)
    unpkd_field = str.unpack("H#{str_length * 2}").first
    num, sign = unpkd_field[0..-2], unpkd_field[-1]
    unless sign == 'f'
      num = num.insert(0, '-')
    end

    if dec_pos > 0
      dec_amt = num.insert((dec_pos + 1) * -1, '.')
    else
      dec_amt = num
    end

    return dec_amt.to_f 

  end

  def set(dec_val)

    #  Not concerned about going the other way
    #  Reverse the get process above

  end

end

class WangRec < BinData::Record
  string  :cust_no, :read_length => 6
  string  :doc_date, :read_length => 6
  string  :doc_no,  :read_length => 6
  string  :doc_type,  :read_length => 1
  string  :apply_to_no, :read_length => 6
  string  :cust_no_alt, :read_length => 6
  string  :apply_to_no_alt, :read_length => 6
  string  :doc_due_date,    :read_length => 6
  PkdDec  :amt1,            :length => 6,  :dec_pos => 2 
  PkdDec  :amt2,            :length => 5,  :dec_pos => 2
  string  :ref,             :read_length => 30
  string  :slsmn1,          :read_length => 3
  string  :slsmn2,          :read_length => 3
  string  :slsmn3,          :read_length => 3
  PkdDec  :amt3,            :length => 5,  :dec_pos => 2

end

wang_aropnfile = File.open('../data/wangdata/AROPNFIL.bin', 'rb')

count = 0

while !wang_aropnfile.eof?
  rec = WangRec.read(wang_aropnfile)
  puts "#{rec.cust_no} #{rec.doc_type} #{rec.amt1} #{rec.amt2} #{rec.amt3}"

  count += 1
end

puts count

Again, a BIG thanks to Dion Mendel

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM