/* 
/ freexl.c
/
/ FreeXL implementation
/
/ version  1.0, 2011 July 26
/
/ Author: Sandro Furieri a.furieri@lqt.it
/
/ ------------------------------------------------------------------------------
/ 
/ Version: MPL 1.1/GPL 2.0/LGPL 2.1
/ 
/ The contents of this file are subject to the Mozilla Public License Version
/ 1.1 (the "License"); you may not use this file except in compliance with
/ the License. You may obtain a copy of the License at
/ http://www.mozilla.org/MPL/
/ 
/ Software distributed under the License is distributed on an "AS IS" basis,
/ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
/ for the specific language governing rights and limitations under the
/ License.
/
/ The Original Code is the FreeXL library
/
/ The Initial Developer of the Original Code is Alessandro Furieri
/ 
/ Portions created by the Initial Developer are Copyright (C) 2011
/ the Initial Developer. All Rights Reserved.
/ 
/ Contributor(s):
/ Brad Hards
/ 
/ Alternatively, the contents of this file may be used under the terms of
/ either the GNU General Public License Version 2 or later (the "GPL"), or
/ the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
/ in which case the provisions of the GPL or the LGPL are applicable instead
/ of those above. If you wish to allow use of your version of this file only
/ under the terms of either the GPL or the LGPL, and not to allow others to
/ use your version of this file under the terms of the MPL, indicate your
/ decision by deleting the provisions above and replace them with the notice
/ and other provisions required by the GPL or the LGPL. If you do not delete
/ the provisions above, a recipient may use your version of this file under
/ the terms of any one of the MPL, the GPL or the LGPL.
/ 
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#if defined(__MINGW32__) || defined(_WIN32)
#define LIBICONV_STATIC
#include <iconv.h>
#define LIBCHARSET_STATIC
#ifdef _MSC_VER
/* <localcharset.h> isn't supported on OSGeo4W */
/* applying a tricky workaround to fix this issue */
extern const char *locale_charset (void);
#else /* sane Windows - not OSGeo4W */
#include <localcharset.h>
#endif /* end localcharset */
#else /* not WINDOWS */
#if defined(__APPLE__) || defined(__ANDROID__)
#include <iconv.h>
#include <localcharset.h>
#else /* neither Mac OsX nor Android */
#include <iconv.h>
#include <langinfo.h>
#endif
#endif

#if defined(_WIN32) && !defined(__MINGW32__)
#include "config-msvc.h"
#else
#include "config.h"
#endif

#include "freexl.h"
#include "freexl_internals.h"

#if defined(_WIN32) && !defined(__MINGW32__)
/* MSVC compiler doesn't support lround() at all */
static double
round (double num)
{
    double integer = ceil (num);
    if (num > 0)
	return integer - num > 0.5 ? integer - 1.0 : integer;
    return integer - num >= 0.5 ? integer - 1.0 : integer;
}

static long
lround (double num)
{
    long integer = (long) round (num);
    return integer;
}
#endif

static void
swap16 (biff_word16 * word)
{
/* Endianness: swapping a 16 bit word */
    unsigned char save = word->bytes[0];
    word->bytes[0] = word->bytes[1];
    word->bytes[1] = save;
}

static void
swap32 (biff_word32 * word)
{
/* Endianness: swapping a 32 bit word */
    unsigned char save0 = word->bytes[0];
    unsigned char save1 = word->bytes[1];
    unsigned char save2 = word->bytes[2];
    word->bytes[0] = word->bytes[3];
    word->bytes[1] = save2;
    word->bytes[2] = save1;
    word->bytes[3] = save0;
}

static void
swap_float (biff_float * word)
{
/* Endianness: swapping a 64 bit float */
    unsigned char save0 = word->bytes[0];
    unsigned char save1 = word->bytes[1];
    unsigned char save2 = word->bytes[2];
    unsigned char save3 = word->bytes[3];
    unsigned char save4 = word->bytes[4];
    unsigned char save5 = word->bytes[5];
    unsigned char save6 = word->bytes[6];
    word->bytes[0] = word->bytes[7];
    word->bytes[1] = save6;
    word->bytes[2] = save5;
    word->bytes[3] = save4;
    word->bytes[4] = save3;
    word->bytes[5] = save2;
    word->bytes[6] = save1;
    word->bytes[7] = save0;
}

static int
biff_set_utf8_converter (biff_workbook * workbook)
{
/* attempting to set up a charset converter to UTF-8 */
    iconv_t cvt = (iconv_t) (-1);
    if (workbook->utf8_converter)
	iconv_close (workbook->utf8_converter);
    workbook->utf8_converter = NULL;
    switch (workbook->biff_code_page)
      {
      case 0x016F:
	  cvt = iconv_open ("UTF-8", "ASCII");
	  break;
      case 0x01B5:
	  cvt = iconv_open ("UTF-8", "CP437");
	  break;
      case 0x02D0:
	  cvt = iconv_open ("UTF-8", "CP720");
	  break;
      case 0x02E1:
	  cvt = iconv_open ("UTF-8", "CP737");
	  break;
      case 0x0307:
	  cvt = iconv_open ("UTF-8", "CP775");
	  break;
      case 0x0352:
	  cvt = iconv_open ("UTF-8", "CP850");
	  break;
      case 0x0354:
	  cvt = iconv_open ("UTF-8", "CP852");
	  break;
      case 0x0357:
	  cvt = iconv_open ("UTF-8", "CP855");
	  break;
      case 0x0359:
	  cvt = iconv_open ("UTF-8", "CP857");
	  break;
      case 0x035A:
	  cvt = iconv_open ("UTF-8", "CP858");
	  break;
      case 0x035C:
	  cvt = iconv_open ("UTF-8", "CP860");
	  break;
      case 0x035D:
	  cvt = iconv_open ("UTF-8", "CP861");
	  break;
      case 0x035E:
	  cvt = iconv_open ("UTF-8", "CP862");
	  break;
      case 0x035F:
	  cvt = iconv_open ("UTF-8", "CP863");
	  break;
      case 0x0360:
	  cvt = iconv_open ("UTF-8", "CP864");
	  break;
      case 0x0361:
	  cvt = iconv_open ("UTF-8", "CP865");
	  break;
      case 0x0362:
	  cvt = iconv_open ("UTF-8", "CP866");
	  break;
      case 0x0365:
	  cvt = iconv_open ("UTF-8", "CP869");
	  break;
      case 0x036A:
	  cvt = iconv_open ("UTF-8", "CP874");
	  break;
      case 0x03A4:
	  cvt = iconv_open ("UTF-8", "CP932");
	  break;
      case 0x03A8:
	  cvt = iconv_open ("UTF-8", "CP936");
	  break;
      case 0x03B5:
	  cvt = iconv_open ("UTF-8", "CP949");
	  break;
      case 0x03B6:
	  cvt = iconv_open ("UTF-8", "CP950");
	  break;
      case 0x04B0:
	  cvt = iconv_open ("UTF-8", "UTF-16LE");
	  break;
      case 0x04E2:
	  cvt = iconv_open ("UTF-8", "CP1250");
	  break;
      case 0x04E3:
	  cvt = iconv_open ("UTF-8", "CP1251");
	  break;
      case 0x04E4:
      case 0x8001:
	  cvt = iconv_open ("UTF-8", "CP1252");
	  break;
      case 0x04E5:
	  cvt = iconv_open ("UTF-8", "CP1253");
	  break;
      case 0x04E6:
	  cvt = iconv_open ("UTF-8", "CP1254");
	  break;
      case 0x04E7:
	  cvt = iconv_open ("UTF-8", "CP1255");
	  break;
      case 0x04E8:
	  cvt = iconv_open ("UTF-8", "CP1256");
	  break;
      case 0x04E9:
	  cvt = iconv_open ("UTF-8", "CP1257");
	  break;
      case 0x04EA:
	  cvt = iconv_open ("UTF-8", "CP1258");
	  break;
      case 0x0551:
	  cvt = iconv_open ("UTF-8", "CP1361");
	  break;
      case 0x2710:
      case 0x8000:
	  cvt = iconv_open ("UTF-8", "MacRoman");
	  break;
      };
    if (cvt == (iconv_t) (-1))
	return 0;
    workbook->utf8_converter = cvt;
    return 1;
}

static char *
convert_to_utf8 (iconv_t converter, const char *buf, int buflen, int *err)
{
/* converting a string to UTF8 */
    char *utf8buf = 0;
#if !defined(__MINGW32__) && defined(_WIN32)
    const char *pBuf;
#else
    char *pBuf;
#endif
    size_t len;
    size_t utf8len;
    int maxlen = buflen * 4;
    char *pUtf8buf;
    *err = FREEXL_OK;
    if (!converter)
      {
	  *err = FREEXL_UNSUPPORTED_CHARSET;
	  return NULL;
      }
    utf8buf = malloc (maxlen);
    len = buflen;
    utf8len = maxlen;
    pBuf = (char *) buf;
    pUtf8buf = utf8buf;
    if (iconv (converter, &pBuf, &len, &pUtf8buf, &utf8len) == (size_t) (-1))
      {
	  free (utf8buf);
	  *err = FREEXL_INVALID_CHARACTER;
	  return NULL;
      }
    utf8buf[maxlen - utf8len] = '\0';
    return utf8buf;
}

static void
get_unicode_params (unsigned char *addr, int swap, unsigned int *start_offset,
		    int *real_utf16, unsigned int *extra_skip)
{
/* retrieving Unicode string params */
    biff_word16 word16;
    biff_word32 word32;
    int skip_1 = 0;
    int skip_2 = 0;
    unsigned char *p_string = addr;
    unsigned char mask = *p_string;

/*
 * a bitwise mask
 * 0x01 - the string is 'real' UTF16LE
 *        otherwise any high-order ZEROes are suppressed
 *        ['stripped' UTF16]
 *
 * 0x04 - an extra 32-bit field is present 
 * 0x08 - another extra 16-bit field is present
 *        (such extra (optional) fields are intended by MS  
 *        for text formatting purposes: we'll ignore them
 *        at all, simply adjusting any offset as required)
 */
    p_string++;
    if ((mask & 0x01) == 0x01)
	*real_utf16 = 1;
    else
	*real_utf16 = 0;
    if ((mask & 0x04) == 0x04)
      {
	  /* optional field: 32-bits */
	  memcpy (word32.bytes, p_string, 2);
	  if (swap)
	      swap32 (&word32);
	  skip_1 = word32.value;
	  p_string += 4;
      }
    if ((mask & 0x08) == 0x08)
      {
	  /* optional field 16-bits */
	  memcpy (word16.bytes, p_string, 2);
	  if (swap)
	      swap16 (&word16);
	  skip_2 = word16.value;
	  p_string += 2;
      }
    *start_offset = p_string - addr;
    *extra_skip = skip_1 + (skip_2 * 4);
}

static int
parse_unicode_string (iconv_t converter, unsigned short characters,
		      int real_utf16, unsigned char *unicode_string,
		      char **utf8_string)
{
/* attemping to convert an Unicode string into UTF-8 */
    unsigned int len = characters * 2;
    char *string;
    int err;
    unsigned char *p_string = unicode_string;

    string = malloc (len);
    if (!real_utf16)
      {
	  /* 'stripped' UTF-16: requires padding */
	  unsigned int i;
	  for (i = 0; i < characters; i++)
	    {
		*(string + (i * 2)) = *p_string;
		p_string++;
		*(string + ((i * 2) + 1)) = 0x00;
	    }
      }
    else
      {
	  /* already encoded as UTF-16 */
	  memcpy (string, p_string, len);
      }
/* converting text to UTF-8 */
    *utf8_string = convert_to_utf8 (converter, string, len, &err);
    free (string);
    if (err)
	return 0;
    return 1;
}

static int
decode_rk_integer (unsigned char *bytes, int *value, int swap)
{
/* attempting to decode an RK value as an INT-32 */
    biff_word32 word32;
    unsigned char mask = bytes[0];
    if ((mask & 0x02) == 0x02 && (mask & 0x01) == 0x00)
      {
	  /* ok, this RK value is an INT-32 */
	  memcpy (word32.bytes, bytes, 4);
	  if (swap)
	      swap32 (&word32);
	  *value = word32.signed_value >> 2;	/* right shift: 2 bits */
	  return 1;
      }
    return 0;
}

static int
decode_rk_float (unsigned char *bytes, double *value, int swap)
{
/* attempting to decode an RK value as a DOUBLE-FLOAT */
    biff_word32 word32;
    biff_float word_float;
    int int_value;
    double dbl_value;
    int div_by_100 = 0;
    unsigned char mask = bytes[0];
    if ((mask & 0x02) == 0x02 && (mask & 0x01) == 0x01)
      {
	  /* ok, this RK value is an INT-32 (divided by 100) */
	  memcpy (word32.bytes, bytes, 4);
	  if (swap)
	      swap32 (&word32);
	  int_value = word32.signed_value >> 2;	/* right shift: 2 bits */
	  *value = (double) int_value / 100.0;
	  return 1;
      }
    if ((mask & 0x02) == 0x00)
      {
	  /* ok, this RK value is a FLOAT */
	  if ((mask & 0x01) == 0x01)
	      div_by_100 = 1;
	  memcpy (word32.bytes, bytes, 4);
	  if (swap)
	      swap32 (&word32);
	  int_value = word32.value;
	  int_value &= 0xfffffffc;
	  word32.value = int_value;
	  memset (word_float.bytes, '\0', 8);
	  if (swap)
	      memcpy (word_float.bytes, word32.bytes, 4);
	  else
	      memcpy (word_float.bytes + 4, word32.bytes, 4);
	  dbl_value = word_float.value;
	  if (div_by_100)
	      dbl_value /= 100.0;
	  *value = dbl_value;
	  return 1;
      }
    return 0;
}

static int
check_xf_datetime (biff_workbook * workbook, unsigned short xf_index,
		   int *is_date, int *is_datetime, int *is_time)
{
/* testing for DATE/DATETIME/TIME formats */
    unsigned short idx;
    unsigned short format_index;
    if (xf_index >= workbook->biff_xf_next_index)
	return 0;
    format_index = workbook->biff_xf_array[xf_index];
    for (idx = 0; idx < workbook->max_format_index; idx++)
      {
	  biff_format *format = workbook->format_array + idx;
	  if (format->format_index == format_index)
	    {
		*is_date = format->is_date;
		*is_datetime = format->is_datetime;
		*is_time = format->is_time;
		return 1;
	    }
      }
    *is_date = 0;
    *is_datetime = 0;
    *is_time = 0;
    return 1;
}

static int
check_xf_datetime_58 (biff_workbook * workbook, unsigned short xf_index,
		      int *is_date, int *is_datetime, int *is_time)
{
/* 
/ testing for DATE/DATETIME/TIME formats 
/ BIFF5 and BIFF8 versions
*/
    unsigned short format_index;
    if (xf_index >= workbook->biff_xf_next_index)
	return 0;
    format_index = workbook->biff_xf_array[xf_index];
    switch (format_index)
      {
      case 14:
      case 15:
      case 16:
      case 17:
	  /* BIFF5/BIFF8 built-in DATE formats */
	  *is_date = 1;
	  *is_datetime = 0;
	  *is_time = 0;
	  return 1;
      case 18:
      case 19:
      case 20:
      case 21:
      case 45:
      case 46:
      case 47:
	  /* BIFF5/BIFF8 built-in TIME formats */
	  *is_date = 0;
	  *is_datetime = 0;
	  *is_time = 1;
	  return 1;
      case 22:
	  /* BIFF5/BIFF8 built-in DATETIME formats */
	  *is_date = 0;
	  *is_datetime = 1;
	  *is_time = 0;
	  return 1;
      default:
	  break;
      };
    return check_xf_datetime (workbook, xf_index, is_date, is_datetime,
			      is_time);
}

static void
compute_time (int *hh, int *mm, int *ss, double percent)
{
/* computing an Excel time */
    int hours;
    int mins;
    int secs;
    double day_seconds = 24 * 60 * 60;
    day_seconds *= percent;
    secs = lround (day_seconds);
    hours = secs / 3600;
    secs -= hours * 3600;
    mins = secs / 60;
    secs -= mins * 60;
    *hh = hours;
    *mm = mins;
    *ss = secs;
}

static void
compute_date (int *year, int *month, int *day, int count)
{
/* coumputing an Excel date */
    int i;
    int yy = *year;
    int mm = *month;
    int dd = *day;
    for (i = 1; i < count; i++)
      {
	  int last_day_of_month;
	  switch (mm)
	    {
	    case 2:
		if ((yy % 4) == 0)
		  {
		      /* February, leap year */
		      last_day_of_month = 29;
		  }
		else
		    last_day_of_month = 28;
		break;
	    case 4:
	    case 6:
	    case 9:
	    case 11:
		last_day_of_month = 30;
		break;
	    default:
		last_day_of_month = 31;
		break;
	    };
	  if (dd == last_day_of_month)
	    {
		if (mm == 12)
		  {
		      mm = 1;
		      yy += 1;
		  }
		else
		    mm += 1;
		dd = 1;
	    }
	  else
	      dd += 1;
      }
    *year = yy;
    *month = mm;
    *day = dd;
}

static int
set_date_int_value (biff_workbook * workbook, unsigned int row,
		    unsigned short col, unsigned short mode, int num)
{
/* setting a DATE value to some cell */
    biff_cell_value *p_cell;
    char *string;
    char buf[64];
    unsigned int len;
    int yy;
    int mm;
    int dd;
    int count = num;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    if (mode)
      {
	  yy = 1904;
	  mm = 1;
	  dd = 2;
      }
    else
      {
	  yy = 1900;
	  mm = 1;
	  dd = 1;
      }
    compute_date (&yy, &mm, &dd, count);
    sprintf (buf, "%04d-%02d-%02d", yy, mm, dd);
    len = strlen (buf);
    string = malloc (len + 1);
    if (!string)
	return FREEXL_INSUFFICIENT_MEMORY;
    strcpy (string, buf);

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_DATE;
    p_cell->value.text_value = string;
    return FREEXL_OK;
}

static int
set_datetime_int_value (biff_workbook * workbook, unsigned int row,
			unsigned short col, unsigned short mode, int num)
{
/* setting a DATETIME value to some cell */
    biff_cell_value *p_cell;
    char *string;
    char buf[64];
    unsigned int len;
    int yy;
    int mm;
    int dd;
    int count = num;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    if (mode)
      {
	  yy = 1904;
	  mm = 1;
	  dd = 2;
      }
    else
      {
	  yy = 1900;
	  mm = 1;
	  dd = 1;
      }
    compute_date (&yy, &mm, &dd, count);
    sprintf (buf, "%04d-%02d-%02d 00:00:00", yy, mm, dd);
    len = strlen (buf);
    string = malloc (len + 1);
    if (!string)
	return FREEXL_INSUFFICIENT_MEMORY;
    strcpy (string, buf);

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_DATETIME;
    p_cell->value.text_value = string;
    return FREEXL_OK;
}

static int
set_date_double_value (biff_workbook * workbook, unsigned int row,
		       unsigned short col, unsigned short mode, double num)
{
/* setting a DATE value to some cell */
    biff_cell_value *p_cell;
    char *string;
    char buf[64];
    unsigned int len;
    int yy;
    int mm;
    int dd;
    int count = (int) floor (num);

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    if (mode)
      {
	  yy = 1904;
	  mm = 1;
	  dd = 2;
      }
    else
      {
	  yy = 1900;
	  mm = 1;
	  dd = 1;
      }
    compute_date (&yy, &mm, &dd, count);
    sprintf (buf, "%04d-%02d-%02d", yy, mm, dd);
    len = strlen (buf);
    string = malloc (len + 1);
    if (!string)
	return FREEXL_INSUFFICIENT_MEMORY;
    strcpy (string, buf);

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_DATE;
    p_cell->value.text_value = string;
    return FREEXL_OK;
}

static int
set_datetime_double_value (biff_workbook * workbook, unsigned int row,
			   unsigned short col, unsigned short mode, double num)
{
/* setting a DATETIME value to some cell */
    biff_cell_value *p_cell;
    char *string;
    char buf[64];
    unsigned int len;
    int yy;
    int mm;
    int dd;
    int h;
    int m;
    int s;
    int count = (int) floor (num);
    double percent = num - (double) count;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    if (mode)
      {
	  yy = 1904;
	  mm = 1;
	  dd = 2;
      }
    else
      {
	  yy = 1900;
	  mm = 1;
	  dd = 1;
      }
    compute_date (&yy, &mm, &dd, count);
    compute_time (&h, &m, &s, percent);
    sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d", yy, mm, dd, h, m, s);
    len = strlen (buf);
    string = malloc (len + 1);
    if (!string)
	return FREEXL_INSUFFICIENT_MEMORY;
    strcpy (string, buf);

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_DATETIME;
    p_cell->value.text_value = string;
    return FREEXL_OK;
}

static int
set_time_double_value (biff_workbook * workbook, unsigned int row,
		       unsigned short col, double num)
{
/* setting a TIME value to some cell */
    biff_cell_value *p_cell;
    char *string;
    char buf[64];
    unsigned int len;
    int h;
    int m;
    int s;
    int count = (int) floor (num);
    double percent = num - (double) count;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    compute_time (&h, &m, &s, percent);
    sprintf (buf, "%02d:%02d:%02d", h, m, s);
    len = strlen (buf);
    string = malloc (len + 1);
    if (!string)
	return FREEXL_INSUFFICIENT_MEMORY;
    strcpy (string, buf);

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_TIME;
    p_cell->value.text_value = string;
    return FREEXL_OK;
}

static int
set_int_value (biff_workbook * workbook, unsigned int row, unsigned short col,
	       int num)
{
/* setting an INTEGER value to some cell */
    biff_cell_value *p_cell;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_INT;
    p_cell->value.int_value = num;
    return FREEXL_OK;
}

static int
set_double_value (biff_workbook * workbook, unsigned int row,
		  unsigned short col, double num)
{
/* setting a DOUBLE value to some cell */
    biff_cell_value *p_cell;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    p_cell->type = FREEXL_CELL_DOUBLE;
    p_cell->value.dbl_value = num;
    return FREEXL_OK;
}

static int
set_text_value (biff_workbook * workbook, unsigned int row, unsigned short col,
		char *text)
{
/* setting a TEXT value to some cell */
    biff_cell_value *p_cell;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    if (!text)
      {
	  p_cell->type = FREEXL_CELL_NULL;
	  return FREEXL_OK;
      }
    p_cell->type = FREEXL_CELL_TEXT;
    p_cell->value.text_value = text;
    return FREEXL_OK;
}

static int
set_sst_value (biff_workbook * workbook, unsigned int row, unsigned short col,
	       const char *text)
{
/* setting an SST-TEXT value to some cell */
    biff_cell_value *p_cell;

    if (workbook->active_sheet == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (row >= workbook->active_sheet->rows
	|| col >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + col;
    if (!text)
      {
	  p_cell->type = FREEXL_CELL_NULL;
	  return FREEXL_OK;
      }
    p_cell->type = FREEXL_CELL_SST_TEXT;
    p_cell->value.sst_value = text;
    return FREEXL_OK;
}

static fat_chain *
alloc_fat_chain (int swap, unsigned short sector_shift,
		 unsigned int directory_start)
{
/* allocating the FAT chain */
    fat_chain *chain = malloc (sizeof (fat_chain));
    if (!chain)
	return NULL;
    chain->swap = swap;
    if (sector_shift == 12)
	chain->sector_size = 4096;
    else
	chain->sector_size = 512;
    chain->next_sector = 0;
    chain->directory_start = directory_start;
    chain->first = NULL;
    chain->last = NULL;
    chain->fat_array = NULL;
    chain->fat_array_count = 0;
    chain->miniCutOff = 0;
    chain->next_sectorMini = 0;
    chain->firstMini = NULL;
    chain->lastMini = NULL;
    chain->miniFAT_array = NULL;
    chain->miniFAT_array_count = 0;
    chain->miniFAT_len = 0;
    chain->miniStream = NULL;
    return chain;
}

static void
destroy_fat_chain (fat_chain * chain)
{
/* destroying a FAT chain */
    fat_entry *entry;
    fat_entry *entry_n;
    if (!chain)
	return;
/* destroying the main FAT */
    entry = chain->first;
    while (entry)
      {
	  entry_n = entry->next;
	  free (entry);
	  entry = entry_n;
      }
    if (chain->fat_array)
	free (chain->fat_array);
/* destroying the miniFAT */
    entry = chain->firstMini;
    while (entry)
      {
	  entry_n = entry->next;
	  free (entry);
	  entry = entry_n;
      }
    if (chain->miniFAT_array)
	free (chain->miniFAT_array);
    if (chain->miniStream)
	free (chain->miniStream);
    free (chain);
}

static unsigned int
get_worksheet_count (biff_workbook * workbook)
{
/* counting how many Worksheet are into the Workbook */
    unsigned int count = 0;
    biff_sheet *p_sheet = workbook->first_sheet;
    while (p_sheet)
      {
	  count++;
	  p_sheet = p_sheet->next;
      }
    return count;
}

static void
destroy_cell (biff_cell_value * cell)
{
/* destroying a cell */
    if (cell->type == FREEXL_CELL_TEXT || cell->type == FREEXL_CELL_DATE
	|| cell->type == FREEXL_CELL_DATETIME || cell->type == FREEXL_CELL_TIME)
      {
	  if (cell->value.text_value)
	      free (cell->value.text_value);
      }
}

static void
destroy_sheet (biff_sheet * sheet)
{
/* destroying a Sheet struct */
    unsigned int row;
    unsigned int col;
    biff_cell_value *p_cell;

    if (!sheet)
	return;
    if (sheet->utf8_name)
	free (sheet->utf8_name);
    if (sheet->cell_values)
      {
	  for (row = 0; row < sheet->rows; row++)
	    {
		/* destroying rows */
		p_cell = sheet->cell_values + (row * sheet->columns);
		for (col = 0; col < sheet->columns; col++)
		  {
		      /* destroying cells */
		      destroy_cell (p_cell);
		      p_cell++;
		  }
	    }
      }
    free (sheet->cell_values);
    free (sheet);
}

static int
allocate_cells (biff_workbook * workbook)
{
/* allocating the rows and cells for the active Worksheet */
    unsigned int row;
    unsigned int col;
    biff_cell_value *p_cell;

    if (workbook == NULL)
	return FREEXL_NULL_ARGUMENT;
    if (workbook->active_sheet == NULL)
	return FREEXL_NULL_ARGUMENT;

/* allocating the cell values array */
    workbook->active_sheet->cell_values =
	malloc (sizeof (biff_cell_value) *
		(workbook->active_sheet->rows *
		 workbook->active_sheet->columns));
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_INSUFFICIENT_MEMORY;
    for (row = 0; row < workbook->active_sheet->rows; row++)
      {
	  /* initializing NULL cell values */
	  p_cell =
	      workbook->active_sheet->cell_values +
	      (row * workbook->active_sheet->columns);
	  for (col = 0; col < workbook->active_sheet->columns; col++)
	    {
		p_cell->type = FREEXL_CELL_NULL;
		p_cell++;
	    }
      }

    return FREEXL_OK;
}

static int
add_sheet_to_workbook (biff_workbook * workbook, unsigned int offset,
		       unsigned char visible, unsigned char type, char *name)
{
/* appending a further Sheet to the Workbook */
    biff_sheet *sheet = malloc (sizeof (biff_sheet));
    if (!sheet)
	return 0;
    sheet->start_offset = offset;
    sheet->visible = visible;
    sheet->type = type;
    sheet->utf8_name = name;
    sheet->rows = 0;
    sheet->columns = 0;
    sheet->cell_values = NULL;
    sheet->valid_dimension = 0;
    sheet->already_done = 0;
    sheet->next = NULL;

/* updating the linked list */
    if (workbook->first_sheet == NULL)
	workbook->first_sheet = sheet;
    if (workbook->last_sheet != NULL)
	workbook->last_sheet->next = sheet;
    workbook->last_sheet = sheet;
    return 1;
}

static void
destroy_workbook (biff_workbook * workbook)
{
/* destroyng the Workbook struct */
    biff_sheet *p_sheet;
    biff_sheet *p_sheet_n;
    if (workbook)
      {
	  if (workbook->xls)
	      fclose (workbook->xls);
	  if (workbook->utf8_converter)
	      iconv_close (workbook->utf8_converter);
	  if (workbook->utf16_converter)
	      iconv_close (workbook->utf16_converter);
	  if (workbook->shared_strings.utf8_strings != NULL)
	    {
		/* destroying the Shared Strings Table [SST] */
		unsigned int i;
		for (i = 0; i < workbook->shared_strings.string_count; i++)
		  {
		      char *string =
			  *(workbook->shared_strings.utf8_strings + i);
		      if (string != NULL)
			  free (string);
		  }
		free (workbook->shared_strings.utf8_strings);
	    }
	  if (workbook->shared_strings.current_utf16_buf)
	      free (workbook->shared_strings.current_utf16_buf);
	  p_sheet = workbook->first_sheet;
	  while (p_sheet)
	    {
		p_sheet_n = p_sheet->next;
		destroy_sheet (p_sheet);
		p_sheet = p_sheet_n;
	    }
	  if (workbook->fat)
	      destroy_fat_chain (workbook->fat);
	  free (workbook);
      }
}

static biff_workbook *
alloc_workbook (int magic)
{
/* allocating and initializing the Workbook struct */
    biff_workbook *workbook = malloc (sizeof (biff_workbook));
    if (!workbook)
	return NULL;
    workbook->magic1 = magic;
    workbook->magic2 = FREEXL_MAGIC_END;
    workbook->xls = NULL;
    workbook->fat = NULL;
    workbook->cfbf_version = 0;
    workbook->cfbf_sector_size = 0;
    workbook->start_sector = 0;
    workbook->size = 0;
    workbook->current_sector = 0;
    workbook->bytes_read = 0;
    workbook->current_offset = 0;
    memset (workbook->sector_buf, 0, sizeof (workbook->sector_buf));
    workbook->p_in = workbook->sector_buf;
    workbook->sector_end = 0;
    workbook->sector_ready = 0;
    workbook->ok_bof = -1;
    workbook->biff_version = 0;
    workbook->biff_max_record_size = 0;
    workbook->biff_content_type = 0;
    workbook->biff_code_page = 0;
    workbook->biff_book_code_page = 0;
    workbook->biff_date_mode = 0;
    workbook->biff_obfuscated = 0;
    workbook->utf8_converter = NULL;
    workbook->utf16_converter = NULL;
    memset (workbook->record, 0, sizeof (workbook->record));
    workbook->record_type = 0;
    workbook->prev_record_type = 0;
    workbook->record_size = 0;
    workbook->shared_strings.string_count = 0;
    workbook->shared_strings.utf8_strings = NULL;
    workbook->shared_strings.current_index = 0;
    workbook->shared_strings.current_utf16_buf = NULL;
    workbook->shared_strings.current_utf16_len = 0;
    workbook->shared_strings.current_utf16_off = 0;
    workbook->shared_strings.current_utf16_skip = 0;
    workbook->first_sheet = NULL;
    workbook->last_sheet = NULL;
    workbook->active_sheet = NULL;
    workbook->second_pass = 0;
    workbook->max_format_index = 0;
    workbook->biff_xf_next_index = 0;
    return workbook;
}

static int
insert_into_fat_chain (fat_chain * chain, unsigned int sector)
{
/* inserting a sector into the FAT chain [linked list] */
    fat_entry *entry = malloc (sizeof (fat_entry));
    if (entry == NULL)
	return FREEXL_INSUFFICIENT_MEMORY;
    entry->current_sector = chain->next_sector;
    chain->next_sector += 1;
    entry->next_sector = sector;
    entry->next = NULL;
    if (chain->first == NULL)
	chain->first = entry;
    if (chain->last != NULL)
	chain->last->next = entry;
    chain->last = entry;
    return FREEXL_OK;
}

static int
insert_into_miniFAT_chain (fat_chain * chain, unsigned int sector)
{
/* inserting a sector into the miniFAT chain [linked list] */
    fat_entry *entry = malloc (sizeof (fat_entry));
    if (entry == NULL)
	return FREEXL_INSUFFICIENT_MEMORY;
    entry->current_sector = chain->next_sectorMini;
    chain->next_sectorMini += 1;
    entry->next_sector = sector;
    entry->next = NULL;
    if (chain->firstMini == NULL)
	chain->firstMini = entry;
    if (chain->lastMini != NULL)
	chain->lastMini->next = entry;
    chain->lastMini = entry;
    return FREEXL_OK;
}

static fat_entry *
get_fat_entry (fat_chain * chain, unsigned int i_sect)
{
/* attempting to retrieve a FAT item [sector] */
    if (!chain)
	return NULL;
    if (i_sect < chain->fat_array_count)
	return *(chain->fat_array + i_sect);
    return NULL;
}

static int
build_fat_arrays (fat_chain * chain)
{
/* building FAT sectors array */
    fat_entry *entry;
    int i;

    if (!chain)
	return FREEXL_NULL_ARGUMENT;
    if (chain->fat_array)
	free (chain->fat_array);
    chain->fat_array = NULL;
    chain->fat_array_count = 0;
    if (chain->miniFAT_array)
	free (chain->miniFAT_array);
    chain->miniFAT_array = NULL;
    chain->miniFAT_array_count = 0;

    entry = chain->first;
    while (entry)
      {
	  /* counting how many sectors are into the FAT list */
	  chain->fat_array_count += 1;
	  entry = entry->next;
      }
    if (chain->fat_array_count == 0)
	return FREEXL_CFBF_EMPTY_FAT_CHAIN;

/* allocating the FAT sectors array */
    chain->fat_array = malloc (sizeof (fat_entry *) * chain->fat_array_count);
    if (chain->fat_array == NULL)
	return FREEXL_INSUFFICIENT_MEMORY;

    i = 0;
    entry = chain->first;
    while (entry)
      {
	  /* populating the pointer array */
	  *(chain->fat_array + i) = entry;
	  i++;
	  entry = entry->next;
      }

    entry = chain->firstMini;
    while (entry)
      {
	  /* counting how many sectors are into the miniFAT list */
	  chain->miniFAT_array_count += 1;
	  entry = entry->next;
      }
    if (chain->miniFAT_array_count > 0)
      {
	  /* allocating the miniFAT sectors array */
	  chain->miniFAT_array =
	      malloc (sizeof (fat_entry *) * chain->miniFAT_array_count);
	  if (chain->miniFAT_array == NULL)
	      return FREEXL_INSUFFICIENT_MEMORY;

	  i = 0;
	  entry = chain->firstMini;
	  while (entry)
	    {
		/* populating the pointer array */
		*(chain->miniFAT_array + i) = entry;
		i++;
		entry = entry->next;
	    }
      }
    return FREEXL_OK;
}

static void
select_active_sheet (biff_workbook * workbook, unsigned int current_offset)
{
/* selecting the currently acrive Sheet (if any) */
    biff_sheet *p_sheet;
    p_sheet = workbook->first_sheet;
    while (p_sheet)
      {
	  if (p_sheet->start_offset == current_offset)
	    {
		/* ok, this one is the current Sheet */
		workbook->active_sheet = p_sheet;
		return;
	    }
	  p_sheet = p_sheet->next;
      }
    workbook->active_sheet = NULL;
}

static int
read_fat_sector (FILE * xls, fat_chain * chain, unsigned int sector)
{
/* reading a FAT chain sector */
    long where = (sector + 1) * chain->sector_size;
    unsigned char buf[4096];
    unsigned char *p_buf = buf;
    int i_fat;
    int max_fat;
    if (fseek (xls, where, SEEK_SET) != 0)
	return FREEXL_CFBF_SEEK_ERROR;
    if (chain->sector_size == 4096)
	max_fat = 1024;
    else
	max_fat = 128;

/* reading a FAT sector */
    if (fread (buf, 1, chain->sector_size, xls) != chain->sector_size)
	return FREEXL_CFBF_READ_ERROR;

    for (i_fat = 0; i_fat < max_fat; i_fat++)
      {
	  int ret;
	  biff_word32 fat;
	  memcpy (fat.bytes, p_buf, 4);
	  p_buf += 4;
	  if (chain->swap)
	      swap32 (&fat);
	  ret = insert_into_fat_chain (chain, fat.value);
	  if (ret != FREEXL_OK)
	      return ret;
      }
    return FREEXL_OK;
}

static int
read_difat_sectors (FILE * xls, fat_chain * chain, unsigned int sector,
		    unsigned int num_sectors)
{
/* reading a DIFAT (DoubleIndirect) chain sector */
    unsigned int next_sector = sector;
    unsigned int blocks = 0;
    long where = (sector + 1) * chain->sector_size;
    biff_word32 difat[1024];
    int i_difat;
    int max_difat;
    int end_of_chain = 0;

    if (chain->sector_size == 4096)
	max_difat = 1024;
    else
	max_difat = 128;

    while (1)
      {
	  where = (next_sector + 1) * chain->sector_size;
	  if (fseek (xls, where, SEEK_SET) != 0)
	      return FREEXL_CFBF_SEEK_ERROR;
	  /* reading a DIFAT sector */
	  if (fread (&difat, 1, chain->sector_size, xls) != chain->sector_size)
	      return FREEXL_CFBF_READ_ERROR;
	  blocks++;
	  if (chain->swap)
	    {
		for (i_difat = 0; i_difat < max_difat; i_difat++)
		    swap32 (difat + i_difat);
	    }

	  for (i_difat = 0; i_difat < max_difat; i_difat++)
	    {
		if (difat[i_difat].value == 0xFFFFFFFE)
		  {
		      end_of_chain = 1;	/* end of FAT chain */
		      break;
		  }
		if (i_difat == max_difat - 1)
		    next_sector = difat[i_difat].value;
		else
		  {
		      int ret;
		      if (difat[i_difat].value == 0xFFFFFFFF)
			  continue;	/* unused sector */
		      ret = read_fat_sector (xls, chain, difat[i_difat].value);
		      if (ret != FREEXL_OK)
			  return ret;
		  }
	    }
	  if (blocks == num_sectors)
	      break;
	  if (end_of_chain)
	      break;
      }
    if (blocks != num_sectors)
	return 0;
    return 1;
}

static int
read_miniFAT_sectors (FILE * xls, fat_chain * chain, unsigned int sector,
		      unsigned int num_sectors)
{
/* reading miniFAT chain sectors */
    long where = (sector - 1) * chain->sector_size;
    unsigned char buf[4096];
    int i_fat;
    int max_fat;
    unsigned int block = 0;

    if (chain->sector_size == 4096)
	max_fat = 1024;
    else
	max_fat = 128;

    if (fseek (xls, where, SEEK_SET) != 0)
	return FREEXL_CFBF_SEEK_ERROR;
    while (block < num_sectors)
      {
	  unsigned char *p_buf = buf;
	  block++;
	  /* reading a miniFAT sector */
	  if (fread (&buf, 1, chain->sector_size, xls) != chain->sector_size)
	      return FREEXL_CFBF_READ_ERROR;
	  for (i_fat = 0; i_fat < max_fat; i_fat++)
	    {
		int ret;
		biff_word32 fat;
		memcpy (fat.bytes, p_buf, 4);
		p_buf += 4;
		if (chain->swap)
		    swap32 (&fat);
		ret = insert_into_miniFAT_chain (chain, fat.value);
		if (ret != FREEXL_OK)
		    return ret;
	    }
      }
    return 1;
}

static fat_chain *
read_cfbf_header (biff_workbook * workbook, int swap, int *err_code)
{
/* attempting to read and check FAT header */
    cfbf_header header;
    fat_chain *chain = NULL;
    int i_fat;
    int ret;
    unsigned char *p_fat = header.fat_sector_map;

    if (fread (&header, 1, 512, workbook->xls) != 512)
      {
	  *err_code = FREEXL_CFBF_READ_ERROR;
	  return NULL;
      }
    if (swap)
      {
	  /* BIG endian arch: swap required */
	  swap16 (&(header.minor_version));
	  swap16 (&(header.major_version));
	  swap16 (&(header.byte_order));
	  swap16 (&(header.sector_shift));
	  swap16 (&(header.mini_sector_shift));
	  swap16 (&(header.reserved1));
	  swap32 (&(header.reserved2));
	  swap32 (&(header.directory_sectors));
	  swap32 (&(header.fat_sectors));
	  swap32 (&(header.directory_start));
	  swap32 (&(header.transaction_signature));
	  swap32 (&(header.mini_cutoff));
	  swap32 (&(header.mini_fat_start));
	  swap32 (&(header.mini_fat_sectors));
	  swap32 (&(header.difat_start));
	  swap32 (&(header.difat_sectors));
      }

    if (header.signature[0] == 0xd0 && header.signature[1] == 0xcf
	&& header.signature[2] == 0x11 && header.signature[3] == 0xe0
	&& header.signature[4] == 0xa1 && header.signature[5] == 0xb1
	&& header.signature[6] == 0x1a && header.signature[7] == 0xe1)
	;			/* magic signature OK */
    else
      {
	  *err_code = FREEXL_CFBF_INVALID_SIGNATURE;
	  return NULL;
      }
    if (header.sector_shift.value == 9 || header.sector_shift.value == 12)
	;			/* ok, valid sector size */
    else
      {
	  *err_code = FREEXL_CFBF_INVALID_SECTOR_SIZE;
	  return NULL;
      }

    workbook->cfbf_version = header.major_version.value;
    if (header.sector_shift.value == 9)
	workbook->cfbf_sector_size = 512;
    if (header.sector_shift.value == 12)
	workbook->cfbf_sector_size = 4096;

    chain =
	alloc_fat_chain (swap, header.sector_shift.value,
			 header.directory_start.value);
    if (!chain)
      {
	  *err_code = FREEXL_INSUFFICIENT_MEMORY;
	  return NULL;
      }
    for (i_fat = 0; i_fat < 109; i_fat++)
      {
	  /* reading FAT sectors */
	  biff_word32 fat;
	  memcpy (fat.bytes, p_fat, 4);
	  p_fat += 4;
	  if (swap)
	      swap32 (&fat);
	  if (fat.value == 0xFFFFFFFF)
	      continue;		/* unused sector */
	  ret = read_fat_sector (workbook->xls, chain, fat.value);
	  if (ret != FREEXL_OK)
	    {
		*err_code = ret;
		destroy_fat_chain (chain);
		return NULL;
	    }
      }

    if (header.difat_sectors.value > 0)
      {
	  /* reading DoubleIndirect [DIFAT] sectors */
	  if (!read_difat_sectors
	      (workbook->xls, chain, header.difat_start.value,
	       header.difat_sectors.value))
	    {
		*err_code = FREEXL_CFBF_READ_ERROR;
		destroy_fat_chain (chain);
		return NULL;
	    }
      }

    if (header.mini_fat_sectors.value > 0)
      {
	  /* there is a miniFAT requiring to be supported */
	  chain->miniCutOff = header.mini_cutoff.value;
	  if (!read_miniFAT_sectors
	      (workbook->xls, chain, header.mini_fat_start.value,
	       header.mini_fat_sectors.value))
	    {
		*err_code = FREEXL_CFBF_READ_ERROR;
		destroy_fat_chain (chain);
		return NULL;
	    }
      }

    ret = build_fat_arrays (chain);
    if (ret != FREEXL_OK)
      {
	  *err_code = ret;
	  destroy_fat_chain (chain);
	  return NULL;
      }

    *err_code = FREEXL_OK;
    return chain;
}

static int
read_mini_stream (biff_workbook * workbook, int *errcode)
{
/* loading in memory the whole ministream */
    unsigned int len = 0;
    unsigned int sector = workbook->fat->miniFAT_start;
    unsigned char buf[4096];
    unsigned char *miniStream;
    fat_entry *entry;
    int eof = 0;

    if (workbook->fat->miniStream)
	free (workbook->fat->miniStream);
    workbook->fat->miniStream = NULL;
    miniStream = malloc (workbook->fat->miniFAT_len);
    if (miniStream == NULL)
      {
	  *errcode = FREEXL_INSUFFICIENT_MEMORY;
	  return 0;
      }
    while (len < workbook->fat->miniFAT_len)
      {
	  /* reading one sector */
	  unsigned int size;
	  long where = (sector + 1) * workbook->fat->sector_size;
	  if (fseek (workbook->xls, where, SEEK_SET) != 0)
	    {
		*errcode = FREEXL_CFBF_SEEK_ERROR;
		return 0;
	    }
	  if (fread (buf, 1, workbook->fat->sector_size, workbook->xls) !=
	      workbook->fat->sector_size)
	    {
		*errcode = FREEXL_CFBF_READ_ERROR;
		return 0;
	    }
	  size = workbook->fat->sector_size;
	  if ((len + size) > workbook->fat->miniFAT_len)
	      size = workbook->fat->miniFAT_len - len;
	  memcpy (miniStream + len, buf, size);
	  len += size;
	  entry = get_fat_entry (workbook->fat, sector);
	  if (entry == NULL)
	    {
		*errcode = FREEXL_CFBF_ILLEGAL_FAT_ENTRY;
		return 0;
	    }
	  if (entry->next_sector == 0xfffffffe)
	    {
		/* EOF: end-of-chain marker found */
		eof = 1;
		break;
	    }
	  sector = entry->next_sector;
      }
    if (!eof || len != workbook->fat->miniFAT_len)
      {
	  free (miniStream);
	  *errcode = FREEXL_INVALID_MINI_STREAM;
	  return 0;
      }
    workbook->fat->miniStream = miniStream;
    return 1;
}

static const char *
find_in_SST (biff_workbook * workbook, unsigned int string_index)
{
/* retieving a string from the SST */
    if (!workbook)
	return NULL;
    if (workbook->shared_strings.utf8_strings == NULL)
	return NULL;
    if (string_index < workbook->shared_strings.string_count)
	return *(workbook->shared_strings.utf8_strings + string_index);
    return NULL;
}

static int
parse_SST (biff_workbook * workbook, int swap)
{
/* attempting to parse a Shared String Table */
    unsigned int i_string;
    unsigned char *p_string;
    biff_word32 n_strings;
    unsigned int required;
    unsigned int available;

    if (workbook->shared_strings.string_count == 0
	&& workbook->shared_strings.utf8_strings == NULL)
      {
	  /* main SST record [initializing] */
	  memcpy (n_strings.bytes, workbook->record + 4, 4);
	  if (swap)
	      swap32 (&n_strings);
	  p_string = workbook->record + 8;
	  workbook->shared_strings.string_count = n_strings.value;
	  if (workbook->shared_strings.string_count > 1024 * 1024)
	    {
		/* unexpected huge count ... cowardly giving up ... */
		return FREEXL_INSUFFICIENT_MEMORY;
	    }
	  workbook->shared_strings.utf8_strings =
	      malloc (sizeof (char **) * workbook->shared_strings.string_count);
	  for (i_string = 0; i_string < workbook->shared_strings.string_count;
	       i_string++)
	      *(workbook->shared_strings.utf8_strings + i_string) = NULL;
      }
    else
      {
	  /* SST-CONTINUE */
	  char *utf8_string;
	  unsigned char mask;
	  unsigned int len;
	  int utf16 = 0;
	  int err;
	  unsigned int utf16_len = workbook->shared_strings.current_utf16_len;
	  unsigned int utf16_off = workbook->shared_strings.current_utf16_off;
	  unsigned int utf16_skip = workbook->shared_strings.current_utf16_skip;
	  char *utf16_buf = workbook->shared_strings.current_utf16_buf;
	  p_string = workbook->record;

	  if (workbook->shared_strings.current_utf16_len > 0)
	    {
		/* completing the last suspended string [split between records] */
		mask = *p_string;
		p_string++;
		len = utf16_len - utf16_off;

		if ((mask & 0x01) == 0x01)
		    utf16 = 1;
		if (!utf16)
		  {
		      /* 'stripped' UTF-16: requires padding */
		      unsigned int i;
		      for (i = 0; i < len; i++)
			{
			    *(utf16_buf + (utf16_off * 2) + (i * 2)) =
				*p_string;
			    p_string++;
			    *(utf16_buf + (utf16_off * 2) + ((i * 2) + 1)) =
				0x00;
			}
		  }
		else
		  {
		      /* already encoded as UTF-16 */
		      memcpy (utf16_buf + (utf16_off * 2), p_string, len * 2);
		      p_string += len * 2;
		  }

		/* skipping extra data (if any) */
		p_string += utf16_skip;

		/* converting text to UTF-8 */
		utf8_string =
		    convert_to_utf8 (workbook->utf16_converter, utf16_buf,
				     utf16_len * 2, &err);
		if (err)
		    return FREEXL_INVALID_CHARACTER;
		*(workbook->shared_strings.utf8_strings +
		  workbook->shared_strings.current_index) = utf8_string;
		free (workbook->shared_strings.current_utf16_buf);
		workbook->shared_strings.current_utf16_buf = NULL;
		workbook->shared_strings.current_utf16_len = 0;
		workbook->shared_strings.current_utf16_off = 0;
		workbook->shared_strings.current_utf16_skip = 0;
		workbook->shared_strings.current_index += 1;
	    }
      }

    for (i_string = workbook->shared_strings.current_index;
	 i_string < workbook->shared_strings.string_count; i_string++)
      {
	  /* parsing strings */
	  char *utf8_string;
	  unsigned int len;
	  int utf16 = 0;
	  biff_word16 word16;
	  unsigned int start_offset;
	  unsigned int extra_skip;

	  if ((unsigned int) (p_string - workbook->record) >=
	      workbook->record_size)
	    {
		/* end of record */
		return FREEXL_OK;
	    }

	  memcpy (word16.bytes, p_string, 2);
	  if (swap)
	      swap16 (&word16);
	  len = word16.value;
	  p_string += 2;

	  get_unicode_params (p_string, swap, &start_offset, &utf16,
			      &extra_skip);
	  p_string += start_offset;

	  /* initializing the current UTF-16 variables */
	  workbook->shared_strings.current_utf16_skip = extra_skip;
	  workbook->shared_strings.current_utf16_off = 0;
	  workbook->shared_strings.current_utf16_len = len;
	  workbook->shared_strings.current_utf16_buf =
	      malloc (workbook->shared_strings.current_utf16_len * 2);

	  if (!utf16)
	      required = len;
	  else
	      required = len * 2;
	  available = workbook->record_size - (p_string - workbook->record);
	  if (required > available)
	    {
		/* not enough input bytes: data spanning on next CONTINUE record */
		char *utf16_buf = workbook->shared_strings.current_utf16_buf;
		if (!utf16)
		  {
		      /* 'stripped' UTF-16: requires padding */
		      unsigned int i;
		      for (i = 0; i < available; i++)
			{
			    *(utf16_buf + (i * 2)) = *p_string;
			    p_string++;
			    *(utf16_buf + ((i * 2) + 1)) = 0x00;
			}
		      workbook->shared_strings.current_utf16_off = available;
		  }
		else
		  {
		      /* already encoded as UTF-16 */
		      memcpy (utf16_buf, p_string, available);
		      workbook->shared_strings.current_utf16_off =
			  available / 2;
		  }
		return FREEXL_OK;
	    }

	  if (!parse_unicode_string
	      (workbook->utf16_converter, len, utf16, p_string, &utf8_string))
	      return FREEXL_INVALID_CHARACTER;

	  /* skipping string data */
	  if (!utf16)
	      p_string += len;
	  else
	      p_string += len * 2;
	  /* skipping extra data (if any) */
	  p_string += workbook->shared_strings.current_utf16_skip;

	  *(workbook->shared_strings.utf8_strings + i_string) = utf8_string;
	  free (workbook->shared_strings.current_utf16_buf);
	  workbook->shared_strings.current_utf16_buf = NULL;
	  workbook->shared_strings.current_utf16_len = 0;
	  workbook->shared_strings.current_utf16_off = 0;
	  workbook->shared_strings.current_utf16_skip = 0;
	  workbook->shared_strings.current_index = i_string + 1;
      }

    return FREEXL_OK;
}

static void
check_format (char *utf8_string, int *is_date, int *is_datetime, int *is_time)
{
/* attempting to identify DATE, DATETIME or TIME formats */
    int y = 0;
    int m = 0;
    int d = 0;
    int h = 0;
    int s = 0;
    unsigned int i;
    for (i = 0; i < strlen (utf8_string); i++)
      {
	  switch (utf8_string[i])
	    {
	    case 'Y':
	    case 'y':
		y++;
		break;
	    case 'M':
	    case 'm':
		m++;
		break;
	    case 'D':
	    case 'd':
		d++;
		break;
	    case 'H':
	    case 'h':
		h++;
		break;
	    case 'S':
	    case 's':
		s++;
		break;
	    }
      }
    *is_date = 0;
    *is_datetime = 0;
    *is_time = 0;
    if (y && m && d && h)
      {
	  *is_datetime = 1;
	  return;
      }
    if ((y && m) || (m && d))
      {
	  *is_date = 1;
	  return;
      }
    if ((h && m) || (m && s))
	*is_time = 1;
}

static void
add_format_to_workbook (biff_workbook * workbook, unsigned short format_index,
			int is_date, int is_datetime, int is_time)
{
/* adding a new DATE/DATETIME/TIME format to the Workbook */
    if (workbook->max_format_index < BIFF_MAX_FORMAT)
      {
	  biff_format *format =
	      workbook->format_array + workbook->max_format_index;
	  format->format_index = format_index;
	  format->is_date = is_date;
	  format->is_datetime = is_datetime;
	  format->is_time = is_time;
	  workbook->max_format_index += 1;
      }
}

static void
add_xf_to_workbook (biff_workbook * workbook, unsigned short format_index)
{
/* adding a new XF to the Workbook */
    if (workbook->biff_xf_next_index < BIFF_MAX_XF)
	workbook->biff_xf_array[workbook->biff_xf_next_index] = format_index;
    workbook->biff_xf_next_index += 1;
}

static int
legacy_emergency_dimension (biff_workbook * workbook, int swap,
			    unsigned short type, unsigned short size,
			    unsigned int *rows, unsigned short *columns)
{
/* performing a preliminary pass so to get DIMENSION */
    biff_word16 record_type;
    biff_word16 record_size;
    unsigned char buf[16];
    int first = 1;
    long where;
    long restart_off = ftell (workbook->xls);

    record_type.value = type;
    record_size.value = size;

    while (1)
      {
	  /* looping on BIFF records */
	  if (!first)
	    {
		if (fread (&buf, 1, 4, workbook->xls) != 4)
		    return 0;
		memcpy (record_type.bytes, buf, 2);
		memcpy (record_size.bytes, buf + 2, 2);
	    }
	  else
	      first = 0;
	  if (swap)
	    {
		/* BIG endian arch: swap required */
		swap16 (&record_type);
		swap16 (&record_size);
	    }

	  if (record_type.value == BIFF_EOF)
	    {
		/* EOF marker found: the current stream is terminated */
		break;
	    }

	  if (record_type.value == BIFF_INTEGER_2
	      && workbook->biff_version == FREEXL_BIFF_VER_2)
	    {
		/* INTEGER marker found */
		biff_word16 word16;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *rows)
		    *rows = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *columns)
		    *columns = word16.value;
		continue;
	    }

	  if ((record_type.value == BIFF_NUMBER_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_NUMBER
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* NUMBER marker found */
		biff_word16 word16;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *rows)
		    *rows = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *columns)
		    *columns = word16.value;
		continue;
	    }

	  if ((record_type.value == BIFF_BOOLERR_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_BOOLERR
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* BOOLERR marker found */
		biff_word16 word16;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *rows)
		    *rows = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *columns)
		    *columns = word16.value;
		continue;
	    }

	  if (record_type.value == BIFF_RK
	      && (workbook->biff_version == FREEXL_BIFF_VER_3
		  || workbook->biff_version == FREEXL_BIFF_VER_4))
	    {
		/* RK marker found */
		biff_word16 word16;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *rows)
		    *rows = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *columns)
		    *columns = word16.value;
		continue;
	    }

	  if ((record_type.value == BIFF_LABEL_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_LABEL
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* LABEL marker found */
		biff_word16 word16;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *rows)
		    *rows = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value > *columns)
		    *columns = word16.value;
		continue;
	    }

	  /* skipping to next record */
	  where = record_size.value;
	  if (fseek (workbook->xls, where, SEEK_CUR) != 0)
	      return 0;
      }

/* repositioning the stream offset */
    if (fseek (workbook->xls, restart_off, SEEK_SET) != 0)
	return 0;
    return 1;
}

static int
check_legacy_undeclared_dimension (biff_workbook * workbook, int swap,
				   unsigned short type, unsigned short size)
{
/* checking (and eventually saning) missing DIMENSION */
    if (workbook->active_sheet == NULL)
      {
	  char *utf8_name;
	  unsigned int rows = 0;
	  unsigned short columns = 0;
	  if (!legacy_emergency_dimension
	      (workbook, swap, type, size, &rows, &columns))
	      return 0;
	  /* initializing the worksheet */
	  utf8_name = malloc (10);
	  strcpy (utf8_name, "Worksheet");
	  if (!add_sheet_to_workbook (workbook, 0, 0, 0, utf8_name))
	      return 0;
	  workbook->active_sheet = workbook->first_sheet;
	  if (workbook->active_sheet != NULL)
	    {
		/* setting Sheet dimensions */
		int ret;
		workbook->active_sheet->rows = rows + 1;
		workbook->active_sheet->columns = columns + 1;
		ret = allocate_cells (workbook);
		if (ret != FREEXL_OK)
		    return 0;
	    }
      }
    return 1;
}

static int
read_legacy_biff (biff_workbook * workbook, int swap)
{
/* 
 * attempting to read legacy BIFF (versions 2,3,4)
 * no CFBF: simply a BIFF stream (one only Worksheet)
 */
    long where;
    biff_word16 word16;
    biff_word16 record_type;
    biff_word16 record_size;
    unsigned char buf[16];
    unsigned short format_index = 0;

/* attempting to get the main BOF */
    rewind (workbook->xls);
    if (fread (&buf, 1, 4, workbook->xls) != 4)
	return 0;
    memcpy (record_type.bytes, buf, 2);
    memcpy (record_size.bytes, buf + 2, 2);
    if (swap)
      {
	  /* BIG endian arch: swap required */
	  swap16 (&record_type);
	  swap16 (&record_size);
      }
    switch (record_type.value)
      {
      case BIFF_BOF_2:		/* BIFF2 */
	  workbook->biff_version = FREEXL_BIFF_VER_2;
	  workbook->ok_bof = 1;
	  break;
      case BIFF_BOF_3:		/* BIFF3 */
	  workbook->biff_version = FREEXL_BIFF_VER_3;
	  workbook->ok_bof = 1;
	  break;
      case BIFF_BOF_4:		/* BIFF4 */
	  workbook->biff_version = FREEXL_BIFF_VER_4;
	  workbook->ok_bof = 1;
	  break;
      };
    if (workbook->ok_bof != 1)
	return 0;

    where = record_size.value;
    if (fseek (workbook->xls, where, SEEK_CUR) != 0)
	return 0;

    while (1)
      {
	  /* looping on BIFF records */

	  if (fread (&buf, 1, 4, workbook->xls) != 4)
	      return 0;
	  memcpy (record_type.bytes, buf, 2);
	  memcpy (record_size.bytes, buf + 2, 2);
	  if (swap)
	    {
		/* BIG endian arch: swap required */
		swap16 (&record_type);
		swap16 (&record_size);
	    }

	  if (record_type.value == BIFF_SHEETSOFFSET)
	    {
/* unsupported BIFF4W format */
		return 0;
	    }

	  if (record_type.value == BIFF_EOF)
	    {
		/* EOF marker found: the current stream is terminated */
		return 1;
	    }

	  if (record_type.value == BIFF_CODEPAGE)
	    {
		/* CODEPAGE marker found */
		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;
		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		workbook->biff_code_page = word16.value;
		if (workbook->ok_bof == 1)
		    workbook->biff_book_code_page = word16.value;
		if (!biff_set_utf8_converter (workbook))
		    return 0;
		continue;
	    }

	  if (record_type.value == BIFF_DATEMODE)
	    {
		/* DATEMODE marker found */
		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;
		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		workbook->biff_date_mode = word16.value;
		continue;
	    }

	  if (record_type.value == BIFF_FILEPASS)
	    {
		/* PASSWORD marker found */
		workbook->biff_obfuscated = 1;
		goto skip_to_next;
	    }

	  if ((record_type.value == BIFF_FORMAT_2
	       && (workbook->biff_version == FREEXL_BIFF_VER_2
		   || workbook->biff_version == FREEXL_BIFF_VER_3))
	      || (record_type.value == BIFF_FORMAT
		  && (workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* FORMAT marker found */
		biff_word16 word16;
		char *string;
		char *utf8_string;
		unsigned int len;
		int err;
		unsigned char *p_string;
		int is_date = 0;
		int is_datetime = 0;
		int is_time = 0;
		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		if (workbook->biff_version == FREEXL_BIFF_VER_2
		    || workbook->biff_version == FREEXL_BIFF_VER_3)
		  {
		      len = *workbook->record;
		      p_string = workbook->record + 1;
		      string = malloc (len);
		      memcpy (string, p_string, len);
		      /* converting text to UTF-8 */
		      utf8_string =
			  convert_to_utf8 (workbook->utf8_converter, string,
					   len, &err);
		      free (string);
		      if (err)
			  return 0;
		      check_format (utf8_string, &is_date, &is_datetime,
				    &is_time);
		      free (utf8_string);
		      if (is_date || is_datetime || is_time)
			  add_format_to_workbook (workbook, format_index,
						  is_date, is_datetime,
						  is_time);
		  }
		else
		  {
		      memcpy (word16.bytes, workbook->record + 2, 2);
		      if (swap)
			  swap16 (&word16);
		      len = word16.value;
		      p_string = workbook->record + 4;

		      len = *(workbook->record + 2);
		      p_string = workbook->record + 3;
		      string = malloc (len);
		      memcpy (string, p_string, len);
		      /* converting text to UTF-8 */
		      utf8_string =
			  convert_to_utf8 (workbook->utf8_converter, string,
					   len, &err);
		      free (string);
		      if (err)
			  return 0;
		      check_format (utf8_string, &is_date, &is_datetime,
				    &is_time);
		      free (utf8_string);
		      if (is_date || is_datetime || is_time)
			  add_format_to_workbook (workbook, format_index,
						  is_date, is_datetime,
						  is_time);
		  }
		format_index++;
		continue;
	    }

	  if ((record_type.value == BIFF_XF_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_XF_3
		  && workbook->biff_version == FREEXL_BIFF_VER_3)
	      || (record_type.value == BIFF_XF_4
		  && workbook->biff_version == FREEXL_BIFF_VER_4))
	    {
		/* XF [Extended Format] marker found */
		unsigned char format;
		unsigned short s_format;
		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;
		switch (workbook->biff_version)
		  {
		  case FREEXL_BIFF_VER_2:
		      format = *(workbook->record + 2);
		      format &= 0x3F;
		      s_format = format;
		      break;
		  case FREEXL_BIFF_VER_3:
		  case FREEXL_BIFF_VER_4:
		      format = *(workbook->record + 1);
		      s_format = format;
		      break;
		  };
		add_xf_to_workbook (workbook, s_format);
		continue;
	    }

	  if ((record_type.value == 0x0000
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_DIMENSION
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* DIMENSION marker found */
		biff_word16 word16;
		unsigned int rows;
		unsigned short columns;
		char *utf8_name;
		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		rows = word16.value;
		memcpy (word16.bytes, workbook->record + 6, 2);
		if (swap)
		    swap16 (&word16);
		columns = word16.value;
		utf8_name = malloc (10);
		strcpy (utf8_name, "Worksheet");
		if (!add_sheet_to_workbook (workbook, 0, 0, 0, utf8_name))
		    return 0;
		workbook->active_sheet = workbook->first_sheet;
		if (workbook->active_sheet != NULL)
		  {
		      /* setting Sheet dimensions */
		      int ret;
		      workbook->active_sheet->rows = rows;
		      workbook->active_sheet->columns = columns;
		      ret = allocate_cells (workbook);
		      if (ret != FREEXL_OK)
			  return 0;
		  }
		continue;
	    }

	  if (record_type.value == BIFF_INTEGER_2
	      && workbook->biff_version == FREEXL_BIFF_VER_2)
	    {
		/* INTEGER marker found */
		biff_word16 word16;
		unsigned short row;
		unsigned short col;
		unsigned short xf_index = 0;
		unsigned short num;
		int is_date;
		int is_datetime;
		int is_time;
		int ret;
		unsigned char format;

		if (!check_legacy_undeclared_dimension
		    (workbook, swap, record_type.value, record_size.value))
		    return 0;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		row = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		col = word16.value;

		format = *(workbook->record + 4);
		format &= 0x3F;
		xf_index = format;
		memcpy (word16.bytes, workbook->record + 7, 2);
		if (swap)
		    swap16 (&word16);
		num = word16.value;

		if (!check_xf_datetime
		    (workbook, xf_index, &is_date, &is_datetime, &is_time))
		  {
		      is_date = 0;
		      is_datetime = 0;
		      is_time = 0;
		  }
		if (is_date)
		    ret =
			set_date_int_value (workbook, row, col,
					    workbook->biff_date_mode, num);
		else if (is_datetime)
		    ret =
			set_datetime_int_value (workbook, row, col,
						workbook->biff_date_mode, num);
		else if (is_time)
		    ret = set_time_double_value (workbook, row, col, 0.0);
		else
		    ret = set_int_value (workbook, row, col, num);
		if (ret != FREEXL_OK)
		    return 0;
		continue;
	    }

	  if ((record_type.value == BIFF_NUMBER_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_NUMBER
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* NUMBER marker found */
		biff_word16 word16;
		biff_float word_float;
		unsigned short row;
		unsigned short col;
		unsigned short xf_index = 0;
		double num;
		int is_date;
		int is_datetime;
		int is_time;
		int ret;

		if (!check_legacy_undeclared_dimension
		    (workbook, swap, record_type.value, record_size.value))
		    return 0;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		row = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		col = word16.value;

		if (workbook->biff_version == FREEXL_BIFF_VER_2)
		  {
		      /* BIFF2 */
		      unsigned char format = *(workbook->record + 4);
		      format &= 0x3F;
		      xf_index = format;
		      memcpy (word_float.bytes, workbook->record + 7, 8);
		      if (swap)
			  swap_float (&word_float);
		      num = word_float.value;
		  }
		else
		  {
		      /* any other sebsequent version */
		      memcpy (word16.bytes, workbook->record + 4, 2);
		      if (swap)
			  swap16 (&word16);
		      xf_index = word16.value;
		      memcpy (word_float.bytes, workbook->record + 6, 8);
		      if (swap)
			  swap_float (&word_float);
		      num = word_float.value;
		  }
		if (!check_xf_datetime
		    (workbook, xf_index, &is_date, &is_datetime, &is_time))
		  {
		      is_date = 0;
		      is_datetime = 0;
		      is_time = 0;
		  }
		if (is_date)
		    ret =
			set_date_double_value (workbook, row, col,
					       workbook->biff_date_mode, num);
		else if (is_datetime)
		    ret =
			set_datetime_double_value (workbook, row, col,
						   workbook->biff_date_mode,
						   num);
		else if (is_time)
		    ret = set_time_double_value (workbook, row, col, num);
		else
		    ret = set_double_value (workbook, row, col, num);
		if (ret != FREEXL_OK)
		    return 0;
		continue;
	    }

	  if ((record_type.value == BIFF_BOOLERR_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_BOOLERR
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* BOOLERR marker found */
		biff_word16 word16;
		unsigned short row;
		unsigned short col;
		unsigned char value;
		int ret;

		if (!check_legacy_undeclared_dimension
		    (workbook, swap, record_type.value, record_size.value))
		    return 0;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		row = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		col = word16.value;

		if (workbook->biff_version == FREEXL_BIFF_VER_2)
		  {
		      /* BIFF2 */
		      value = *(workbook->record + 7);
		  }
		else
		  {
		      /* any other sebsequent version */
		      value = *(workbook->record + 6);
		  }
		if (value != 0)
		    value = 1;
		ret = set_int_value (workbook, row, col, value);
		if (ret != FREEXL_OK)
		    return 0;
		continue;
	    }

	  if (record_type.value == BIFF_RK
	      && (workbook->biff_version == FREEXL_BIFF_VER_3
		  || workbook->biff_version == FREEXL_BIFF_VER_4))
	    {
		/* RK marker found */
		biff_word16 word16;
		biff_word32 word32;
		unsigned short row;
		unsigned short col;
		unsigned short xf_index;
		int int_value;
		double dbl_value;
		int is_date;
		int is_datetime;
		int is_time;
		int ret;

		if (!check_legacy_undeclared_dimension
		    (workbook, swap, record_type.value, record_size.value))
		    return 0;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		row = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		col = word16.value;
		memcpy (word16.bytes, workbook->record + 4, 2);
		if (swap)
		    swap16 (&word16);
		xf_index = word16.value;
		memcpy (word32.bytes, workbook->record + 6, 4);
		if (decode_rk_integer (word32.bytes, &int_value, swap))
		  {
		      if (!check_xf_datetime
			  (workbook, xf_index, &is_date, &is_datetime,
			   &is_time))
			{
			    is_date = 0;
			    is_datetime = 0;
			    is_time = 0;
			}
		      if (is_date)
			  ret =
			      set_date_int_value (workbook, row, col,
						  workbook->biff_date_mode,
						  int_value);
		      else if (is_datetime)
			  ret =
			      set_datetime_int_value (workbook, row, col,
						      workbook->biff_date_mode,
						      int_value);
		      else if (is_time)
			  ret = set_time_double_value (workbook, row, col, 0.0);
		      else
			  ret = set_int_value (workbook, row, col, int_value);
		      if (ret != FREEXL_OK)
			  return 0;
		  }
		else if (decode_rk_float (word32.bytes, &dbl_value, swap))
		  {
		      if (!check_xf_datetime
			  (workbook, xf_index, &is_date, &is_datetime,
			   &is_time))
			{
			    is_date = 0;
			    is_datetime = 0;
			    is_time = 0;
			}
		      if (is_date)
			  ret =
			      set_date_double_value (workbook, row, col,
						     workbook->biff_date_mode,
						     dbl_value);
		      else if (is_datetime)
			  ret =
			      set_datetime_double_value (workbook, row, col,
							 workbook->biff_date_mode,
							 dbl_value);
		      else if (is_time)
			  ret =
			      set_time_double_value (workbook, row, col,
						     dbl_value);
		      else
			  ret =
			      set_double_value (workbook, row, col, dbl_value);
		      if (ret != FREEXL_OK)
			  return 0;
		  }
		else
		    return 0;
		continue;
	    }

	  if ((record_type.value == BIFF_LABEL_2
	       && workbook->biff_version == FREEXL_BIFF_VER_2)
	      || (record_type.value == BIFF_LABEL
		  && (workbook->biff_version == FREEXL_BIFF_VER_3
		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
	    {
		/* LABEL marker found */
		biff_word16 word16;
		char *string;
		char *utf8_string;
		unsigned int len;
		int err;
		unsigned short row;
		unsigned short col;
		unsigned char *p_string;
		int ret;

		if (!check_legacy_undeclared_dimension
		    (workbook, swap, record_type.value, record_size.value))
		    return 0;

		if (fread
		    (workbook->record, 1, record_size.value,
		     workbook->xls) != record_size.value)
		    return 0;

		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		row = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		col = word16.value;

		if (workbook->biff_version == FREEXL_BIFF_VER_2)
		  {
		      len = *(workbook->record + 7);
		      p_string = workbook->record + 8;
		  }
		else
		  {
		      memcpy (word16.bytes, workbook->record + 6, 2);
		      if (swap)
			  swap16 (&word16);
		      len = word16.value;
		      p_string = workbook->record + 8;
		  }

		string = malloc (len);
		memcpy (string, p_string, len);

		/* converting text to UTF-8 */
		utf8_string =
		    convert_to_utf8 (workbook->utf8_converter, string, len,
				     &err);
		free (string);
		if (err)
		    return 0;
		ret = set_text_value (workbook, row, col, utf8_string);
		if (ret != FREEXL_OK)
		    return 0;
		continue;
	    }

	  /* skipping to next record */
	skip_to_next:
	  where = record_size.value;
	  if (fseek (workbook->xls, where, SEEK_CUR) != 0)
	      return 0;
      }

/* saving the current record */
    workbook->record_type = record_type.value;
    workbook->record_size = record_size.value;

    return 0;
}

static int
check_already_done (biff_workbook * workbook)
{
/* checking if the currently active sheet has been already loaded */
    if (workbook->active_sheet != NULL)
      {
	  if (workbook->active_sheet->already_done)
	    {
		/* already loaded */
		return 1;
	    }
      }
    return 0;
}

static int
check_undeclared_dimension (biff_workbook * workbook, unsigned int row,
			    unsigned short col)
{
/* checking if DIMENSION isn't yet set */
    if (workbook->active_sheet != NULL)
      {
	  if (workbook->active_sheet->valid_dimension == 0)
	    {
		/* not yet set */
		if (row > workbook->active_sheet->rows)
		    workbook->active_sheet->rows = row;
		if (col > workbook->active_sheet->columns)
		    workbook->active_sheet->columns = col;
		return 1;
	    }
      }
    return 0;
}

static int
parse_biff_record (biff_workbook * workbook, int swap)
{
/* 
 * attempting to parse a BIFF record 
 * please note well: BIFF5 and BIFF8 versions only
 *
 * oldest BIFF2, BIFF3 and BIFF4 are processed separatedly
 * by read_legacy_biff() function
 *
 */
    biff_word16 word16;
    unsigned int base_offset = workbook->current_offset;

    workbook->current_offset += workbook->record_size + 4;

    if (workbook->record_type == BIFF_CONTINUE)
      {
	  /* CONTINUE marker found: restoring the previous record type */
	  if (workbook->prev_record_type == BIFF_SST)
	    {
		/* continuing: SST [Shared String Table] */
		if (workbook->second_pass)
		    return FREEXL_OK;
		return parse_SST (workbook, swap);
	    }
	  return FREEXL_OK;
      }

    workbook->prev_record_type = workbook->record_type;
    if (workbook->ok_bof == -1)
      {
	  /* 
	   * the first record is expected to be of BOF type 
	   * and contains Version related information 
	   */
	  switch (workbook->record_type)
	    {
	    case BIFF_BOF:	/* BIFF5 or BIFF8 */
		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		if (word16.value == 0x0500)
		    workbook->biff_version = FREEXL_BIFF_VER_5;
		else if (word16.value == 0x0600)
		    workbook->biff_version = FREEXL_BIFF_VER_8;
		else
		  {
		      /* unknown, probably wrong or corrupted */
		      workbook->ok_bof = 0;
		      return FREEXL_BIFF_INVALID_BOF;
		  }
		workbook->ok_bof = 1;
		break;
	    default:
		workbook->ok_bof = 0;
		return FREEXL_BIFF_INVALID_BOF;
	    };
	  if (workbook->biff_version == FREEXL_BIFF_VER_8)
	      workbook->biff_max_record_size = 8224;
	  else
	      workbook->biff_max_record_size = 2080;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  workbook->biff_content_type = word16.value;
	  workbook->biff_code_page = 0;
	  return FREEXL_OK;
      }
    if (workbook->ok_bof == 0)
      {
	  /* we are expecting to find some BOF record here (not the main one) */
	  switch (workbook->record_type)
	    {
	    case BIFF_BOF:	/* BIFF5 or BIFF8 */
		workbook->ok_bof = 1;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		workbook->biff_content_type = word16.value;
		workbook->biff_code_page = 0;
		break;
	    default:
		workbook->ok_bof = 0;
		return FREEXL_BIFF_INVALID_BOF;
	    };
	  select_active_sheet (workbook, base_offset);
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_EOF)
      {
	  /* EOF marker found: the current stream is terminated */
	  workbook->ok_bof = 0;
	  workbook->biff_content_type = 0;
	  workbook->biff_code_page = 0;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_SST)
      {
	  /* SST [Shared String Table] marker found */
	  if (workbook->second_pass)
	      return FREEXL_OK;
	  return parse_SST (workbook, swap);
      }

    if (workbook->record_type == BIFF_CODEPAGE)
      {
	  /* CODEPAGE marker found */
	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  workbook->biff_code_page = word16.value;
	  if (workbook->ok_bof == 1)
	      workbook->biff_book_code_page = word16.value;
	  if (!biff_set_utf8_converter (workbook))
	      return FREEXL_UNSUPPORTED_CHARSET;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_DATEMODE)
      {
	  /* DATEMODE marker found */
	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  workbook->biff_date_mode = word16.value;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_FILEPASS)
      {
	  /* PASSWORD marker found */
	  workbook->biff_obfuscated = 1;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_SHEET)
      {
	  /* SHEET marker found */
	  char *utf8_name;
	  char name[4096];
	  int err;
	  int len;
	  biff_word32 offset;

	  if (workbook->second_pass)
	    {
		if (workbook->active_sheet == NULL)
		    workbook->active_sheet = workbook->first_sheet;
		else
		    workbook->active_sheet = workbook->active_sheet->next;
		return FREEXL_OK;
	    }

	  memcpy (offset.bytes, workbook->record, 4);
	  if (swap)
	      swap32 (&offset);
	  len = workbook->record[6];
	  if (workbook->biff_version == FREEXL_BIFF_VER_5)
	    {
		/* BIFF5: codepage text */
		memcpy (name, workbook->record + 7, len);
		utf8_name =
		    convert_to_utf8 (workbook->utf8_converter, name, len, &err);
		if (err)
		    return FREEXL_INVALID_CHARACTER;
	    }
	  else
	    {
		/* BIFF8: Unicode text */
		if (workbook->record[7] == 0x00)
		  {
		      /* 'stripped' UTF-16: requires padding */
		      int i;
		      for (i = 0; i < len; i++)
			{
			    name[i * 2] = workbook->record[8 + i];
			    name[(i * 2) + 1] = 0x00;
			}
		      len *= 2;
		  }
		else
		  {
		      /* already encoded as UTF-16 */
		      len *= 2;
		      memcpy (name, workbook->record + 8, len);
		  }
		utf8_name =
		    convert_to_utf8 (workbook->utf16_converter, name, len,
				     &err);
		if (err)
		    return FREEXL_INVALID_CHARACTER;
	    }
	  if (!add_sheet_to_workbook
	      (workbook, offset.value, workbook->record[4], workbook->record[5],
	       utf8_name))
	      return FREEXL_INSUFFICIENT_MEMORY;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_DIMENSION
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* DIMENSION marker found */
	  biff_word16 word16;
	  biff_word32 word32;
	  unsigned int rows;
	  unsigned short columns;

	  if (workbook->second_pass)
	      return FREEXL_OK;
	  if (workbook->biff_version == FREEXL_BIFF_VER_8)
	    {
		/* BIFF8: 32-bit row index */
		memcpy (word32.bytes, workbook->record + 4, 4);
		if (swap)
		    swap32 (&word32);
		rows = word32.value;
		memcpy (word16.bytes, workbook->record + 10, 2);
		if (swap)
		    swap16 (&word16);
		columns = word16.value;
	    }
	  else
	    {
		/* any previous version: 16-bit row index */
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		rows = word16.value;
		memcpy (word16.bytes, workbook->record + 6, 2);
		if (swap)
		    swap16 (&word16);
		columns = word16.value;
	    }
	  if (workbook->active_sheet != NULL)
	    {
		/* setting Sheet dimensions */
		int ret;
		workbook->active_sheet->rows = rows;
		workbook->active_sheet->columns = columns;
		ret = allocate_cells (workbook);
		if (ret != FREEXL_OK)
		    return ret;
		workbook->active_sheet->valid_dimension = 1;
	    }
	  return FREEXL_OK;
      }

    if (workbook->magic1 == FREEXL_MAGIC_INFO)
      {
	  /* when open in INFO mode we can safely ignore any other */
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_FORMAT
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* FORMAT marker found */
	  biff_word16 word16;
	  char *string;
	  char *utf8_string;
	  unsigned int len;
	  int err;
	  unsigned short format_index;
	  unsigned char *p_string;
	  int is_date = 0;
	  int is_datetime = 0;
	  int is_time = 0;

	  if (workbook->second_pass)
	      return FREEXL_OK;
	  if (workbook->biff_version == FREEXL_BIFF_VER_5)
	    {
		/* CODEPAGE string */
		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		format_index = word16.value;
		len = *(workbook->record + 3);
		p_string = workbook->record + 3;
		string = malloc (len);
		memcpy (string, p_string, len);

		/* converting text to UTF-8 */
		utf8_string =
		    convert_to_utf8 (workbook->utf8_converter, string, len,
				     &err);
		free (string);
		if (err)
		    return FREEXL_INVALID_CHARACTER;
		check_format (utf8_string, &is_date, &is_datetime, &is_time);
		free (utf8_string);
		if (is_date || is_datetime || is_time)
		    add_format_to_workbook (workbook, format_index, is_date,
					    is_datetime, is_time);
	    }
	  if (workbook->biff_version == FREEXL_BIFF_VER_8)
	    {
		/* please note: this always is UTF-16 */
		int utf16 = 0;
		unsigned int start_offset;
		unsigned int extra_skip;
		memcpy (word16.bytes, workbook->record, 2);
		if (swap)
		    swap16 (&word16);
		format_index = word16.value;
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		len = word16.value;
		p_string = workbook->record + 4;
		get_unicode_params (p_string, swap, &start_offset, &utf16,
				    &extra_skip);
		p_string += start_offset;
		if (!parse_unicode_string
		    (workbook->utf16_converter, len, utf16, p_string,
		     &utf8_string))
		    return FREEXL_INVALID_CHARACTER;
		check_format (utf8_string, &is_date, &is_datetime, &is_time);
		free (utf8_string);
		if (is_date || is_datetime || is_time)
		    add_format_to_workbook (workbook, format_index, is_date,
					    is_datetime, is_time);
	    }
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_XF
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* XF [Extended Format] marker found */
	  unsigned short s_format;
	  biff_word16 word16;
	  if (workbook->second_pass)
	      return FREEXL_OK;
	  switch (workbook->biff_version)
	    {
	    case FREEXL_BIFF_VER_5:
	    case FREEXL_BIFF_VER_8:
		memcpy (word16.bytes, workbook->record + 2, 2);
		if (swap)
		    swap16 (&word16);
		s_format = word16.value;
		break;
	    };
	  add_xf_to_workbook (workbook, s_format);
	  return FREEXL_OK;
      }

    if ((workbook->record_type == BIFF_NUMBER
	 && (workbook->biff_version == FREEXL_BIFF_VER_5
	     || workbook->biff_version == FREEXL_BIFF_VER_8)))
      {
	  /* NUMBER marker found */
	  biff_word16 word16;
	  biff_float word_float;
	  unsigned short row;
	  unsigned short col;
	  unsigned short xf_index = 0;
	  double num;
	  int is_date;
	  int is_datetime;
	  int is_time;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record + 4, 2);
	  if (swap)
	      swap16 (&word16);
	  xf_index = word16.value;
	  memcpy (word_float.bytes, workbook->record + 6, 8);
	  if (swap)
	      swap_float (&word_float);
	  num = word_float.value;

	  if (!check_xf_datetime_58
	      (workbook, xf_index, &is_date, &is_datetime, &is_time))
	    {
		is_date = 0;
		is_datetime = 0;
		is_time = 0;
	    }
	  if (is_date)
	      ret =
		  set_date_double_value (workbook, row, col,
					 workbook->biff_date_mode, num);
	  else if (is_datetime)
	      ret =
		  set_datetime_double_value (workbook, row, col,
					     workbook->biff_date_mode, num);
	  else if (is_time)
	      ret = set_time_double_value (workbook, row, col, num);
	  else
	      ret = set_double_value (workbook, row, col, num);
	  if (ret != FREEXL_OK)
	      return ret;
	  return FREEXL_OK;
      }

    if ((workbook->record_type == BIFF_BOOLERR
	 && (workbook->biff_version == FREEXL_BIFF_VER_5
	     || workbook->biff_version == FREEXL_BIFF_VER_8)))
      {
	  /* BOOLERR marker found */
	  biff_word16 word16;
	  unsigned short row;
	  unsigned short col;
	  unsigned char value;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  value = *(workbook->record + 6);
	  if (value != 0)
	      value = 1;
	  ret = set_int_value (workbook, row, col, value);
	  if (ret != FREEXL_OK)
	      return ret;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_RK
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* RK marker found */
	  biff_word16 word16;
	  biff_word32 word32;
	  unsigned short row;
	  unsigned short col;
	  unsigned short xf_index;
	  int int_value;
	  double dbl_value;
	  int is_date;
	  int is_datetime;
	  int is_time;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record + 4, 2);
	  if (swap)
	      swap16 (&word16);
	  xf_index = word16.value;
	  memcpy (word32.bytes, workbook->record + 6, 4);
	  if (decode_rk_integer (word32.bytes, &int_value, swap))
	    {
		if (!check_xf_datetime_58
		    (workbook, xf_index, &is_date, &is_datetime, &is_time))
		  {
		      is_date = 0;
		      is_datetime = 0;
		      is_time = 0;
		  }
		if (is_date)
		    ret =
			set_date_int_value (workbook, row, col,
					    workbook->biff_date_mode,
					    int_value);
		else if (is_datetime)
		    ret =
			set_datetime_int_value (workbook, row, col,
						workbook->biff_date_mode,
						int_value);
		else if (is_time)
		    ret = set_time_double_value (workbook, row, col, 0.0);
		else
		    ret = set_int_value (workbook, row, col, int_value);
		if (ret != FREEXL_OK)
		    return ret;
	    }
	  else if (decode_rk_float (word32.bytes, &dbl_value, swap))
	    {
		if (!check_xf_datetime_58
		    (workbook, xf_index, &is_date, &is_datetime, &is_time))
		  {
		      is_date = 0;
		      is_datetime = 0;
		      is_time = 0;
		  }
		if (is_date)
		    ret =
			set_date_double_value (workbook, row, col,
					       workbook->biff_date_mode,
					       dbl_value);
		else if (is_datetime)
		    ret =
			set_datetime_double_value (workbook, row, col,
						   workbook->biff_date_mode,
						   dbl_value);
		else if (is_time)
		    ret = set_time_double_value (workbook, row, col, dbl_value);
		else
		    ret = set_double_value (workbook, row, col, dbl_value);
		if (ret != FREEXL_OK)
		    return ret;
	    }
	  else
	      return FREEXL_ILLEGAL_RK_VALUE;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_MULRK
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* MULRK marker found */
	  biff_word16 word16;
	  biff_word32 word32;
	  unsigned short row;
	  unsigned short col;
	  unsigned short xf_index;
	  unsigned int off = 4;
	  int int_value;
	  double dbl_value;
	  int is_date;
	  int is_datetime;
	  int is_time;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  while ((off + 6) < workbook->record_size)
	    {
		/* fetching one cell value */
		memcpy (word16.bytes, workbook->record + off, 2);
		if (swap)
		    swap16 (&word16);
		xf_index = word16.value;
		memcpy (word32.bytes, workbook->record + off + 2, 4);
		if (decode_rk_integer (word32.bytes, &int_value, swap))
		  {
		      if (!check_xf_datetime_58
			  (workbook, xf_index, &is_date, &is_datetime,
			   &is_time))
			{
			    is_date = 0;
			    is_datetime = 0;
			    is_time = 0;
			}
		      if (is_date)
			  ret =
			      set_date_int_value (workbook, row, col,
						  workbook->biff_date_mode,
						  int_value);
		      else if (is_datetime)
			  ret =
			      set_datetime_int_value (workbook, row, col,
						      workbook->biff_date_mode,
						      int_value);
		      else if (is_time)
			  ret = set_time_double_value (workbook, row, col, 0.0);
		      else
			  ret = set_int_value (workbook, row, col, int_value);
		      if (ret != FREEXL_OK)
			  return ret;
		  }
		else if (decode_rk_float (word32.bytes, &dbl_value, swap))
		  {
		      if (!check_xf_datetime_58
			  (workbook, xf_index, &is_date, &is_datetime,
			   &is_time))
			{
			    is_date = 0;
			    is_datetime = 0;
			    is_time = 0;
			}
		      if (is_date)
			  ret =
			      set_date_double_value (workbook, row, col,
						     workbook->biff_date_mode,
						     dbl_value);
		      else if (is_datetime)
			  ret =
			      set_datetime_double_value (workbook, row, col,
							 workbook->biff_date_mode,
							 dbl_value);
		      else if (is_time)
			  ret =
			      set_time_double_value (workbook, row, col,
						     dbl_value);
		      else
			  ret =
			      set_double_value (workbook, row, col, dbl_value);
		      if (ret != FREEXL_OK)
			  return ret;
		  }
		else
		    return FREEXL_ILLEGAL_MULRK_VALUE;
		off += 6;
		col++;
	    }
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_LABEL
	&& (workbook->biff_version == FREEXL_BIFF_VER_5
	    || workbook->biff_version == FREEXL_BIFF_VER_8))
      {
	  /* LABEL marker found */
	  biff_word16 word16;
	  char *string;
	  char *utf8_string;
	  unsigned int len;
	  int err;
	  unsigned short row;
	  unsigned short col;
	  unsigned char *p_string;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record + 6, 2);
	  if (swap)
	      swap16 (&word16);
	  len = word16.value;
	  p_string = workbook->record + 8;

	  if (workbook->biff_version == FREEXL_BIFF_VER_5)
	    {
		/* CODEPAGE string */
		string = malloc (len);
		memcpy (string, p_string, len);

		/* converting text to UTF-8 */
		utf8_string =
		    convert_to_utf8 (workbook->utf8_converter, string, len,
				     &err);
		free (string);
		if (err)
		    return FREEXL_INVALID_CHARACTER;
	    }
	  else
	    {
		/* please note: this always is UTF-16 [BIFF8] */
		int utf16 = 0;
		unsigned int start_offset;
		unsigned int extra_skip;
		get_unicode_params (p_string, swap, &start_offset, &utf16,
				    &extra_skip);
		p_string += start_offset;
		if (!parse_unicode_string
		    (workbook->utf16_converter, len, utf16, p_string,
		     &utf8_string))
		    return FREEXL_INVALID_CHARACTER;
	    }
	  ret = set_text_value (workbook, row, col, utf8_string);
	  if (ret != FREEXL_OK)
	      return ret;
	  return FREEXL_OK;
      }

    if (workbook->record_type == BIFF_LABEL_SST
	&& workbook->biff_version == FREEXL_BIFF_VER_8)
      {
	  /* LABELSST marker found */
	  biff_word16 word16;
	  biff_word32 word32;
	  unsigned short row;
	  unsigned short col;
	  unsigned int string_index;
	  const char *utf8_string;
	  int ret;

	  if (check_already_done (workbook))
	      return FREEXL_OK;

	  memcpy (word16.bytes, workbook->record, 2);
	  if (swap)
	      swap16 (&word16);
	  row = word16.value;
	  memcpy (word16.bytes, workbook->record + 2, 2);
	  if (swap)
	      swap16 (&word16);
	  col = word16.value;

	  if (check_undeclared_dimension (workbook, row, col))
	      return FREEXL_OK;

	  memcpy (word32.bytes, workbook->record + 6, 4);
	  if (swap)
	      swap32 (&word32);
	  string_index = word32.value;
	  utf8_string = find_in_SST (workbook, string_index);
	  if (!utf8_string)
	      return FREEXL_BIFF_ILLEGAL_SST_INDEX;
	  ret = set_sst_value (workbook, row, col, utf8_string);
	  if (ret != FREEXL_OK)
	      return ret;
	  return FREEXL_OK;
      }

    return FREEXL_OK;
}

static int
read_cfbf_sector (biff_workbook * workbook, unsigned char *buf)
{
/* attempting to read a physical sector from the CFBF stream */
    long where = (workbook->current_sector + 1) * workbook->fat->sector_size;
    if (fseek (workbook->xls, where, SEEK_SET) != 0)
	return FREEXL_CFBF_SEEK_ERROR;
    if (fread (buf, 1, workbook->fat->sector_size, workbook->xls) !=
	workbook->fat->sector_size)
	return FREEXL_CFBF_READ_ERROR;
    return FREEXL_OK;
}

static int
read_cfbf_next_sector (biff_workbook * workbook, int *errcode)
{
/* attempting to read the next sector from the CFBF stream */
    int ret;
    fat_entry *entry = get_fat_entry (workbook->fat, workbook->current_sector);
    if (entry == NULL)
      {
	  *errcode = FREEXL_CFBF_ILLEGAL_FAT_ENTRY;
	  return 0;
      }
    if (entry->next_sector == 0xfffffffe)
      {
	  /* EOF: end-of-chain marker found */
	  *errcode = FREEXL_OK;
	  return -1;
      }
    workbook->current_sector = entry->next_sector;
    if (workbook->sector_end > workbook->fat->sector_size)
      {
	  /* shifting back the current sector buffer */
	  memcpy (workbook->sector_buf,
		  workbook->sector_buf + workbook->fat->sector_size,
		  workbook->fat->sector_size);
	  workbook->p_in -= workbook->fat->sector_size;
      }
/* reading into the second half of the sector buffer */
    ret =
	read_cfbf_sector (workbook,
			  workbook->sector_buf + workbook->fat->sector_size);
    if (ret != FREEXL_OK)
      {
	  *errcode = ret;
	  return 0;
      }
    workbook->bytes_read += workbook->fat->sector_size;
    if (workbook->bytes_read > workbook->size)
      {
	  /* incomplete last sector */
	  workbook->sector_end =
	      (workbook->fat->sector_size * 2) - (workbook->bytes_read -
						  workbook->size);
      }
    else
	workbook->sector_end = (workbook->fat->sector_size * 2);
    *errcode = FREEXL_OK;
    return 1;
}

static int
read_biff_next_record (biff_workbook * workbook, int swap, int *errcode)
{
/* 
 * attempting to read the next BIFF record
 * from the Workbook stream
 */
    biff_word16 record_type;
    biff_word16 record_size;
    int ret;

    if (workbook->sector_ready == 0)
      {
	  /* first access: loading the first stream sector */
	  ret = read_cfbf_sector (workbook, workbook->sector_buf);
	  if (ret != FREEXL_OK)
	    {
		*errcode = ret;
		return 0;
	    }
	  workbook->current_sector = workbook->start_sector;
	  workbook->bytes_read += workbook->fat->sector_size;
	  if (workbook->bytes_read > workbook->size)
	    {
		/* incomplete last sector */
		workbook->sector_end =
		    workbook->fat->sector_size - (workbook->bytes_read -
						  workbook->size);
	    }
	  else
	      workbook->sector_end = workbook->fat->sector_size;
	  workbook->p_in = workbook->sector_buf;
	  workbook->sector_ready = 1;
      }

/* 
 * four bytes are now expected:
 * USHORT record-type
 * USHORT record-size
 */
    if ((workbook->p_in - workbook->sector_buf) + 4 > workbook->sector_end)
      {
	  /* reading next sector */
	  ret = read_cfbf_next_sector (workbook, errcode);
	  if (ret == -1)
	      return -1;	/* EOF found */
	  if (ret == 0)
	      return 0;
      }
/* fetching record-type and record-size */
    memcpy (record_type.bytes, workbook->p_in, 2);
    workbook->p_in += 2;
    memcpy (record_size.bytes, workbook->p_in, 2);
    workbook->p_in += 2;
    if (swap)
      {
	  /* BIG endian arch: swap required */
	  swap16 (&record_type);
	  swap16 (&record_size);
      }
/* 
/ Sandro 2011-09-04
/ apparently a record-type 0x0000 and a record-size 0
/ seems to be an alternative way to mark EOF
*/
    if (record_type.value == 0x0000 && record_size.value == 0)
	return -1;

/* saving the current record */
    workbook->record_type = record_type.value;
    workbook->record_size = record_size.value;

    if (((workbook->p_in + workbook->record_size) - workbook->sector_buf) >
	workbook->sector_end)
      {
	  /* the current record spans on the following sector(s) */
	  unsigned int already_done;
	  unsigned int chunk =
	      workbook->sector_end - (workbook->p_in - workbook->sector_buf);
	  if (workbook->sector_end <= (workbook->p_in - workbook->sector_buf))
	      return -1;
	  memcpy (workbook->record, workbook->p_in, chunk);
	  workbook->p_in += chunk;
	  already_done = chunk;

	  while (already_done < workbook->record_size)
	    {
		/* reading a further sector */
		ret = read_cfbf_next_sector (workbook, errcode);
		if (ret == -1)
		    return -1;	/* EOF found */
		if (ret == 0)
		    return 0;
		chunk = workbook->record_size - already_done;
		if (chunk <= workbook->fat->sector_size)
		  {
		      /* ok, finished: whole record reassembled */
		      memcpy (workbook->record + already_done, workbook->p_in,
			      chunk);
		      workbook->p_in += chunk;
		      goto record_done;
		  }
		/* record still spanning on the following sector */
		memcpy (workbook->record + already_done, workbook->p_in,
			workbook->fat->sector_size);
		workbook->p_in += workbook->fat->sector_size;
		already_done += workbook->fat->sector_size;
	    }
      }
    else
      {
	  /* the record is fully contained into the current sector */
	  memcpy (workbook->record, workbook->p_in, workbook->record_size);
	  workbook->p_in += record_size.value;
      }
  record_done:
    ret = parse_biff_record (workbook, swap);
    if (ret != FREEXL_OK)
	return 0;
    *errcode = FREEXL_OK;
    return 1;
}

static int
read_mini_biff_next_record (biff_workbook * workbook, int swap, int *errcode)
{
/* 
 * attempting to read the next BIFF record
 * from the Workbook MINI-stream
 */
    biff_word16 record_type;
    biff_word16 record_size;
    int ret;

/* 
 * four bytes are now expected:
 * USHORT record-type
 * USHORT record-size
 */
    if ((workbook->p_in - workbook->fat->miniStream) + 4 > (int) workbook->size)
	return -1;		/* EOF found */

/* fetching record-type and record-size */
    memcpy (record_type.bytes, workbook->p_in, 2);
    workbook->p_in += 2;
    memcpy (record_size.bytes, workbook->p_in, 2);
    workbook->p_in += 2;
    if (swap)
      {
	  /* BIG endian arch: swap required */
	  swap16 (&record_type);
	  swap16 (&record_size);
      }
/* saving the current record */
    workbook->record_type = record_type.value;
    workbook->record_size = record_size.value;

    if ((workbook->p_in - workbook->fat->miniStream) + workbook->record_size >
	(int) workbook->size)
	return 0;		/* unexpected EOF */

    memcpy (workbook->record, workbook->p_in, workbook->record_size);
    workbook->p_in += record_size.value;

    ret = parse_biff_record (workbook, swap);
    if (ret != FREEXL_OK)
	return 0;
    *errcode = FREEXL_OK;
    return 1;
}

static int
parse_dir_entry (void *block, int swap, iconv_t utf16_utf8_converter,
		 unsigned int *workbook, unsigned int *workbook_len,
		 unsigned int *miniFAT_start, unsigned int *miniFAT_len,
		 int *rootEntry)
{
/* parsing a Directory entry */
    char *name;
    int err;
    cfbf_dir_entry *entry = (cfbf_dir_entry *) block;
    if (swap)
      {
	  /* BIG endian arch: swap required */
	  swap16 (&(entry->name_size));
	  swap32 (&(entry->previous));
	  swap32 (&(entry->next));
	  swap32 (&(entry->child));
	  swap32 (&(entry->timestamp_1));
	  swap32 (&(entry->timestamp_2));
	  swap32 (&(entry->timestamp_3));
	  swap32 (&(entry->timestamp_4));
	  swap32 (&(entry->start_sector));
	  swap32 (&(entry->extra_size));
	  swap32 (&(entry->size));
      }

    name =
	convert_to_utf8 (utf16_utf8_converter, entry->name,
			 entry->name_size.value, &err);
    if (err)
	return FREEXL_INVALID_CHARACTER;

    if (strcmp (name, "Root Entry") == 0)
      {
	  *miniFAT_start = entry->start_sector.value;
	  *miniFAT_len = entry->size.value;
	  *rootEntry = 1;
      }
    else
	*rootEntry = 0;

    if (strcmp (name, "Workbook") == 0 || strcmp (name, "Book") == 0)
      {
	  *workbook = entry->start_sector.value;
	  *workbook_len = entry->size.value;
      }
    free (name);
    return FREEXL_OK;
}

static int
get_workbook_stream (biff_workbook * workbook)
{
/* attempting to locate the Workbook into the main FAT directory */
    long where;
    unsigned int sector = workbook->fat->directory_start;
    unsigned char dir_block[4096];
    int max_entries;
    int i_entry;
    unsigned char *p_entry;
    unsigned int workbook_start;
    unsigned int workbook_len;
    unsigned int miniFAT_start;
    unsigned int miniFAT_len;
    int rootEntry;
    int ret;

    if (workbook->fat->sector_size == 4096)
	max_entries = 32;
    else
	max_entries = 4;

    where = (sector + 1) * workbook->fat->sector_size;
    if (fseek (workbook->xls, where, SEEK_SET) != 0)
	return FREEXL_CFBF_SEEK_ERROR;
/* reading a FAT Directory block [sector] */
    if (fread (dir_block, 1, workbook->fat->sector_size, workbook->xls) !=
	workbook->fat->sector_size)
	return FREEXL_CFBF_READ_ERROR;
    workbook_start = 0xFFFFFFFF;
    for (i_entry = 0; i_entry < max_entries; i_entry++)
      {
	  /* scanning dir entries until Workbook found */
	  p_entry = dir_block + (i_entry * 128);
	  ret =
	      parse_dir_entry (p_entry, workbook->fat->swap,
			       workbook->utf16_converter, &workbook_start,
			       &workbook_len, &miniFAT_start, &miniFAT_len,
			       &rootEntry);
	  if (ret != FREEXL_OK)
	      return ret;
	  if (rootEntry)
	    {
		/* ok, Root Entry found */
		workbook->fat->miniFAT_start = miniFAT_start;
		workbook->fat->miniFAT_len = miniFAT_len;
	    }
	  if (workbook_start != 0xFFFFFFFF)
	    {
		/* ok, Workbook found */
		workbook->start_sector = workbook_start;
		workbook->size = workbook_len;
		workbook->current_sector = workbook_start;
		return FREEXL_OK;
	    }
      }
    return FREEXL_BIFF_WORKBOOK_NOT_FOUND;
}

static void *
create_utf16_utf8_converter (void)
{
/* creating the UTF16/UTF8 converter and returning on opaque reference to it */
    iconv_t cvt = iconv_open ("UTF-8", "UTF-16LE");
    if (cvt == (iconv_t) (-1))
	return NULL;
    return cvt;
}

static int
check_little_endian_arch ()
{
/* checking if target CPU is a little-endian one */
    biff_word32 word32;
    word32.value = 1;
    if (word32.bytes[0] == 0)
	return 1;
    return 0;
}

static int
common_open (const char *path, const void **xls_handle, int magic)
{
/* opening and initializing the Workbook */
    biff_workbook *workbook;
    biff_sheet *p_sheet;
    fat_chain *chain = NULL;
    int errcode;
    int ret;
    int swap = check_little_endian_arch ();

    *xls_handle = NULL;
/* allocating the Workbook struct */
    workbook = alloc_workbook (magic);
    if (!workbook)
	return FREEXL_INSUFFICIENT_MEMORY;
    *xls_handle = workbook;

    workbook->xls = fopen (path, "rb");
    if (workbook->xls == NULL)
	return FREEXL_FILE_NOT_FOUND;

/*
 * the XLS file is internally structured as a FAT-like
 * file-system (Compound File Binary Format, CFBF) 
 * so we'll start by parsing the FAT
 */
    chain = read_cfbf_header (workbook, swap, &errcode);
    if (!chain)
      {
	  /* it's not a CFBF file: testing older BIFF-(2,3,4) formats */
	  if (read_legacy_biff (workbook, swap))
	      return FREEXL_OK;
	  goto stop;
      }

/* transferring FAT chain ownership */
    workbook->fat = chain;
    chain = NULL;

/* creating the UTF16/UTF8 converter */
    workbook->utf16_converter = create_utf16_utf8_converter ();
    if (workbook->utf16_converter == NULL)
      {
	  errcode = FREEXL_UNSUPPORTED_CHARSET;
	  goto stop;
      }

/* we'll now retrieve the FAT main Directory */
    ret = get_workbook_stream (workbook);
    if (ret != FREEXL_OK)
      {
	  errcode = ret;
	  goto stop;
      }

/* we'll now parse the Workbook */
    if (workbook->size <= workbook->fat->miniCutOff)
      {
	  /* mini-stream stored in miniFAT */
	  int ret = read_mini_stream (workbook, &errcode);
	  if (!ret)
	      goto stop;
	  workbook->p_in = workbook->fat->miniStream;
	  while (1)
	    {
		ret = read_mini_biff_next_record (workbook, swap, &errcode);
		if (ret == -1)
		    break;	/* EOF */
		if (ret == 0)
		    goto stop;
	    }
      }
    else
      {
	  /* normal stream */
	  while (1)
	    {
		int ret = read_biff_next_record (workbook, swap, &errcode);
		if (ret == -1)
		    break;	/* EOF */
		if (ret == 0)
		    goto stop;
	    }
      }

    p_sheet = workbook->first_sheet;
    while (p_sheet)
      {
	  if (p_sheet->valid_dimension == 0)
	    {
		/* setting Sheet dimensions */
		int ret;
		p_sheet->rows += 1;
		p_sheet->columns += 1;
		ret = allocate_cells (workbook);
		if (ret != FREEXL_OK)
		  {
		      errcode = ret;
		      goto stop;
		  }
		p_sheet->valid_dimension = 1;
		workbook->second_pass = 1;
	    }
	  else
	      p_sheet->already_done = 1;
	  p_sheet = p_sheet->next;
      }

    if (workbook->second_pass)
      {
	  /* attempting to fetch cell values performing a second pass */
	  workbook->active_sheet = NULL;
	  workbook->start_sector = 0;
	  workbook->size = 0;
	  workbook->current_sector = 0;
	  workbook->bytes_read = 0;
	  workbook->current_offset = 0;
	  workbook->p_in = workbook->sector_buf;
	  workbook->sector_end = 0;
	  workbook->sector_ready = 0;
	  workbook->ok_bof = -1;

	  ret = get_workbook_stream (workbook);
	  if (ret != FREEXL_OK)
	    {
		errcode = ret;
		goto stop;
	    }

/* we'll now parse the Workbook */
	  if (workbook->size <= workbook->fat->miniCutOff)
	    {
		/* mini-stream stored in miniFAT */
		int ret = read_mini_stream (workbook, &errcode);
		if (!ret)
		    goto stop;
		workbook->p_in = workbook->fat->miniStream;
		while (1)
		  {
		      ret =
			  read_mini_biff_next_record (workbook, swap, &errcode);
		      if (ret == -1)
			  break;	/* EOF */
		      if (ret == 0)
			  goto stop;
		  }
	    }
	  else
	    {
		/* normal stream */
		while (1)
		  {
		      int ret =
			  read_biff_next_record (workbook, swap, &errcode);
		      if (ret == -1)
			  break;	/* EOF */
		      if (ret == 0)
			  goto stop;
		  }
	    }
      }

    return FREEXL_OK;

  stop:
    if (chain)
	destroy_fat_chain (chain);
    if (workbook)
	destroy_workbook (workbook);
    *xls_handle = NULL;
    return errcode;
}

FREEXL_DECLARE int
freexl_open (const char *path, const void **xls_handle)
{
/* opening and initializing the Workbook */
    return common_open (path, xls_handle, FREEXL_MAGIC_START);
}

FREEXL_DECLARE int
freexl_open_info (const char *path, const void **xls_handle)
{
/* opening and initializing the Workbook (only for Info) */
    return common_open (path, xls_handle, FREEXL_MAGIC_INFO);
}

FREEXL_DECLARE int
freexl_close (const void *xls_handle)
{
/* attempting to destroy the Workbook */
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

/* destroying the workbook */
    destroy_workbook (workbook);

    return FREEXL_OK;
}

FREEXL_DECLARE int
freexl_get_info (const void *xls_handle, unsigned short what,
		 unsigned int *info)
{
/* attempting to retrieve some info */
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!info)
	return FREEXL_NULL_ARGUMENT;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    switch (what)
      {
      case FREEXL_CFBF_VERSION:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->cfbf_version == 3)
	      *info = FREEXL_CFBF_VER_3;
	  if (workbook->cfbf_version == 4)
	      *info = FREEXL_CFBF_VER_4;
	  return FREEXL_OK;
      case FREEXL_CFBF_SECTOR_SIZE:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->cfbf_sector_size == 512)
	      *info = FREEXL_CFBF_SECTOR_512;
	  if (workbook->cfbf_sector_size == 4096)
	      *info = FREEXL_CFBF_SECTOR_4096;
	  return FREEXL_OK;
      case FREEXL_CFBF_FAT_COUNT:
	  if (workbook->fat != NULL)
	      *info = workbook->fat->fat_array_count;
	  else
	      *info = 0;
	  return FREEXL_OK;
      case FREEXL_BIFF_MAX_RECSIZE:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->biff_max_record_size == 2080)
	      *info = FREEXL_BIFF_MAX_RECSZ_2080;
	  if (workbook->biff_max_record_size == 8224)
	      *info = FREEXL_BIFF_MAX_RECSZ_8224;
	  return FREEXL_OK;
      case FREEXL_BIFF_DATEMODE:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->biff_date_mode == 0)
	      *info = FREEXL_BIFF_DATEMODE_1900;
	  if (workbook->biff_date_mode == 1)
	      *info = FREEXL_BIFF_DATEMODE_1904;
	  return FREEXL_OK;
      case FREEXL_BIFF_CODEPAGE:
	  switch (workbook->biff_book_code_page)
	    {
	    case 0x016F:
		*info = FREEXL_BIFF_ASCII;
		break;
	    case 0x01B5:
		*info = FREEXL_BIFF_CP437;
		break;
	    case 0x02D0:
		*info = FREEXL_BIFF_CP720;
		break;
	    case 0x02E1:
		*info = FREEXL_BIFF_CP737;
		break;
	    case 0x0307:
		*info = FREEXL_BIFF_CP775;
		break;
	    case 0x0352:
		*info = FREEXL_BIFF_CP850;
		break;
	    case 0x0354:
		*info = FREEXL_BIFF_CP852;
		break;
	    case 0x0357:
		*info = FREEXL_BIFF_CP855;
		break;
	    case 0x0359:
		*info = FREEXL_BIFF_CP857;
		break;
	    case 0x035A:
		*info = FREEXL_BIFF_CP858;
		break;
	    case 0x035C:
		*info = FREEXL_BIFF_CP860;
		break;
	    case 0x035D:
		*info = FREEXL_BIFF_CP861;
		break;
	    case 0x035E:
		*info = FREEXL_BIFF_CP862;
		break;
	    case 0x035F:
		*info = FREEXL_BIFF_CP863;
		break;
	    case 0x0360:
		*info = FREEXL_BIFF_CP864;
		break;
	    case 0x0361:
		*info = FREEXL_BIFF_CP865;
		break;
	    case 0x0362:
		*info = FREEXL_BIFF_CP866;
		break;
	    case 0x0365:
		*info = FREEXL_BIFF_CP869;
		break;
	    case 0x036A:
		*info = FREEXL_BIFF_CP874;
		break;
	    case 0x03A4:
		*info = FREEXL_BIFF_CP932;
		break;
	    case 0x03A8:
		*info = FREEXL_BIFF_CP936;
		break;
	    case 0x03B5:
		*info = FREEXL_BIFF_CP949;
		break;
	    case 0x03B6:
		*info = FREEXL_BIFF_CP950;
		break;
	    case 0x04B0:
		*info = FREEXL_BIFF_UTF16LE;
		break;
	    case 0x04E2:
		*info = FREEXL_BIFF_CP1250;
		break;
	    case 0x04E3:
		*info = FREEXL_BIFF_CP1251;
		break;
	    case 0x04E4:
	    case 0x8001:
		*info = FREEXL_BIFF_CP1252;
		break;
	    case 0x04E5:
		*info = FREEXL_BIFF_CP1253;
		break;
	    case 0x04E6:
		*info = FREEXL_BIFF_CP1254;
		break;
	    case 0x04E7:
		*info = FREEXL_BIFF_CP1255;
		break;
	    case 0x04E8:
		*info = FREEXL_BIFF_CP1256;
		break;
	    case 0x04E9:
		*info = FREEXL_BIFF_CP1257;
		break;
	    case 0x04EA:
		*info = FREEXL_BIFF_CP1258;
		break;
	    case 0x0551:
		*info = FREEXL_BIFF_CP1361;
		break;
	    case 0x2710:
	    case 0x8000:
		*info = FREEXL_BIFF_MACROMAN;
		break;
	    default:
		*info = FREEXL_UNKNOWN;
		break;
	    };
	  return FREEXL_OK;
      case FREEXL_BIFF_VERSION:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->biff_version == 2)
	      *info = FREEXL_BIFF_VER_2;
	  if (workbook->biff_version == 3)
	      *info = FREEXL_BIFF_VER_3;
	  if (workbook->biff_version == 4)
	      *info = FREEXL_BIFF_VER_4;
	  if (workbook->biff_version == 5)
	      *info = FREEXL_BIFF_VER_5;
	  if (workbook->biff_version == 8)
	      *info = FREEXL_BIFF_VER_8;
	  return FREEXL_OK;
      case FREEXL_BIFF_STRING_COUNT:
	  *info = workbook->shared_strings.string_count;
	  return FREEXL_OK;
      case FREEXL_BIFF_SHEET_COUNT:
	  *info = get_worksheet_count (workbook);
	  return FREEXL_OK;
      case FREEXL_BIFF_FORMAT_COUNT:
	  *info = workbook->max_format_index;
	  return FREEXL_OK;
      case FREEXL_BIFF_XF_COUNT:
	  *info = workbook->biff_xf_next_index;
	  return FREEXL_OK;
      case FREEXL_BIFF_PASSWORD:
	  *info = FREEXL_UNKNOWN;
	  if (workbook->biff_obfuscated == 0)
	      *info = FREEXL_BIFF_PLAIN;
	  else if (workbook->biff_obfuscated == 0)
	      *info = FREEXL_BIFF_OBFUSCATED;
	  else
	      *info = workbook->biff_xf_next_index;
	  return FREEXL_OK;
      };

    return FREEXL_INVALID_INFO_ARG;
}

FREEXL_DECLARE int
freexl_get_FAT_entry (const void *xls_handle, unsigned int sector_index,
		      unsigned int *next_sector_index)
{
/* attempting to retrieve some FAT entry [by index] */
    fat_entry *entry;
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!next_sector_index)
	return FREEXL_NULL_ARGUMENT;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    if (workbook->fat == NULL)
	return FREEXL_CFBF_EMPTY_FAT_CHAIN;

    entry = get_fat_entry (workbook->fat, sector_index);
    if (entry == NULL)
	return FREEXL_CFBF_ILLEGAL_FAT_ENTRY;
    *next_sector_index = entry->next_sector;

    return FREEXL_OK;

}

FREEXL_DECLARE int
freexl_get_worksheet_name (const void *xls_handle,
			   unsigned short worksheet_index, const char **string)
{
/* attempting to retrieve some Worksheet name [by index] */
    unsigned int count = 0;
    biff_sheet *worksheet;
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!string)
	return FREEXL_NULL_ARGUMENT;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    worksheet = workbook->first_sheet;
    while (worksheet)
      {
	  if (count == worksheet_index)
	    {
		*string = worksheet->utf8_name;
		return FREEXL_OK;
	    }
	  count++;
	  worksheet = worksheet->next;
      }
    return FREEXL_BIFF_ILLEGAL_SHEET_INDEX;
}

FREEXL_DECLARE int
freexl_select_active_worksheet (const void *xls_handle,
				unsigned short worksheet_index)
{
/* selecting the currently active worksheet [by index] */
    unsigned int count = 0;
    biff_sheet *worksheet;
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    worksheet = workbook->first_sheet;
    while (worksheet)
      {
	  if (count == worksheet_index)
	    {
		workbook->active_sheet = worksheet;
		return FREEXL_OK;
	    }
	  count++;
	  worksheet = worksheet->next;
      }
    return FREEXL_BIFF_ILLEGAL_SHEET_INDEX;
}

FREEXL_DECLARE int
freexl_get_active_worksheet (const void *xls_handle,
			     unsigned short *worksheet_index)
{
/* retrieving the currently active worksheet index */
    unsigned int count = 0;
    biff_sheet *worksheet;
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!worksheet_index)
	return FREEXL_NULL_ARGUMENT;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    worksheet = workbook->first_sheet;
    while (worksheet)
      {
	  if (workbook->active_sheet == worksheet)
	    {
		*worksheet_index = count;
		return FREEXL_OK;
	    }
	  count++;
	  worksheet = worksheet->next;
      }
    return FREEXL_BIFF_UNSELECTED_SHEET;
}

FREEXL_DECLARE int
freexl_worksheet_dimensions (const void *xls_handle, unsigned int *rows,
			     unsigned short *columns)
{
/* dimensions: currently selected Worksheet */
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!rows)
	return FREEXL_NULL_ARGUMENT;
    if (!columns)
	return FREEXL_NULL_ARGUMENT;
    if ((workbook->magic1 == FREEXL_MAGIC_INFO
	 || workbook->magic1 == FREEXL_MAGIC_START)
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    if (workbook->active_sheet == NULL)
	return FREEXL_BIFF_UNSELECTED_SHEET;

    *rows = workbook->active_sheet->rows;
    *columns = workbook->active_sheet->columns;
    return FREEXL_OK;
}

FREEXL_DECLARE int
freexl_get_SST_string (const void *xls_handle, unsigned short string_index,
		       const char **string)
{
/* attempting to retrieve some SST entry [by index] */
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!string)
	return FREEXL_NULL_ARGUMENT;
    if (workbook->magic1 == FREEXL_MAGIC_START
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    *string = NULL;
    if (workbook->shared_strings.utf8_strings == NULL)
	return FREEXL_BIFF_INVALID_SST;
    if (string_index < workbook->shared_strings.string_count)
      {
	  *string = *(workbook->shared_strings.utf8_strings + string_index);
	  return FREEXL_OK;
      }
    return FREEXL_BIFF_ILLEGAL_SST_INDEX;
}

FREEXL_DECLARE int
freexl_get_cell_value (const void *xls_handle, unsigned int row,
		       unsigned short column, FreeXL_CellValue * val)
{
/* attempting to fetch a cell value */
    biff_cell_value *p_cell;
    biff_workbook *workbook = (biff_workbook *) xls_handle;
    if (!workbook)
	return FREEXL_NULL_HANDLE;
    if (!val)
	return FREEXL_NULL_ARGUMENT;
    if (workbook->magic1 == FREEXL_MAGIC_START
	&& workbook->magic2 == FREEXL_MAGIC_END)
	;
    else
	return FREEXL_INVALID_HANDLE;

    if (row >= workbook->active_sheet->rows
	|| column >= workbook->active_sheet->columns)
	return FREEXL_ILLEGAL_CELL_ROW_COL;
    if (workbook->active_sheet->cell_values == NULL)
	return FREEXL_ILLEGAL_CELL_ROW_COL;

    p_cell =
	workbook->active_sheet->cell_values +
	(row * workbook->active_sheet->columns) + column;
/* 
/ kindly contributed by Brad Hards: 2011-09-03
/ this function now return the Cell Value using the
/ FreeXL_CellValue multi-type container 
*/
    val->type = p_cell->type;
    switch (p_cell->type)
      {
      case FREEXL_CELL_INT:
	  val->value.int_value = p_cell->value.int_value;
	  break;
      case FREEXL_CELL_DOUBLE:
	  val->value.double_value = p_cell->value.dbl_value;
	  break;
      case FREEXL_CELL_DATE:
      case FREEXL_CELL_DATETIME:
      case FREEXL_CELL_TIME:
      case FREEXL_CELL_TEXT:
	  val->value.text_value = p_cell->value.text_value;
	  break;
      case FREEXL_CELL_SST_TEXT:
	  val->value.text_value = p_cell->value.sst_value;
	  break;
      };

    return FREEXL_OK;
}
