# iexlib - Import/export library for the 'Bbuuzzb' database and ASCII files.
# Rick Smereka. Copyright (C) 2006

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, get a copy via the Internet at
# http://gnu.org/copyleft/gpl.html or write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA

# You can contact the author via email at rsmereka@future-lab.com

# This module will import (and eventually export) ascii data into
# a 'bbuuzzb' table. The field ascii data may be fixed or delimited (by
# any single character). The import is template or profile
# based. An import profile record is expected in the 'bbuuzzb' table
# 'iex.config'. The field layout for import profile record is as follows:

#    field #  description

#      1      profile name (any length)
#      2      comment
#      3      'i' for import, 'x' for export (no export currently)
#      4      ascii format, 'f' for fixed or field delimiter otherwise
#      5      array of specs, each complete spec is a subfield containing
#             the following subsubfields:
#      5,*,1  source field, field number for delimited or 'start,chars'
#             for fixed, note that start byte is zero based
#      5,*,2  conversion, one of:
#                'fxp[,n]' - fxp number with 'n' scale (zero if no scale)
#                'time' - convert hh:mm[a|p] to 24hrs. no colon
#                'int[,n]' - integer padded on the left with zeros to 'n' len
#                'pint,n' - integer plus the parameter which is mandatory
#                'winddir' - convert a wind direction alpha (N,NW,S,SSE etc)
#                            to degrees (result is fxp with scale of one)
#                'month' - convert a 3 char alpha month name to an integer
#                          (base one) zero padded on the left to two digits
#             a empty value here will denote no conversion
#      5,*,3  destination which may be a 'bbuuzzb' field, subfield or
#             or subsubfield in the form 'f[,sf][,ssf]', zero may be
#             used in any part to indicate an append (not recommended)
#      6      auto record locate/create switch (see below)
#      7      'bbuuzzb' field to match (optional), if present, an
#             attempt will be made to locate an existing matching record
#      8      ascii null field signature (optional), if present,
#             these chars will denote an empty input ascii field
       
# There must be at least six fields present in the iex profile.
# This module may be used in a couple of different ways. To import an
# entire file at once without any special processsing, use 'iexlib_import'.
# You also may import one ascii line at a time with any processing you
# like by first using the procedure 'iexlib_start' to load the profile
# followed by the procedure 'iexlib_import_do_line' for each ascii input
# line and 'iexlib_stop' when all lines are completed. The procedure
# 'iexlib_import' is a good example of manually controlling the import.
# The auto record locate/create switch is used in conjunction with
# the match field. If the auto record switch is off, the caller is
# assumed to have already set the 'bbuuzzb' file position and no
# automatic record location or creation will take place. If the
# auto switch is on and there is a match field present, an
# attempt will be made to find the matching 'bbuuzzb' record before
# the import of that ascii line takes place. If the find of the
# 'bbuuzzb' record fails, a new output record will be created.
# If the auto switch is on and there is no match field present,
# a new 'bbuuzzb' output record will be created for each ascii
# input line. As a guideline, set the auto switch high when you
# will be using the procedure 'iexlib_import' (high level) to
# import the entire ascii file at once. Set the auto switch low
# when you will be manually controlling the import. When the
# auto record switch is low, any value in the match field will
# be ignored.

# This Tcl version uses the array 'iex_spec' to hold all the
# import specifications. The array is pseudo two-dimensional
# with the first part being the spec number (sequential) and 
# the second part, the name of the element. The array element
# names are as follows:
#
# source
# source_add1
# source_add2
# conversion
# conversion_parm
# dest
# dest_add1
# dest_add2

# Initial tcl version. Jun/2006, Rick Smereka

# global data
# name of 'Bbuuzzb' IEX config table
const IEX_CONFIG_TABLE_NAME "iex.config"

# any element of the config table that may
# have multiple parameters will be delimited
# by this character
const IEXLIB_CONFIG_SPEC_SEP ","

# IEX config table layout
const IEX_CONFIG_FIELD_PROFILE_NAME 1
const IEX_CONFIG_FIELD_COMMENT 2
const IEX_CONFIG_FIELD_TYPE 3
const IEX_CONFIG_FIELD_FORMAT 4
const IEX_CONFIG_FIELD_SPECS 5
const IEX_CONFIG_FIELD_AUTO_REC 6
const IEX_CONFIG_FIELD_MATCH 7
const IEX_CONFIG_FIELD_NULL 8
const IEX_CONFIG_MIN_ALLOW_FIELDS 6

# specs subsubfield layout
const IEX_CONFIG_SUBSUBFIELD_SOURCE 1
const IEX_CONFIG_SUBSUBFIELD_CONV 2
const IEX_CONFIG_SUBSUBFIELD_DEST 3

# conversion types
# import
const IEX_CONV_TYPE_IFXP 0
const IEX_CONV_TYPE_ITIME 1
const IEX_CONV_TYPE_IINT 2
const IEX_CONV_TYPE_IWINDDIR 3
const IEX_CONV_TYPE_IMONTH 4
const IEX_CONV_TYPE_IPINT 5
const IEX_CONV_TYPE_UNKNOWN -1
const IEX_CONV_TYPE_IMAXTYPE 6

# wind direction fxp scale
const IEXLIB_SCALE_WIND_DIR 1

# no parameter in array
const IEXLIB_LL_PARM_NULL -1

# are we processing an export?
set iex_is_export 0

# is the ASCII format fixed?
set iex_is_fixed 0

# ASCII field delimiter
set iex_ascii_delim ""

# auto record locate/create?
set iex_auto_rec 0

# dest field number to match
set iex_match_field 0

# spec number to match
set iex_import_match 0

# null ASCII field char seq
set iex_null_field ""

# count of specs in use
set iex_spec_count 0

proc iexlib_import {profile_name in_file_name out_table_name} {
   # Perform a complete import of the input ascii file into the
   # 'bbuuzzb' table using the supplied profile. Returns a
   # 'dbeng' code.
   global DBENG_OK DBENG_INVALID_FUNCTION DBENG_UNABLE_TO_OPEN
   set mname "iexlib_import"

   if {$profile_name == ""} {
      db_io_code_log $mname "empty(profile_name)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$in_file_name == ""} {
      db_io_code_log $mname "empty(in_file__name)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$out_table_name == ""} {
      db_io_code_log $mname "empty(out_table_name)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   set ret [iexlib_start $profile_name]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "error loading profile" $ret
      return $ret
   }

   if {[catch {open $in_file_name r} fid]} {
      logman "$mname:unable to open $in_file_name"
      iexlib_stop
      return $DBENG_UNABLE_TO_OPEN
   }

   set ret [db_open $out_table_name tid]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_open(tid)" $ret
      close $fid
      iexlib_stop
      return $ret
   }
   
   set line [get_rec $fid]
   set line_no 1

   while {![eof $fid]} {
      set ret [iexlib_import_do_line $tid $line]

      if {$ret != $DBENG_OK} {
         logman "exit([db_io_code_string $ret]):bad rc from\
                 iexlib_import_do_line($line_no)" 
         db_close $tid
         close $fid
         iexlib_stop
         return $ret
      }

      if {$line != ""} {
         set ret [db_write $tid]

         if {$ret != $DBENG_OK} {
            logman "exit([db_io_code_string $ret]):bad rc from\
                    db_write($line_no)" 
            db_close $tid
            close $fid
            iexlib_stop
            return $ret
            }
      }

      set line [get_rec $fid]
      incr line_no
   }

   db_close $tid
   close $fid
   iexlib_stop
   return $DBENG_OK
}

proc iexlib_start {profile_name} {
   # Obtain the iex configuration record from the 'profile_name' and
   # load all module globals and the iex array. Returns a 'dbeng' code.
   global iex_spec DBENG_OK DBENG_INVALID_FUNCTION IEX_CONFIG_TABLE_NAME
   global IEX_CONFIG_FIELD_PROFILE_NAME IEX_CONFIG_MIN_ALLOW_FIELDS
   global IEX_CONFIG_FIELD_TYPE IEX_CONFIG_FIELD_FORMAT iex_is_export
   global iex_is_fixed iex_ascii_delim IEX_CONFIG_FIELD_AUTO_REC iex_auto_rec
   global IEX_CONFIG_FIELD_MATCH iex_match_field IEXLIB_LL_PARM_NULL
   global iex_null_field IEX_CONFIG_FIELD_SPECS iex_spec_count
   global IEX_CONFIG_FIELD_NULL
   set mname "iexlib_start"

   logman "$mname:enter:profile_name=$profile_name"

   if {$profile_name == ""} {
      db_io_code_log $mname "empty(profile_name)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # if array exists, delete it
   if {[array exists iex_spec]} {
      iexlib_stop
   }

   # open the iex config table and find the config record
   set ret [db_open $IEX_CONFIG_TABLE_NAME tid]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_open(iex.config)" $ret
      return $ret
   }

   set ret [db_find_field $tid 0 $profile_name $IEX_CONFIG_FIELD_PROFILE_NAME]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_find_field(profile_name)" $ret
      db_close $tid
      return $ret
   }

   # get the number of fields in the config record, must have min
   set ret [db_get_nfields $tid count]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_nfields(profile_name)" $ret
      db_close $tid
      return $ret
   }

   if {$count < $IEX_CONFIG_MIN_ALLOW_FIELDS} {
      db_io_code_log $mname "insufficient number of fields(profile_name)" \
                     $DBENG_INVALID_FUNCTION
      db_close $tid
      return $DBENG_INVALID_FUNCTION
   }

   # get and load module globals
   # import or export
   set ret [db_get_field $tid $IEX_CONFIG_FIELD_TYPE fdat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_field(profile_name,type)" $ret
      db_close $tid
      return $ret
   }

   switch [ucase [string range $fdat 0 0]] {
      I {
         set iex_is_export 0
      }

      X {
         set iex_is_export 1
      }

      default {
         db_io_code_log $mname "unknown iex type" $DBENG_INVALID_FUNCTION
         db_close $tid
         return $DBENG_INVALID_FUNCTION
      }
   }

   # fixed or delimited with delimiter
   set ret [db_get_field $tid $IEX_CONFIG_FIELD_FORMAT fdat]
 
   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_field(profile_name,format)" \
                     $ret
      db_close $tid
      return $ret
   }

   set ret [ucase [string range $fdat 0 0]]

   if {$ret == "F"} {
      set iex_is_fixed 1
      set iex_ascii_delim ""
   } else {
      set iex_is_fixed 0
      set iex_ascii_delim $ret
   }

   # auto record locate/create
   set ret [db_get_field $tid $IEX_CONFIG_FIELD_AUTO_REC fdat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_field(profile_name,auto_rec" \
                     $ret
      db_close $tid
      return $ret
   }

   if {$fdat == ""} {
      db_io_code_log $mname "empty profile field(auto_rec)" \
                     $DBENG_INVALID_FUNCTION
      db_close $tid
      return $DBENG_INVALID_FUNCTION
   }

   if {![num $fdat]} {
      db_io_code_log $mname "auto_rec is not numeric" $DBENG_INVALID_FUNCTION
      db_close $tid
      return $DBENG_INVALID_FUNCTION
   }

   set iex_auto_rec $fdat

   # 'bbuuzzb' field number to match
   if {$count >= $IEX_CONFIG_FIELD_MATCH} {
      set ret [db_get_field $tid $IEX_CONFIG_FIELD_MATCH fdat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from db_get_field(profile_name,match" \
                        $ret
         db_close $tid
         return $ret
      }

      if {$fdat != ""} {
         if {![num $fdat]} {
            db_io_code_log $mname "match_field is not numeric" \
                           $DBENG_INVALID_FUNCTION
            db_close $tid
            return $DBENG_INVALID_FUNCTION
         }

         set iex_match_field $fdat
      } else {
         set iex_match_field $IEXLIB_LL_PARM_NULL
      }
   } else {
      set iex_match_field $IEXLIB_LL_PARM_NULL
   }

   # ascii null field signature
   if {$count > $IEX_CONFIG_FIELD_MATCH} {
      set ret [db_get_field $tid $IEX_CONFIG_FIELD_NULL fdat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname \
                        "bad rc from db_get_field(profile_name,null_field" \
                        $ret
         db_close $tid
         return $ret
      }

      if {$fdat != ""} {
         set iex_null_field $fdat
      } else {
         set iex_null_field ""
      }
   } else {
      set iex_null_field ""
   }

   # load iex specs into array
   # get number of specs (subfields)
   set ret [db_get_nsubfields $tid $IEX_CONFIG_FIELD_SPECS count]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_nsubfields(specs)" $ret
      db_close $tid
      return $ret
   }

   for {set i 1} {$i <= $count} {incr i} {
      set iex_spec($i,source) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,source_add1) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,source_add2) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,conversion) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,conversion_parm) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,dest) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,dest_add1) $IEXLIB_LL_PARM_NULL
      set iex_spec($i,dest_add2) $IEXLIB_LL_PARM_NULL

      # load the source field of this spec
      set ret [iexlib_config_set_sd 1 $tid $i] 

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from iexlib_config_set_sd(source)" $ret
         db_close $tid
         iexlib_stop
         return $ret
      }

      # load the conversion spec
      set ret [iexlib_config_set_conv $tid $i]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from iexlib_config_set_conv" $ret
         db_close $tid
         iexlib_stop
         return $ret
      }

      # load the dest field
      set ret [iexlib_config_set_sd 0 $tid $i]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from iexlib_config_set_sd(dest)" $ret
         db_close $tid
         iexlib_stop
         return $ret
      }
   }

   set iex_spec_count $count
   db_close $tid
   iexlib_dump
   return $DBENG_OK
}

proc iexlib_stop {} {
   # Unset all spec array entries.
   global iex_spec iex_spec_count
   set mname "iexlib_stop"

   logman "$mname:enter"

   # if array does not exist, nothing to do
   if {![array exists iex_spec]} {
      logman "$mname:exit:array empty"
      return
   }

   # loop through the array
   for {set i 1} {$i <= $iex_spec_count} {incr i} {
      # use catch to trap any gaps (just in case)
      if {![catch {set ret $iex_spec($i,source)}]} {
         array unset iex_spec $i,source
         array unset iex_spec $i,source_add1
         array unset iex_spec $i,source_add2
         array unset iex_spec $i,conversion
         array unset iex_spec $i,conversion_parm
         array unset iex_spec $i,dest
         array unset iex_spec $i,dest_add1
         array unset iex_spec $i,dest_add2
      } else {
         logman "$mname:no spec in use for seq $i"
      }
   }

   set iex_spec_count 0
   logman "$mname:normal exit"
}

proc iexlib_active {} {
   # Determine whether there is a current iex profile loaded into
   # the array. Returns 1 if there is a profile loaded, 0 otherwise.
   global iex_spec_count

   if {$iex_spec_count > 0} {
      return 1
   }
   
   return 0
}

proc iexlib_dump {} {
   # Output the contents of the global data and the spec array to the
   # log manager. Note that logging must already be turned on.
   global iex_is_fixed iex_is_export iex_auto_rec iex_match_field iex_null_field
   global iex_ascii_delim iex_spec iex_spec_count
   set mname "iexlib_dump"

   if {$iex_is_fixed} {
      logman [format "%s:is_export=%d,is_fixed=%d,auto_rec=%d,match_field=%d,\
                      null_field=%s" $mname $iex_is_export $iex_is_fixed \
                      $iex_auto_rec $iex_match_field $iex_null_field]
   } else {
      logman [format "%s:is_export=%d,is_fixed=%d,ascii_delim='%s',\
                      auto_rec=%d,match_field=%d,null_field=%s" $mname \
                      $iex_is_export $iex_is_fixed $iex_ascii_delim \
                      $iex_auto_rec $iex_match_field $iex_null_field]
   }

   if {$iex_spec_count == 0} {
      logman "$mname:iex spec array is empty"
      return
   }

   for {set i 1} {$i <= $iex_spec_count} {incr i} {
      logman [format "%s:spec(%d):source=%d,source_add1=%d,source_add2=%d,\
                      conv=%d,conv_parm=%d,dest=%d,dest_add1=%d,dest_add2=%d" \
                      $mname $i $iex_spec($i,source) $iex_spec($i,source_add1) \
                      $iex_spec($i,source_add2) $iex_spec($i,conversion) \
                      $iex_spec($i,conversion_parm) $iex_spec($i,dest) \
                      $iex_spec($i,dest_add1) $iex_spec($i,dest_add2)]
   }
}

proc iexlib_import_do_line {out_tid line} {
   # Import an entire line from the input ascii file into the
   # 'bbuuzzb' table. Returns a 'dbeng' code.
   global DBENG_OK iex_spec DBENG_INVALID_FUNCTION iex_auto_rec iex_match_field
   global IEXLIB_LL_PARM_NULL iex_spec_count iex_null_field
   global IEX_CONV_TYPE_IFXP IEX_CONV_TYPE_ITIME IEX_CONV_TYPE_IINT
   global IEX_CONV_TYPE_IPINT IEX_CONV_TYPE_IWINDDIR IEX_CONV_TYPE_IMONTH
   set mname "iexlib_import_do_line"

   if {$line == ""} {
      # empty line is not fatal
      db_io_code_log $mname "empty(line)" $DBENG_OK
      return $DBENG_OK
   }
   
   logman "$mname:out_tid=$out_tid,line=$line,l=[string length $line]"

   # it is an error if the spec array does not exist
   if {![array exists iex_spec]} {
      db_io_code_log $mname "no spec array" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # if the auto record locate/create switch is on, attempt to locate
   # an existing bbuuzzb record if there is a match field, if the
   # locate fails, create a new output record, if the auto switch is on
   # and there is no match field, create a new output record, if the auto
   # switch is off, assume the caller has already set the bbuuzzb file
   # position and do nothing
   if {$iex_auto_rec} {
      if {$iex_match_field != $IEXLIB_LL_PARM_NULL} {
         set ret [iexlib_import_match $out_tid $line]

         if {$ret != $DBENG_OK} {
            # if match fails, create a new 'bbuuzzb' record
            logman "$mname:no match,creating new record"
            db_new $out_tid
         } else {
            logman "$mname:found matching bbuuzzb record"
         }
      } else {
         db_new $out_tid
      }
   }

   set ascii_field ""

   # loop through all the specs, for each spec, get the ascii
   # field, perform the conversion and put to the bbuuzzb record
   for {set i 1} {$i <= $iex_spec_count} {incr i} {
      set ret [iexlib_import_get_ascii_field $line ascii_field $i]

      if {$ret != $DBENG_OK} {
         # if the ascii field cannot be obtained, skip the spec
         logman "$mname:cannot get ascii field data(f=$ascii_field,\
                 dest=$iex_spec($i,dest))"
         continue
      }

      # if the field is empty, skip
      if {$ascii_field == ""} {
         logman "$mname:empty field(dest=$iex_spec($i,dest))"   
         continue
      }

      # if there is a null signature present and it matches, skip
      if {$iex_null_field != ""} {
         # compare only to length of null signature
         if {![string compare -length [string length $iex_null_field] \
                              $ascii_field $iex_null_field]} {
            logman "$mname:null field(dest=$iex_spec($i,dest))"
            continue
         }
      }

      logman "$mname:ascii field($iex_spec($i,source),\
              $iex_spec($i,source_add1))=$ascii_field,\
              l=[string length $ascii_field]"

      switch -- $iex_spec($i,conversion) \
         $IEX_CONV_TYPE_IFXP {set ret [iexlib_import_do_fxp $out_tid \
                                      $ascii_field $i]} \
         $IEX_CONV_TYPE_ITIME {set ret [iexlib_import_do_time $out_tid \
                                       $ascii_field $i]} \
         $IEX_CONV_TYPE_IINT {set ret [iexlib_import_do_int $out_tid \
                                      $ascii_field $i]} \
         $IEX_CONV_TYPE_IPINT {set ret [iexlib_import_do_pint $out_tid \
                                       $ascii_field $i]} \
         $IEX_CONV_TYPE_IWINDDIR {set ret [iexlib_import_do_wind_dir $out_tid \
                                          $ascii_field $i]} \
         $IEX_CONV_TYPE_IMONTH {set ret [iexlib_import_do_month $out_tid \
                                          $ascii_field $i]} \
         default {set ret [iexlib_import_put_data $out_tid \
                          $ascii_field $i]}
   }

   return $DBENG_OK
}

proc iexlib_config_set_sd {is_source tid rsf} {
   # Load the source or destination field specification into the
   # array. Returns a 'dbeng' code.
   global IEX_CONFIG_SUBSUBFIELD_SOURCE IEX_CONFIG_SUBSUBFIELD_DEST
   global IEX_CONFIG_FIELD_SPECS DBENG_OK iex_spec
   set mname "iexlib_config_set_sd"

   if {$is_source} {
      set ssf $IEX_CONFIG_SUBSUBFIELD_SOURCE
   } else {
      set ssf $IEX_CONFIG_SUBSUBFIELD_DEST
   }

   set ret [db_get_subsubfield $tid $IEX_CONFIG_FIELD_SPECS $rsf $ssf fdat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_subsubfield" $ret
      return $ret
   }

   set ret [iexlib_config_get_sd $fdat f sf ssf]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_config_get_sd" $ret
      return $ret
   }

   if {$is_source} {
      set iex_spec($rsf,source) $f
      set iex_spec($rsf,source_add1) $sf
      set iex_spec($rsf,source_add2) $ssf
   } else {
      set iex_spec($rsf,dest) $f
      set iex_spec($rsf,dest_add1) $sf
      set iex_spec($rsf,dest_add2) $ssf
   }

   return $DBENG_OK
}

proc iexlib_config_set_conv {tid rsf} {
   # Load the conversion spec into the spec array. Returns a 'dbeng' code.
   global DBENG_OK IEX_CONFIG_FIELD_SPECS IEX_CONFIG_SUBSUBFIELD_CONV
   global IEXLIB_CONFIG_SPEC_SEP IEX_CONV_TYPE_UNKNOWN DBENG_INVALID_FUNCTION
   global DBENG_INTERNAL_ERROR IEXLIB_LL_PARM_NULL iex_spec
   set mname "iexlib_config_set_conv"

   set ret [db_get_subsubfield $tid $IEX_CONFIG_FIELD_SPECS $rsf \
                               $IEX_CONFIG_SUBSUBFIELD_CONV fdat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_get_subsubfield" $ret
      return $ret
   }

   # separate out keyword and any additional parameter
   set nwords [ll_words $fdat $IEXLIB_CONFIG_SPEC_SEP]

   # if the conversion subsubfield is empty, assume no conversion
   if {!$nwords} {
      db_io_code_log $mname "exit:no conv spec,assuming no conv" $DBENG_OK
      set iex_spec($rsf,conversion) $IEX_CONV_TYPE_UNKNOWN
      return $DBENG_OK
   }

   if {$nwords > 2} {
      db_io_code_log $mname "improper number of conversion parms" \
                     $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$nwords > 1} {
      if {![ll_word $fdat parm 1 $IEXLIB_CONFIG_SPEC_SEP]} {
         db_io_code_log $mname "bad rc from ll_word(1)" $DBENG_INTERNAL_ERROR
         return $DBENG_INTERNAL_ERROR
      }
   } else {
      set parm $fdat
      set iex_spec($rsf,conversion_parm) $IEXLIB_LL_PARM_NULL
   }

   set ret [iexlib_import_conv_get_token $parm]

   if {$ret == $IEX_CONV_TYPE_UNKNOWN} {
      logman "$mname:exit:unknown conv type($parm)"
      return $DBENG_INVALID_FUNCTION
   }

   set iex_spec($rsf,conversion) $ret

   if {$nwords > 1} {
      if {![ll_word $fdat parm 2 $IEXLIB_CONFIG_SPEC_SEP]} {
         db_io_code_log $mname "bad rc from ll_word(2)" $DBENG_INTERNAL_ERROR
         return $DBENG_INTERNAL_ERROR
      }

      if {![num $parm]} {
         db_io_code_log $mname "non-numeric(conv_parm)" $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }

      set iex_spec($rsf,conversion_parm) $parm
   }

   return $DBENG_OK
}

proc iexlib_config_get_sd {fdat f sf ssf} {
   # Parse a source or destination field spec into its
   # component parts. Returns a 'dbeng' code.
   global DBENG_OK IEXLIB_CONFIG_SPEC_SEP IEXLIB_LL_PARM_NULL
   global DBENG_INVALID_FUNCTION DBENG_INTERNAL_ERROR
   upvar $f uf
   upvar $sf usf
   upvar $ssf ussf
   set mname "iexlib_config_get_sd"

   set nelements [ll_words $fdat $IEXLIB_CONFIG_SPEC_SEP]
   set uf $IEXLIB_LL_PARM_NULL      
   set usf $IEXLIB_LL_PARM_NULL      
   set ussf $IEXLIB_LL_PARM_NULL      

   if {!$nelements} {
      db_io_code_log $mname "empty(fdat)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$nelements == 1} {
      if {![num $fdat]} {
         db_io_code_log $mname "non-numeric(fdat)" $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }

      set uf $fdat
      return $DBENG_OK
   }

   for {set i 1} {$i <= $nelements} {incr i} {
      if {![ll_word $fdat wrd $i $IEXLIB_CONFIG_SPEC_SEP]} {
         logman "$mname:exit:bad rc from ll_word(fdat=$fdat,wrd=$wrd,i=$i)"
         return $DBENG_INTERNAL_ERROR
      }

      if {![num $wrd]} {
         logman "$mname:non-numeric(fdat=$fdat,wrd=$wrd,i=$i)"
         return $DBENG_INVALID_FUNCTION
      }

      switch $i {
         1 {
            set uf $wrd
         }

         2 {
            set usf $wrd
         }

         3 {
            set ussf $wrd
         }

         default {
            logman "$mname:unknown parameter(fdat=$fdat,wrd=$wrd,i=$i)"
            return $DBENG_INVALID_FUNCTION
         }
      }
   }

   return $DBENG_OK
}

proc iexlib_import_get_ascii_field {line outfield spec} {
   # Obtain a field within the ascii import line. The field data
   # is retrieved based on whether the input format is fixed or
   # delimited (contained in the globals). The 'spec' number
   # must be a valid import specification number in the
   # 'iex_spec' array and the field will be obtained from this
   # spec and placed into 'outfield' upon success. Returns
   # a 'dbeng' code.
   global iex_spec iex_is_fixed iex_ascii_delim iex_spec_count DBENG_OK
   global DBENG_INVALID_FUNCTION IEXLIB_LL_PARM_NULL DBENG_INTERNAL_ERROR
   upvar $outfield uoutfield
   set mname "iexlib_import_get_ascii_field"
   set uoutfield ""

   if {$line == ""} {
      do_io_code_log $mname "empty(line)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # validate source field spec
   if {$iex_spec($spec,source) == $IEXLIB_LL_PARM_NULL} {
      db_io_code_log $mname "spec $spec source is null" $DBENG_INVALID_FUNCTION
      reutrn $DBENG_INVALID_FUNCTION
   }

   if {$iex_is_fixed} {
      set len [string length  $line]

      if {$iex_spec($spec,source_add1) == $IEXLIB_LL_PARM_NULL} {
         db_io_code_log $mname "spec $spec source_add1 is null" \
                        $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }   

      if {$iex_spec($spec,source_add1) < 1} {
         db_io_code_log $mname "spec $spec source_add1(out of range)" \
                        $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }

      if {$iex_spec($spec,source) > $len || [expr $iex_spec($spec,source) + \
          $iex_spec($spec,source_add1)] > $len} {
         db_io_code_log $mname "spec $spec:out of range source" \
                        $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }
   } else {
      set nwords [ll_words $line $iex_ascii_delim]

      if {$iex_spec($spec,source) > $nwords} {
         db_io_code_log $mname "spec $spec:out of range source" \
                        $DBENG_INVALID_FUNCTION
         return $DBENG_INVALID_FUNCTION
      }
   }

   # obtain the field data
   if {$iex_is_fixed} {
      set tfield [string range $line $iex_spec($spec,source) \
                               [expr $iex_spec($spec,source) + \
                                     $iex_spec($spec,source_add1) - 1]]
   } else {
      if {![ll_word $line tfield $iex_spec($spec,source) $iex_ascii_delim]} {
         db_io_code_log $mname "bad rc from ll_word" $DBENG_INTERNAL_ERROR
         return $DBENG_INTERNAL_ERROR
      }
   }

   set uoutfield [trim $tfield]
   return $DBENG_OK
}

proc iexlib_import_match {tid line} {
   # Attempt to match the input ascii field with 'bbuuzzb' data by
   # applying the specified conversion to the input ascii field
   # and attempting to locate a 'bbuuzzb' record with the matching
   # data. Returns a 'dbeng' code.
   global iex_spec iex_import_match iex_spec_count DBENG_OK DBENG_INTERNAL_ERROR
   global iex_match_field DBENG_NO_SUCH_FIELD IEX_CONV_TYPE_UNKNOWN
   global DBENG_INVALID_FUNCTION IEX_CONV_TYPE_IFXP IEX_CONV_TYPE_ITIME
   global IEX_CONV_TYPE_IINT IEX_CONV_TYPE_IPINT IEX_CONV_TYPE_IWINDDIR
   global IEX_CONV_TYPE_IMONTH
   set mname "iexlib_import_match"

   # eror if spec array does not exist
   if {![array exists iex_spec]} {
      db_io_code_log "spec array empty" $DBENG_INTERNAL_ERROR
      return $DBENG_INTERNAL_ERROR
   }

   # locate matching spec array element only if the existing array
   # index is zero
   if {!$iex_import_match} {
      for {set i 1} {$i <= $iex_spec_count} {incr i} {
         if {$iex_spec($i,dest) == $iex_match_field} {
            set iex_import_match $i
            break
         }
      }

      if {!$iex_import_match} {
         db_io_code_log $mname "not found(match_field)" $DBENG_NO_SUCH_FIELD
         return $DBENG_NO_SUCH_FIELD
      }
   }
   
   set in_field ""
   set out_field ""

   # get the raw ascii input field
   set ret [iexlib_import_get_ascii_field $line in_field $iex_import_match]
   logman "$mname:i_i_m=$iex_import_match,in_field=$in_field"

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_get_ascii_field" $ret
      return $ret
   }

   # unknown conversion is a signal to not apply any conversion
   if {$iex_spec($iex_import_match,conversion) == $IEX_CONV_TYPE_UNKNOWN} {
      logman "$mname:conv(unknown):no conv applied"
      set out_field $in_field
   } else {
      # apply conversion
      switch -- $iex_spec($iex_import_match,conversion) \
         $IEX_CONV_TYPE_IFXP {set ret [iexlib_import_conv_fxp $in_field \
                                      $iex_import_match out_field]} \
         $IEX_CONV_TYPE_ITIME {set ret [iexlib_import_conv_time $in_field \
                                       out_field]} \
         $IEX_CONV_TYPE_IINT {set ret [iexlib_import_conv_int $in_field \
                                      $iex_import_match out_field]} \
         $IEX_CONV_TYPE_IPINT {set ret [iexlib_import_conv_pint $in_field \
                                      $iex_import_match out_field]} \
         $IEX_CONV_TYPE_IWINDDIR {set ret [iexlib_import_conv_wind_dir \
                                      $in_field $iex_import_match out_field]} \
         $IEX_CONV_TYPE_IMONTH {set ret [iexlib_import_conv_month $in_field \
                                       out_field]} \
         default {db_io_code_log $mname "unknown conv" \
                                 $DBENG_INVALID_FUNCTION; \
                  return $DBENG_INVALID_FUNCTION}
   }

   if {!$ret} {
      db_io_code_log $mname "conv failed" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # attempt to locate the 'bbuuzzb' record with matching data
   db_top $tid
   logman "$mname:out_field=$out_field"
   set ret [db_find_field $tid 0 $out_field $iex_spec($iex_import_match,dest)]
   return $ret
}

proc iexlib_import_do_wind_dir {out_tid in_field spec} {
   # Convert the wind direction from alpha to degrees and place into
   # the 'bbuuzzb' recod. Returns a 'dbeng' code.
   global iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_wind_dir"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # convert the field to wind direction in degrees
   if {![wlib_wdir_desc_2_deg $in_field result]} {
      logman "$mname:bad rc(0) from wlib_wdir_desc_2_deg($in_field)"
      return $DBENG_INVALID_FUNCTION
   }

   # place the 'fxp' data into the 'bbuuzzb' record
   set ret [iexlib_import_put_fxp $out_tid $result $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_fxp" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_do_time {out_tid in_field spec} {
   # Convert a time field (in the form hh:mm[a|p]) to 24 hour
   # and place into a 'bbuuzzb' record. Returns a 'dbeng' code.
   global iex_spec iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_time"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # convert the time field to 24hr clock
   if {![iexlib_import_conv_time $in_field out_field]} {
      logman "$mname:bad rc(0) from iexlib_import_conv_time($in_field)"
      return $DBENG_INVALID_FUNCTION
   }

   set ret [iexlib_import_put_data $out_tid $out_field $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_data" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_do_fxp {out_tid in_field spec} {
   # Convert an 'fxp' field and place it into the 'bbuuzzb' record.
   # Returns a 'dbeng' code.
   global iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_fxp"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   set result [fxp_iconv $in_field]

   if {$result == ""} {
      logman "$mname:bad rc from fxp_iconv($in_field)"
      return $DBENG_INVALID_FUNCTION
   }

   # place the 'fxp' data into the 'bbuuzzb' record
   set ret [iexlib_import_put_fxp $out_tid $result $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_fxp" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_do_int {out_tid in_field spec} {
   # Validate and place an integer in the 'bbuuzzb' record.
   # Returns a 'dbeng' code.
   global iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_int"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }
   
   if {![iexlib_import_conv_int $in_field $spec out_field]} {
      db_io_code_log $mname "bad rc(0) from iexlib_import_conv_int" \
                     $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   set ret [iexlib_import_put_data $out_tid $out_field $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_data" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_do_pint {out_tid in_field spec} {
   # Validate an integer plus a constant and place into the
   # 'bbuuzzb' record. Returns a 'dbeng' code.
   global iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_pint"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {![iexlib_import_conv_pint $in_field $spec out_field]} {
      db_io_code_log $mname "bad rc(0) from iexlib_import_conv_pint" \
                     $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   set ret [iexlib_import_put_data $out_tid $out_field $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_data" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_do_month {out_tid in_field spec} {
   # Convert a month name (3 chars) to a month number (base 1)
   # and place into a 'bbuuzzb' record. Returns a 'dbeng' code.
   global iex_spec_count DBENG_OK DBENG_INVALID_FUNCTION
   set mname "iexlib_import_do_month"

   if {$in_field == ""} {
      db_io_code_log $mname "empty(in_field)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }
   
   # convert the month name to a number
   if {![iexlib_import_conv_month $in_field out_field]} {
      logman "$mname:bad rc(0) from iexlib_import_conv_month($in_field)"
      return $DBENG_INVALID_FUNCTION
   }

   set ret [iexlib_import_put_data $out_tid $out_field $spec]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from iexlib_import_put_data" $ret
      return $ret
   }

   return $DBENG_OK
}
   
proc iexlib_import_conv_wind_dir {in_field spec out_field} {
   # Convert the wind direction from alpha to degrees. Returns
   # 1 upon success with the result loaded into 'out_field',
   # 0 otherwise. Note that the result is in 'bbuuzzb' 'fxp'
   # format (no decimal and padded on the right with zeros
   # to the desired precision or scale). This procedure
   # is used only to match records ('iexlib_import_match').
   global iex_spec iex_spec_count IEXLIB_LL_PARM_NULL
   upvar $out_field uout_field
   set mname "iexlib_import_conv_wind_dir"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      logman "$mname:spec(out of range)"
      return 0
   }

   # convert the field to wind direction in degrees as 'fxp'
   if {![wlib_wdir_desc_2_deg $in_field result]} {
      logman "$mname:bad rc(0) from wlib_wdir_desc_2_deg($in_field)"
      return 0
   }

   # use the raw fxp number string
   if {![fxp_get_list $result r_num r_scale]} {
      logman "$mname:bad rc(0) from fxp_get_list"
      return 0
   }

   set uout_field $r_num
   return 1
}
   
proc iexlib_import_conv_time {in_field out_field} {
   # Convert a time field (in the form hh:mm[a|p] to 24 hour.
   # Returns 1 upon success, 0 otherwise.
   upvar $out_field uout_field
   set mname "iexlib_import_conv_time"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }

   # convert the time field to 24hr clock
   if {![datime_hour12_2_hour24 $in_field result 0]} {
      logman "$mname:bad rc(0) from datime_hour12_2_hour24($in_field)"
      return 0
   }

   set uout_field $result
   return 1
}

proc iexlib_import_conv_int {in_field spec out_field} {
   # Validate and pad (if requested) an integer. Returns 1 upon
   # success with the result loaded into 'out_field', 0 otherwise.
   global iex_spec iex_spec_count IEXLIB_LL_PARM_NULL
   upvar $out_field uout_field
   set mname "iexlib_import_conv_int"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      logman "$mname:out of range(spec)"
      return 0
   }

   # make sure data is numeric
   if {![num $in_field]} {
      logman "$mname:bad rc(0) from num($in_field)"
      return 0
   }
   
   # pad on left with zeros if requested
   if {$iex_spec($spec,conversion_parm) != $IEXLIB_LL_PARM_NULL} {
      set uout_field [lpad $in_field "0" $iex_spec($spec,conversion_parm)]
   } else {
      set uout_field $in_field
   }

   return 1
}

proc iexlib_import_conv_pint {in_field spec out_field} {
   # Validate a plus integer, which is an integer plus a constant.
   # Returns 1 upon success with the result load into 'out_field',
   # 0 otherwise.
   global iex_spec iex_spec_count IEXLIB_LL_PARM_NULL
   upvar $out_field uout_field
   set mname "iexlib_import_conv_pint"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      logman "$mname:out of range(spec)"
      return 0
   }

   # make sure data is numeric
   if {![num $in_field]} {
      logman "$mname:bad rc(0) from num($in_field)"
      return 0
   }
   
   set ret [int_remove_lz $in_field]

   # make sure a parm (the constant) was supplied
   if {$iex_spec($spec,conversion_parm) == $IEXLIB_LL_PARM_NULL} {
      logman "$mname:no constant supplied"
      return 0
   }

   set ret [expr $ret + $iex_spec($spec,conversion_parm)]
   set uout_field $ret
   return 1
}

proc iexlib_import_conv_fxp {in_field spec out_field} {
   # Convert an fxp number. Returns 1 upon success with the
   # result loaded into 'out_field', 0 otherwise. Note that
   # the result is in 'bbuuzzb' 'fxp' format (no decimal
   # and padded on the rught with zeros to the desired
   # precision or scale). This procedure is used only to
   # match records ('iexlib_import_match').
   global iex_spec iex_spec_count IEXLIB_LL_PARM_NULL
   upvar $out_field uout_field
   set mname "iexlib_import_conv_fxp"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }

   if {$spec <= 0 || $spec > $iex_spec_count} {
      logman "$mname:out of range(spec)"
      return 0
   }

   # convert the number to 'fxp'
   set result [fxp_iconv $in_field]

   if {$result == ""} {
      logman "$mname:bad rc(empty) from fxp_iconv($in_field)"
      return 0
   }

   if {$iex_spec($spec,conversion_parm) == $IEXLIB_LL_PARM_NULL} {
      set scale 0
   } else {
      set scale $iex_spec($spec,conversion_parm)
   }

   logman "$mname:in_field=$in_field,result$result,scale=$scale"

   # force the 'fxp' number to the specified scale
   if {![fxp_scale result $scale]} {
      logman "$mname:bad rc(0) from fxp_scale"
      return 0
   }

   logman "$mname:result=$result"

   # use the raw fxp number string
   if {![fxp_get_list $result r_num r_scale]} {
      logman "$mname:bad rc(0) from fxp_get_list"
      return 0
   }

   logman "$mname:r_num=$r_num"

   set uout_field $r_num
   return 1
}

proc iexlib_import_conv_month {in_field out_field} {
   # Convert a month name (3 chars) to a month number zero padded
   # to two digits. Returns 1 with the month number loaded into
   # 'out_field' upon success, 0 otherwise.
   upvar $out_field uout_field
   set mname "iexlib_import_conv_month"
   set uout_field ""

   if {$in_field == ""} {
      logman "$mname:empty(in_field)"
      return 0
   }
   
   set ret [datime_month_name_2_num $in_field]

   if {!$ret} {
      logman "$mname:bad rc(0) from datime_month_name_2_num($in_field)"
      return 0
   }

   set uout_field [lpad $ret "0" 2]
   return 1
}
   
proc iexlib_import_put_fxp {out_tid dat spec} {
   # Place 'fxp' data into a 'bbuuzzb' record. Returns a
   # 'dbeng' code.
   global iex_spec DBENG_OK IEXLIB_LL_PARM_NULL DBENG_INVALID_FUNCTION
   global IEX_CONV_TYPE_IWINDDIR IEXLIB_SCALE_WIND_DIR iex_spec_count
   set mname "iexlib_import_put_fxp"

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$iex_spec($spec,dest) == $IEXLIB_LL_PARM_NULL} {
      db_io_code_log $mname "out of range(dest)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   # if no scale was supplied, assume zero (int) unless we are
   # processing a windir direction in which case we use a
   # pre-defined scale
   if {$iex_spec($spec,conversion) == $IEX_CONV_TYPE_IWINDDIR} {
      set scale $IEXLIB_SCALE_WIND_DIR
   } else {
      if {$iex_spec($spec,conversion_parm) == $IEXLIB_LL_PARM_NULL} {
         set scale 0
      } else {
         set scale $iex_spec($spec,conversion_parm)
      }
   }

   if {$iex_spec($spec,dest_add1) != $IEXLIB_LL_PARM_NULL && \
       $iex_spec($spec,dest_add2) != $IEXLIB_LL_PARM_NULL} {
      set ret [dbfxp_put_subsubfield $out_tid $iex_spec($spec,dest) \
                                     $iex_spec($spec,dest_add1) \
                                     $iex_spec($spec,dest_add2) $scale $dat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from dbfxp_put_subsubfield" $ret
         return $ret
      }

      return $DBENG_OK
   }

   if {$iex_spec($spec,dest_add1) != $IEXLIB_LL_PARM_NULL} {
      set ret [dbfxp_put_subfield $out_tid $iex_spec($spec,dest) \
                                  $iex_spec($spec,dest_add1) $scale $dat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from dbfxp_put_subfield" $ret
         return $ret
      }

      return $DBENG_OK
   }

   set ret [dbfxp_put $out_tid $iex_spec($spec,dest) $scale $dat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from dbfxp_put" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_put_data {out_tid dat spec} {
   # Place data into the 'bbuuzzb' record. Returns a 'dbeng' code.
   global iex_spec DBENG_OK IEXLIB_LL_PARM_NULL DBENG_INVALID_FUNCTION
   global iex_spec_count
   set mname "iexlib_import_put_data"

   if {$spec <= 0 || $spec > $iex_spec_count} {
      db_io_code_log $mname "out of range(spec)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }

   if {$iex_spec($spec,dest) == $IEXLIB_LL_PARM_NULL} {
      db_io_code_log $mname "out of range(dest)" $DBENG_INVALID_FUNCTION
      return $DBENG_INVALID_FUNCTION
   }
   
   if {$iex_spec($spec,dest_add1) != $IEXLIB_LL_PARM_NULL && \
       $iex_spec($spec,dest_add2) != $IEXLIB_LL_PARM_NULL} {
      set ret [db_put_subsubfield $out_tid $iex_spec($spec,dest) \
                                  $iex_spec($spec,dest_add1) \
                                  $iex_spec($spec,dest_add2) $dat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from db_put_subsubfield" $ret
         return $ret
      }

      return $DBENG_OK
   }
      
   if {$iex_spec($spec,dest_add1) != $IEXLIB_LL_PARM_NULL} {
      set ret [db_put_subfield $out_tid $iex_spec($spec,dest) \
                               $iex_spec($spec,dest_add1) $dat]

      if {$ret != $DBENG_OK} {
         db_io_code_log $mname "bad rc from db_put_subfield" $ret
         return $ret
      }

      return $DBENG_OK
   }

   set ret [db_put_field $out_tid $iex_spec($spec,dest) $dat]

   if {$ret != $DBENG_OK} {
      db_io_code_log $mname "bad rc from db_put_field" $ret
      return $ret
   }

   return $DBENG_OK
}

proc iexlib_import_conv_get_token {keywrd} {
   # Get conversion keyword token. Returns a numeric representation
   # of the token.
   global IEX_CONV_TYPE_UNKNOWN

   # list of conversion keywords
   set convs [list FXP TIME INT WINDDIR MONTH PINT]
   set ucase_keywrd [ucase [trim $keywrd]]
   set token [lsearch $convs $ucase_keywrd]

   if {$token == -1} {
      set token $IEX_CONV_TYPE_UNKNOWN
   }

   return $token
}
