home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / YellowBox / Kits / MiscTableScroll-138.1 / Palettes / MiscTableScroll / Framework / MiscExporterDBF.M < prev    next >
Encoding:
Text File  |  1998-03-31  |  21.0 KB  |  828 lines

  1. //=============================================================================
  2. //
  3. //    Copyright (C) 1996-1997 by Paul S. McCarthy and Eric Sunshine.
  4. //        Written by Paul S. McCarthy and Eric Sunshine.
  5. //                All Rights Reserved.
  6. //
  7. //    This notice may not be removed from this source code.
  8. //
  9. //    This object is included in the MiscKit by permission from the authors
  10. //    and its use is governed by the MiscKit license, found in the file
  11. //    "License.rtf" in the MiscKit distribution.  Please refer to that file
  12. //    for a list of all applicable permissions and restrictions.
  13. //
  14. //=============================================================================
  15. //-----------------------------------------------------------------------------
  16. // MiscExporterDBF.M
  17. //
  18. //    Routines to export MiscTableScrolls in dBASE III (.DBF) format.
  19. //
  20. // TODO:
  21. //    Provide some public means to set the "AMERICAN_DATE" and the
  22. //    locale-specific characters.
  23. //-----------------------------------------------------------------------------
  24. //-----------------------------------------------------------------------------
  25. // $Id: MiscExporterDBF.M,v 1.3 97/04/01 07:47:22 sunshine Exp $
  26. // $Log:    MiscExporterDBF.M,v $
  27. // Revision 1.3  97/04/01  07:47:22  sunshine
  28. // v0.125.5: Removed unused argument.  No longer shadows variable in
  29. // -dbfAnalyze:::.
  30. // 
  31. // Revision 1.2  97/02/07  13:53:18  sunshine
  32. // v108: Ported to OPENSTEP 4.1 (gamma).
  33. // 
  34. // Revision 1.1  96/09/25  11:55:17  zarnuk
  35. // Exports the contents of table scroll in dBASEIII .dbf format.
  36. //-----------------------------------------------------------------------------
  37. #import "MiscExporterPrivate.h"
  38. #import    "bool.h"
  39.  
  40. extern "C" {
  41. #import    <stdio.h>
  42. #import    <time.h>        // time(), localtime(), strftime()
  43. }
  44.  
  45. char const DECIMAL_CHAR = '.';        // American value.
  46. char const DATE_CHAR = '/';        // American value.
  47. char const TIME_CHAR = ':';        // American value.
  48. bool const AMERICAN_DATE = true;    // mm/dd/yy vs. dd/mm/yy
  49.  
  50. int const DBF_NUM_FLDS_MAX    = 1022;        // Max # fields.
  51. int const DBF_REC_LEN_MAX    = 0x7fff;    // Max record length.
  52. int const DBF_FLD_NAME_MAX    = 10;
  53. int const DBF_CHAR_LEN_MAX    = 254;
  54. int const DBF_DATE_WIDTH    = 8;        // "YYYYMMDD"
  55. int const DBF_TIME_WIDTH    = 5;        // "HH:MM"
  56. int const DBF_NUMERIC_LEN_MAX    = 19;        // Max length for "N" fields.
  57.  
  58. static char const DBF_NULL_DATE_STR[] = "        ";
  59. static char const DBF_NULL_TIME_STR[] = "     ";
  60.  
  61.  
  62. struct DBFHeader
  63.     {
  64.     unsigned char    version;
  65.     unsigned char    update_yr;
  66.     unsigned char    update_mon;
  67.     unsigned char    update_day;
  68.     unsigned char    num_recs[4];    // little-endian long int
  69.     unsigned char    hdr_len[2];    // little-endian short int
  70.     unsigned char    rec_len[2];    // little-endian short int
  71.     char        fill[20];
  72.     };
  73.  
  74. struct DBFFieldDef
  75.     {
  76.     char        name[ DBF_FLD_NAME_MAX + 1 ];
  77.     char        type;        // Char, Numeric, Logical, Date, Memo.
  78.     char        fill[4];
  79.     unsigned char    fld_len;
  80.     unsigned char    num_decimals;
  81.     char        fill2[14];
  82.     };
  83.  
  84. enum DBFDataType
  85.     {
  86.     DBF_TYPE_CHAR,            // Non-structured type.
  87.     DBF_TYPE_NUMERIC,        // Numeric values only.
  88.     DBF_TYPE_DATE,            // Date values only.
  89.     DBF_TYPE_DATETIME        // Date-time values only.
  90.     };
  91.  
  92. #define    DBF_BIT_CHAR        (1 << DBF_TYPE_CHAR)
  93. #define    DBF_BIT_NUMERIC        (1 << DBF_TYPE_NUMERIC)
  94. #define    DBF_BIT_DATE        (1 << DBF_TYPE_DATE)
  95. #define    DBF_BIT_DATETIME    (1 << DBF_TYPE_DATETIME)
  96.  
  97. static char const DBF_TYPE_CODE[] = "CNDD";
  98.  
  99.  
  100. struct DBFInfo
  101.     {
  102.     DBFDataType    type;        // Final data type.
  103.     unsigned int    mask;        // All candidate types.
  104.     int        max_width;    // Max width for this column.
  105.     int        max_left;    // Left of decimal for numeric.
  106.     int        max_right;    // Right of decimal for numeric.
  107.     };
  108.  
  109. struct DBFDateTime
  110.     {
  111.     int        year;
  112.     int        month;
  113.     int        day;
  114.     int        hour;
  115.     int        minute;
  116.     int        second;
  117.     };
  118.  
  119.  
  120. //-----------------------------------------------------------------------------
  121. // dbf_field_name
  122. //    Make sure all field names conform to DBF field name rules.
  123. //-----------------------------------------------------------------------------
  124. static int dbf_field_name( char* buff, NSString* str )
  125.     {
  126.     int len = 0;
  127.     char const* s = (str != 0 ? [str lossyCString] : 0);
  128.     if (s != 0)
  129.     {
  130.     bool in_gap = true;
  131.     for (; len < DBF_FLD_NAME_MAX && *s != 0; s++)
  132.         {
  133.         char const c = toupper(*s);
  134.         if ('A' <= c && c <= 'Z')
  135.         {
  136.         in_gap = false;
  137.         buff[len++] = c;
  138.         }
  139.         else if ('0' <= c && c <= '9')
  140.         {
  141.         in_gap = false;
  142.         if (len == 0)        // Cannot start with digit.
  143.             buff[len++] = 'F';
  144.         buff[len++] = c;
  145.         }
  146.         else if (!in_gap)
  147.         {
  148.         in_gap = true;
  149.         buff[len++] = '_';
  150.         }
  151.         }
  152.     if (in_gap && len > 0)        // Remove trailing underline.
  153.         len--;
  154.     }
  155.  
  156.     if (len == 0)
  157.     {
  158.     char const DEFAULT_FIELD_NAME[] = "FIELD";
  159.     strcpy( buff, DEFAULT_FIELD_NAME );
  160.     len = sizeof( DEFAULT_FIELD_NAME ) - 1;
  161.     }
  162.  
  163.     buff[ len ] = '\0';
  164.  
  165.     return len;
  166.     }
  167.  
  168.  
  169. //-----------------------------------------------------------------------------
  170. // adjust_name
  171. //    Adjust this field name to try to avoid a collision with other
  172. //    field names.  Performs the adjustment by appending a counter to
  173. //    the end of the name.  If the counter does not fit in the space
  174. //    following the field name, the field name's "core" length is
  175. //    trimmed back to allow more space for the counter.  Returns the
  176. //    new "core" length for the field.
  177. //-----------------------------------------------------------------------------
  178. static int adjust_name( char* name, int core_len, int counter )
  179.     {
  180.     char buff[ 16 ];
  181.     sprintf( buff, "%d", counter );
  182.  
  183.     int buff_len = strlen( buff );
  184.     if (core_len + buff_len > DBF_FLD_NAME_MAX)
  185.     core_len = DBF_FLD_NAME_MAX - buff_len;
  186.  
  187.     strcpy( name + core_len, buff );
  188.  
  189.     return core_len;
  190.     }
  191.  
  192.  
  193. //-----------------------------------------------------------------------------
  194. // collision
  195. //    Does this field name collide with any preceeding field name?
  196. //-----------------------------------------------------------------------------
  197. static BOOL collision(    DBFFieldDef const* flds,
  198.             DBFFieldDef const* flds_lim )
  199.     {
  200.     char const* const name = flds_lim->name;
  201.     for ( ; flds < flds_lim; flds++)
  202.     if (strcmp( flds->name, name ) == 0)
  203.         return YES;
  204.     return NO;
  205.     }
  206.  
  207.  
  208. //-----------------------------------------------------------------------------
  209. // set_field_name
  210. //-----------------------------------------------------------------------------
  211. static void set_field_name( NSString* s, DBFFieldDef* fld,
  212.             DBFFieldDef* flds )
  213.     {
  214.     int slen = dbf_field_name( fld->name, s );
  215.     int num_tries = 0;
  216.     while (collision( flds, fld ))
  217.     slen = adjust_name( fld->name, slen, ++num_tries );
  218.     }
  219.  
  220.  
  221. //-----------------------------------------------------------------------------
  222. // set_field
  223. //-----------------------------------------------------------------------------
  224. static void set_field( NSString* s, DBFFieldDef* fld,
  225.             DBFFieldDef* flds, DBFInfo const* ip )
  226.     {
  227.     set_field_name( s, fld, flds );
  228.     fld->type = DBF_TYPE_CODE[ ip->type ];
  229.     fld->fld_len = (unsigned char) ip->max_width;
  230.     if (ip->type == DBF_TYPE_NUMERIC)
  231.     fld->num_decimals = ip->max_right;
  232.     }
  233.  
  234.  
  235.  
  236. //-----------------------------------------------------------------------------
  237. // set_time_field
  238. //-----------------------------------------------------------------------------
  239. static void set_time_field( NSString* s, DBFFieldDef* fld,
  240.             DBFFieldDef* flds, DBFInfo const* )
  241.     {
  242.     set_field_name( s, fld, flds );
  243.     fld->type = DBF_TYPE_CODE[ DBF_TYPE_CHAR ];
  244.     fld->fld_len = DBF_TIME_WIDTH;
  245.     }
  246.  
  247.  
  248.  
  249. //-----------------------------------------------------------------------------
  250. // skip_whitespace
  251. //-----------------------------------------------------------------------------
  252. inline static char const* skip_whitespace( char const* s )
  253.     {
  254.     if (s != 0)
  255.     for (; *s != 0 && isspace(*s); s++) /*empty*/;
  256.     return s;
  257.     }
  258.  
  259.  
  260. //-----------------------------------------------------------------------------
  261. // dbf_is_numeric
  262. //-----------------------------------------------------------------------------
  263. static bool dbf_is_numeric( NSString* str, DBFInfo* ip )
  264.     {
  265.     bool neg = false;
  266.     int left_len = 0;
  267.     int right_len = 0;
  268.     char const* s = [str lossyCString];
  269.  
  270.     if (*s == '\0')            // Empty string, treat as null.
  271.     return true;
  272.  
  273.     if (*s == '-')
  274.     { s++; neg = true; }
  275.     else if (*s == '+')
  276.     s++;
  277.  
  278.     s = skip_whitespace( s );
  279.  
  280.     while ('0' <= *s && *s <= '9')
  281.     {
  282.     s++;
  283.     left_len++;
  284.     }
  285.  
  286.     if (*s == DECIMAL_CHAR)
  287.     {
  288.     s++;
  289.     while ('0' <= *s && *s <= '9')
  290.         {
  291.         s++;
  292.         right_len++;
  293.         }
  294.     }
  295.  
  296.     s = skip_whitespace( s );
  297.  
  298.     if (*s == '\0' && (left_len > 0 || right_len > 0))
  299.     {
  300.     if (left_len == 0)
  301.         left_len++;
  302.     if (neg)
  303.         left_len++;
  304.  
  305.     if (ip->max_left < left_len)
  306.         ip->max_left = left_len;
  307.  
  308.     if (ip->max_right < right_len)
  309.         ip->max_right = right_len;
  310.  
  311.     return true;
  312.     }
  313.  
  314.     return false;
  315.     }
  316.  
  317.  
  318. //-----------------------------------------------------------------------------
  319. // dbf_parse_time
  320. //-----------------------------------------------------------------------------
  321. static DBFDataType dbf_parse_time( NSString* str, DBFDateTime& dt )
  322.     {
  323.     char const* s = [str lossyCString];
  324.     DBFDataType type = DBF_TYPE_CHAR;
  325.     if ('0' <= *s && *s <= '9')
  326.     {
  327.     int hour = 0;
  328.     do { hour = hour * 10 + *s - '0'; s++; }
  329.     while ('0' <= *s && *s <= '9');
  330.     if (*s == TIME_CHAR)
  331.         {
  332.         s++;
  333.         if ('0' <= *s && *s <= '9')
  334.         {
  335.         int minute = 0;
  336.         int second = 0;
  337.         do { minute = minute * 10 + *s - '0'; s++; }
  338.         while ('0' <= *s && *s <= '9');
  339.         if (*s == TIME_CHAR)    // seconds are optional.
  340.             {
  341.             s++;
  342.             second = -1;
  343.             if ('0' <= *s && *s <= '9')
  344.             {
  345.             second = 0;
  346.             do { second = second * 10 + *s - '0'; s++; }
  347.             while ('0' <= *s && *s <= '9');
  348.             if (*s == TIME_CHAR || *s == DECIMAL_CHAR)
  349.                 {
  350.                 s++;    // fractions of seconds ignored.
  351.                 while ('0' <= *s && *s <= '9')
  352.                 s++;
  353.                 }
  354.             }
  355.             }
  356.         s = skip_whitespace(s);
  357.         if (*s == '\0' &&
  358.             0 <= hour && hour <= 23 &&
  359.             0 <= minute && minute <= 59 &&
  360.             0 <= second && second <= 59)
  361.             {
  362.             dt.hour = hour;
  363.             dt.minute = minute;
  364.             dt.second = second;
  365.             type = DBF_TYPE_DATETIME;
  366.             }
  367.         }
  368.         }
  369.     }
  370.     return type;
  371.     }
  372.  
  373.  
  374. //-----------------------------------------------------------------------------
  375. // dbf_parse_date
  376. //-----------------------------------------------------------------------------
  377. static DBFDataType dbf_parse_date( NSString* str, DBFDateTime& dt )
  378.     {
  379.     char const* s = [str lossyCString];
  380.     memset( &dt, 0, sizeof(dt) );
  381.     DBFDataType type = DBF_TYPE_CHAR;
  382.     if (*s == '\0')
  383.     type = DBF_TYPE_DATE;
  384.     else if ('0' <= *s && *s <= '9')
  385.     {
  386.     int month = 0;
  387.     do { month = month * 10 + *s - '0'; s++; }
  388.     while ('0' <= *s && *s <= '9');
  389.     if (*s == DATE_CHAR)
  390.         {
  391.         s++;
  392.         if ('0' <= *s && *s <= '9')
  393.         {
  394.         int day = 0;
  395.         do { day = day * 10 + *s - '0'; s++; }
  396.         while ('0' <= *s && *s <= '9');
  397.         if (*s == DATE_CHAR)
  398.             {
  399.             s++;
  400.             if ('0' <= *s && *s <= '9')
  401.             {
  402.             int year = 0;
  403.             do { year = year * 10 + *s - '0'; s++; }
  404.             while ('0' <= *s && *s <= '9');
  405.             if (!AMERICAN_DATE)
  406.                 {
  407.                 int tmp = month;
  408.                 month = day;
  409.                 day = tmp;
  410.                 }
  411.             if (1 <= month && month <= 12 &&
  412.                 1 <= day && day <= 31 &&
  413.                 ((0 <= year && year < 100) ||
  414.                 (1700 < year && year < 3000)))
  415.                 {
  416.                 if (year < 100)
  417.                 {
  418.                 time_t t;
  419.                 time(&t);
  420.                 struct tm const* tm = localtime(&t);
  421.                 int tm_year = tm->tm_year + 1900;
  422.                 year += 1900;
  423.                 while ((tm_year - year) > 90)
  424.                     year += 100;
  425.                 }
  426.                 dt.year = year;
  427.                 dt.month = month;
  428.                 dt.day = day;
  429.                 s = skip_whitespace(s);
  430.                 if (*s == '\0')
  431.                 type = DBF_TYPE_DATE;
  432.                 else 
  433.                 {
  434.                 NSString* tm = [NSString stringWithCString:s];
  435.                 type = dbf_parse_time( tm, dt );
  436.                 }
  437.                 }
  438.             }
  439.             }
  440.         }
  441.         }
  442.     }
  443.     return type;
  444.     }
  445.  
  446.  
  447.  
  448. //=============================================================================
  449. // MiscExporter
  450. //=============================================================================
  451. @implementation MiscExporter(DBF)
  452.  
  453. //-----------------------------------------------------------------------------
  454. // dbfAnalyze:::
  455. //-----------------------------------------------------------------------------
  456. - (DBFInfo*)dbfAnalyze:(int)nrows :(int)ncols :(int const*)map
  457.     {
  458.     int c;
  459.     DBFDateTime datetime;
  460.     DBFInfo* const info = (DBFInfo*) calloc( ncols, sizeof(*info) );
  461.  
  462.     for (c = 0; c < ncols; c++)
  463.     info[c].mask = (DBF_BIT_NUMERIC | DBF_BIT_DATE | DBF_BIT_DATETIME);
  464.  
  465.     for (int r = 0; r < nrows; r++)
  466.     {
  467.     DBFInfo* ip = info;
  468.     for (c = 0; c < ncols; c++,ip++)
  469.         {
  470.         NSString* const s = str_at( r, map[c], tableScroll );
  471.         if (s != 0 && [s length] > 0)
  472.         {
  473.         int const len = [s length];
  474.         if (ip->max_width < len)
  475.             ip->max_width = len;
  476.         unsigned int mask = ip->mask;
  477.         if (mask & DBF_BIT_NUMERIC)
  478.             {
  479.             if (!dbf_is_numeric( s, ip ))
  480.             mask &= ~DBF_BIT_NUMERIC;
  481.             }
  482.         if (mask & (DBF_BIT_DATE | DBF_BIT_DATETIME))
  483.             {
  484.             switch (dbf_parse_date( s, datetime ))
  485.             {
  486.             default:
  487.             case DBF_TYPE_CHAR:
  488.             case DBF_TYPE_NUMERIC:
  489.                 mask &= ~(DBF_BIT_DATE | DBF_BIT_DATETIME);
  490.                 break;
  491.             case DBF_TYPE_DATETIME:
  492.                 mask &= ~DBF_BIT_DATE;
  493.                 break;
  494.             case DBF_TYPE_DATE:
  495.                 break;
  496.             }
  497.             }
  498.         ip->mask = mask;
  499.         }
  500.         }
  501.     }
  502.  
  503.     DBFInfo* ip = info;
  504.     for (c = 0; c < ncols; c++,ip++)
  505.     {
  506.     DBFDataType type = DBF_TYPE_CHAR;
  507.     unsigned int const mask = ip->mask;
  508.     if (mask & DBF_BIT_DATE)
  509.         {
  510.         type = DBF_TYPE_DATE;
  511.         ip->max_width = DBF_DATE_WIDTH;
  512.         }
  513.     else if (mask & DBF_BIT_DATETIME)
  514.         {
  515.         type = DBF_TYPE_DATETIME;
  516.         ip->max_width = DBF_DATE_WIDTH;
  517.         }
  518.     else if (mask & DBF_BIT_NUMERIC)
  519.         {
  520.         type = DBF_TYPE_NUMERIC;
  521.         int width = ip->max_left;
  522.         if (ip->max_right > 0)
  523.         width += ip->max_right + 1;
  524.         if (width > DBF_NUMERIC_LEN_MAX)
  525.         {
  526.         type = DBF_TYPE_CHAR;
  527.         if (width > DBF_CHAR_LEN_MAX)
  528.             width = DBF_CHAR_LEN_MAX;
  529.         }
  530.         ip->max_width = width;
  531.         }
  532.     else if (ip->max_width > DBF_CHAR_LEN_MAX)
  533.         ip->max_width = DBF_CHAR_LEN_MAX;
  534.     ip->type = type;
  535.     }
  536.  
  537.     return info;
  538.     }
  539.  
  540.  
  541. //-----------------------------------------------------------------------------
  542. // dbfFields:::::
  543. //    Set the names of the Field definitions.
  544. //    Make sure all field names conform to DBF field name rules.
  545. //    Make sure all field names are unique within the DBF length limits.
  546. //-----------------------------------------------------------------------------
  547. - (void)dbfFields:(int)row_title_width
  548.     :(DBFFieldDef*)flds :(int)ncols :(int const*)map :(DBFInfo const*)info
  549.     {
  550.     DBFFieldDef* fld = flds;
  551.  
  552.     if (row_title_width > 0)
  553.     {
  554.     strcpy( fld->name, "ROW_TITLE" );
  555.     fld->type = DBF_TYPE_CODE[ DBF_TYPE_CHAR ];
  556.     fld->fld_len = (unsigned char) row_title_width;
  557.     fld++;
  558.     }
  559.  
  560.     DBFInfo const* ip = info;
  561.     for (int c = 0; c < ncols; c++,fld++,ip++)
  562.     {
  563.     NSString* const s = col_title( map[c], tableScroll );
  564.     set_field( s, fld, flds, ip );
  565.     if (ip->type == DBF_TYPE_DATETIME)
  566.         set_time_field( s, ++fld, flds, ip );
  567.     }
  568.     }
  569.  
  570.  
  571. //-----------------------------------------------------------------------------
  572. // dbfHeader::::
  573. //-----------------------------------------------------------------------------
  574. - (int)dbfHeader:(int)row_title_width :(int)nrows :(int)ncols
  575.     :(int const*)map :(DBFInfo const*)info :(FILE*)fp
  576.     {
  577.     BOOL const rowTitlesOn = (row_title_width > 0);
  578.     int rec_len = 1;            // All records have a one-byte flag.
  579.     int nflds = 0;
  580.     int ntime = 0;            // Number of extra time fields.
  581.  
  582.     if (rowTitlesOn)
  583.     {
  584.     rec_len += row_title_width;
  585.     nflds++;
  586.     }
  587.  
  588.     DBFInfo const* ip = info;
  589.     for (int i = 0; i < ncols; i++,ip++)
  590.     {
  591.     if (ip->type == DBF_TYPE_DATETIME)
  592.         {
  593.         if (nflds + 1 < DBF_NUM_FLDS_MAX &&
  594.         rec_len <= DBF_REC_LEN_MAX - DBF_DATE_WIDTH - DBF_TIME_WIDTH)
  595.         {
  596.         rec_len += DBF_DATE_WIDTH + DBF_TIME_WIDTH;
  597.         nflds += 2;
  598.         ntime++;
  599.         }
  600.         else
  601.         break;            // Either get both in, or neither.
  602.         }
  603.     else if (nflds < DBF_NUM_FLDS_MAX &&
  604.         rec_len + ip->max_width < DBF_REC_LEN_MAX)
  605.         {
  606.         rec_len += ip->max_width;
  607.         nflds++;
  608.         }
  609.     }
  610.  
  611.     int const nbytes = sizeof(DBFHeader) + (nflds * sizeof(DBFFieldDef)) + 1;
  612.     DBFHeader* hdr = (DBFHeader*) calloc( 1, nbytes );
  613.     DBFFieldDef* flds = (DBFFieldDef*)(hdr + 1);
  614.  
  615.     time_t t;
  616.     time( &t );
  617.     struct tm const* tm = localtime( &t );
  618.  
  619.     hdr->version    = 0x03;
  620.     hdr->update_yr    = (unsigned char) ((tm->tm_year) & 0x0ff);
  621.     hdr->update_mon    = (unsigned char) ((tm->tm_mon + 1) & 0x0ff);
  622.     hdr->update_day    = (unsigned char) ((tm->tm_mday) & 0x0ff);
  623.  
  624.     int n = nrows;
  625.     hdr->num_recs[0]    = (unsigned char) (n & 0x0ff);    n = n >> 8;
  626.     hdr->num_recs[1]    = (unsigned char) (n & 0x0ff);    n = n >> 8;
  627.     hdr->num_recs[2]    = (unsigned char) (n & 0x0ff);    n = n >> 8;
  628.     hdr->num_recs[3]    = (unsigned char) (n & 0x0ff);
  629.  
  630.     n = nbytes;
  631.     hdr->hdr_len[0]    = (unsigned char) (n & 0x0ff); n = n >> 8;
  632.     hdr->hdr_len[1]    = (unsigned char) (n & 0x0ff);
  633.  
  634.     n = rec_len;
  635.     hdr->rec_len[0] = (unsigned char) (n & 0x0ff); n = n >> 8;
  636.     hdr->rec_len[1] = (unsigned char) (n & 0x0ff);
  637.  
  638.     ncols = (rowTitlesOn ? nflds - 1 : nflds) - ntime;
  639.  
  640.     [self dbfFields:row_title_width:flds:ncols:map:info];
  641.  
  642.     flds[nflds].name[0] = '\r';
  643.  
  644.     fwrite( hdr, nbytes, 1, fp );
  645.  
  646.     free( hdr );
  647.  
  648.     return ncols;
  649.     }
  650.  
  651.  
  652. //-----------------------------------------------------------------------------
  653. // dbf_put_char
  654. //-----------------------------------------------------------------------------
  655. static void dbf_put_char( NSString* str, int width, FILE* fp )
  656.     {
  657.     char const* s = [str lossyCString];
  658.     if (s == 0)
  659.     pad( width, fp );
  660.     else
  661.     {
  662.     int const len = strlen( s );
  663.     int const delta = width - len;
  664.     if (delta > 0)
  665.         {
  666.         fwrite( s, len, 1, fp );
  667.         pad( delta, fp );
  668.         }
  669.     else
  670.         fwrite( s, width, 1, fp );
  671.     }
  672.     }
  673.  
  674.  
  675. //-----------------------------------------------------------------------------
  676. // dbf_put_numeric
  677. //-----------------------------------------------------------------------------
  678. static void dbf_put_numeric( NSString* str, DBFInfo const* ip, FILE* fp )
  679.     {
  680.     if (str == 0)
  681.     {
  682.     if (ip->max_left > 1)
  683.         pad( ip->max_left - 1, fp );
  684.     fputc( '0', fp );
  685.     if (ip->max_right > 0)
  686.         {
  687.         fputc( '.', fp );
  688.         repchar( ip->max_right, '0', fp );
  689.         }
  690.     }
  691.     else
  692.     {
  693.     bool neg = false;
  694.     int right_len = 0;
  695.  
  696.     char const* s = [str lossyCString];
  697.     s = skip_whitespace(s);
  698.     if (*s == '-')
  699.         { s++; neg = true; }
  700.     else if (*s == '+')
  701.         s++;
  702.     s = skip_whitespace(s);
  703.     char const* const left_part = s;
  704.  
  705.     while ('0' <= *s && *s <= '9')
  706.         s++;
  707.  
  708.     int const left_len = (s - left_part);
  709.  
  710.     char const* right_part = s;
  711.     if (*s == DECIMAL_CHAR)
  712.         {
  713.         s++;
  714.         right_part++;
  715.         while ('0' <= *s && *s <= '9')
  716.         s++;
  717.         right_len = (s - right_part);
  718.         }
  719.  
  720.     int left_pad = ip->max_width - left_len;
  721.     if (left_len == 0)
  722.         left_pad--;            // Room for required '0'.
  723.  
  724.     if (ip->max_right > 0)        // Room for decimal and fraction.
  725.         left_pad -= ip->max_right + 1;
  726.  
  727.     if (neg)
  728.         left_pad--;            // Room for '-'.
  729.  
  730.     pad( left_pad, fp );
  731.     if (neg)
  732.         fputc( '-', fp );
  733.     if (left_len <= 0)
  734.         fputc( '0', fp );
  735.     else
  736.         fwrite( left_part, 1, left_len, fp );
  737.  
  738.     if (ip->max_right > 0)
  739.         {
  740.         fputc( '.', fp );
  741.         if (right_len > 0)
  742.         fwrite( right_part, 1, right_len, fp );
  743.         if (right_len < ip->max_right)
  744.         repchar( ip->max_right - right_len, '0', fp );
  745.         }
  746.     }
  747.     }
  748.  
  749.  
  750. //-----------------------------------------------------------------------------
  751. // dbf_put_date
  752. //-----------------------------------------------------------------------------
  753. static void dbf_put_date( NSString* str, DBFInfo const* ip, FILE* fp )
  754.     {
  755.     char const* s = (str != 0 ? [str lossyCString] : 0);
  756.     s = skip_whitespace(s);
  757.     if (s == 0 || *s == 0)
  758.     {
  759.     fputs( DBF_NULL_DATE_STR, fp );
  760.     if (ip->type == DBF_TYPE_DATETIME)
  761.         fputs( DBF_NULL_TIME_STR, fp );
  762.     }
  763.     else
  764.     {
  765.     char buff[ 64 ];
  766.     DBFDateTime dt;
  767.     dbf_parse_date( str, dt );
  768.     sprintf( buff, "%04d%02d%02d", dt.year, dt.month, dt.day );
  769.     fputs( buff, fp );
  770.     if (ip->type == DBF_TYPE_DATETIME)
  771.         {
  772.         sprintf( buff, "%02d:%02d", dt.hour, dt.minute );
  773.         fputs( buff, fp );
  774.         }
  775.     }
  776.     }
  777.  
  778.  
  779. //-----------------------------------------------------------------------------
  780. // exportDBF:
  781. //-----------------------------------------------------------------------------
  782. - (void)exportDBF:(FILE*)fp
  783.     {
  784.     int const nrows = [tableScroll numberOfRows];
  785.     int const tcols = [tableScroll numberOfColumns];
  786.     int* col_map = [self makeColMap:tcols];
  787.     DBFInfo* info = [self dbfAnalyze:nrows:tcols:col_map];
  788.     int row_title_width = [self rowTitleCharWidth:nrows];
  789.  
  790.     if (row_title_width > DBF_CHAR_LEN_MAX)
  791.     row_title_width = DBF_CHAR_LEN_MAX;
  792.  
  793.     int const ncols =
  794.     [self dbfHeader:row_title_width:nrows:tcols:col_map:info:fp];
  795.  
  796.     for (int r = 0; r < nrows; r++)
  797.     {
  798.     int const pr = row_at( r, tableScroll );
  799.     fputc( ' ', fp );
  800.     if (row_title_width > 0)
  801.         dbf_put_char( row_title( pr, tableScroll ), row_title_width, fp );
  802.     DBFInfo const* ip = info;
  803.     for (int c = 0; c < ncols; c++,ip++)
  804.         {
  805.         NSString* const s = str_at( pr, col_map[c], tableScroll );
  806.         switch (ip->type)
  807.         {
  808.         case DBF_TYPE_CHAR:
  809.             dbf_put_char( s, ip->max_width, fp );
  810.             break;
  811.         case DBF_TYPE_NUMERIC:
  812.             dbf_put_numeric( s, ip, fp );
  813.             break;
  814.         case DBF_TYPE_DATE:
  815.         case DBF_TYPE_DATETIME:
  816.             dbf_put_date( s, ip, fp );
  817.             break;
  818.         }
  819.         }
  820.     }
  821.     fputc( '\x1a', fp );    // Terminating Control-Z.
  822.  
  823.     free( info );
  824.     free( col_map );
  825.     }
  826.  
  827. @end
  828.