/*
 *	Copyright © 2005–2025 Jan Bobrowski <jb@torinak.com>
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	version 2 as published by the Free Software Foundation.
 */

/*
 * Based on information from sc.openoffice.org/excelfileformat.pdf
 * Some bugs spotted by Sebastian Freundt were fixed
 */

#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include "xls2txt.h"

void output(char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	int r = vprintf(format, ap);
	if (r < 0)
		perror(0);
	va_end(ap);
}

#define TRUNC errx(1, "Truncated  &%d", __LINE__)
#define BADF(T) errx(1, *T""?T"  &%d":"Format error  &%d", __LINE__);

struct g {
	unsigned all:1;
	unsigned sel:1; // -n
	unsigned nofmt:1;
	unsigned titles:1;
	unsigned biff2ok:1; // -2
	int nr; // sheet number
	int row, col; // current pos
	unsigned top, bottom, left, right;
} g;

struct sst {
	u8 *ptr, *rend;
};

enum fmt_type {
	Unknown = 0,
	Number = 1,
	Scientific = 2,
	Date = 3,
	Time = 4,
	DateTime = 5
};

struct fmt {
	enum fmt_type type:8;
	unsigned arg:8;
};

struct tab {
	void *tab;
	int nelem, aelem, esize;
};

static inline void *tab_ptr(struct tab *tab, unsigned n)
{
	return (char *)tab->tab + n*tab->esize;
}

#define TAB(S,T,N) (*(T*)((char*)(S).tab + (N) * sizeof(T)))

void *tab_alloc(struct tab *tab, unsigned n, const void *dflt)
{
	if (n < tab->nelem)
		return tab_ptr(tab, n);
	if (n >= tab->aelem) {
		int sz = n+16 & ~15;
		tab->aelem = sz;
		tab->tab = realloc(tab->tab, sz * tab->esize);
		if (!tab->tab)
			err(1, "realloc");
	}
	u8 *p = tab_ptr(tab, tab->nelem);
	n -= tab->nelem;
	tab->nelem += n + 1;
	if (n) do {
		memcpy(p, dflt, tab->esize);
		p += tab->esize;
	} while (--n);
	return p;
}

struct xls {
	meml_t map;
	u8 *end;
	u8 *shptr;
/*
	00:*	BIFF2
	02:*	BIFF3
	04:*	BIFF4
	08:0500	BIFF5 (and BIFF7)
	08:0600	BIFF8

	08:0000 BIFF5
	08:0200 BIFF2
	08:0300 BIFF3
	08:0400 BIFF4
*/
	enum {
		BIFF2 = 2,
		BIFF3 = 3,
		BIFF4 = 4,
		BIFF5 = 5,
		BIFF8 = 6
	} biffv;

	unsigned e1904;

	struct sst *sst;
	unsigned nsst;

	struct tab fmt;
	struct tab xf_ptr;
	struct tab xf_fmt;
};

static struct xls x;

void check_biffv(u8 *p)
{
	int v;
	if (p[0] != 9)
		errx(1, "Format not recognized");
	switch (p[1]) {
	case 0:
biff2:
		if (!g.biff2ok)
			errx(1, "Format not supported (BIFF2), try with -d");
		v = BIFF2; goto ok;
	case 2: v = BIFF3; goto ok;
	case 4: v = BIFF4; goto ok;
	case 8: break;
	default:
nsupp:
		errx(1, "Format not supported");
	}
	switch (p[5]) {
	case 0: v = BIFF5; break;
	case 2: goto biff2;
	default:
		v = p[5];
		if (v < BIFF3 || v > BIFF8)
			goto nsupp;
	}
ok:
	x.biffv = v;
}

static u8 *output_string(u8 *p, int l)
{
	if (x.biffv < BIFF8) {
		p = output_cp_string(p, l);
	} else {
		u8 f = *p++;
		int a = 0;
		if (f & 8) {
			a += 4*g16(p);
			p += 2;
		}
		if (f & 4) {
			a += g32(p);
			p += 4;
		}
		p = a + output_unicode(p, l, f);
	}
	return p;
}

static void output_sst(int n)
{
	if (n < 0 || n >= x.nsst)
		BADF("Wrong string index");

	u8 *p = x.sst[n].ptr;
	u8 *re = x.sst[n].rend;
	unsigned l = g16(p);
	u8 f = p[2];
	p += 3 + (f & 8 ? 2 : 0) + (f & 4 ? 4 : 0);
	for (;;) {
		int s = re - p;
		f &= 1;
		if (l <= s >> f)
			break;

		if (re[0] != 0x3C) // CONTINUE
			BADF("String truncated");

		l -= s >> f;
		if (s & f)
			BADF("String cut at the middle of a char");
		output_unicode(p, s >> f, f);

		p = re + 4;
		re = p + g16(re + 2);
		f = *p++;
	}
	output_unicode(p, l, f);
}

static u8 *read_sst(u8 *p, u8 *re, u8 *fe)
{
	x.nsst = g32(p + 4);
	if (!x.nsst)
		return re;

	x.sst = calloc(x.nsst, sizeof *x.sst);
	if (!x.sst)
		err(1, "calloc");

	p += 8;

	for (unsigned nsst = 0;;) {
		if (re - p < 3)
			BADF("String table truncated");

		x.sst[nsst].ptr = p;
		x.sst[nsst].rend = re;
		if (++nsst == x.nsst)
			break;

		unsigned l = g16(p);
		u8 f = p[2];
		p += 3;
		unsigned a = 0;
		if (f & 8) {
			a = 4*g16(p);
			p += 2;
		}
		if (f & 4) {
			a += g32(p);
			p += 4;
		}
//		fmt_assert(p < re);
		for (;;) {
			int s = re - p;
			f &= 1;
			if (s >= l << f)
				break;
//			fmt_assert(!(s & f));
//			fmt_assert(re < fe);
			l -= s>>f;
			if (re[0] != 0x3C) // CONTINUE
				BADF("String truncated");
			p = re + 4;
			re = p + g16(re + 2);
//			fmt_assert(re < fe);
			f = *p++;
		}
		p += l << f;
		for (;;) {
			int s = re - p;
			if (s > a)
				break;
			a -= s;

			if (re[0] != 0x3C) // CONTINUE
				BADF("String truncated");
			p = re + 4;
			re = p + g16(re + 2);
//			fmt_assert(re < fe);
		}
		p += a;
	}
	return re;
}

static const struct fmt default_fmt;
static const u8 *null_ptr;

static void xls_init_struc()
{
	static u8 t[] = { 0, 0x10, 0x12, 0x10, 0x12, 0x10, 0x10, 0x12, 0x12, 0x12, 0x14,
		0x22, 0, 0, 0x30, 0x30, 0x30, 0x30, 0x40, 0x40, 0x40, 0x40, 0x50, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0x10, 0x12, 0x12, 0x10, 0x10, 0x12, 0x12,
		0x40, 0x40, 0x40, 0x21 };

	x.fmt.esize = sizeof default_fmt;
	x.fmt.nelem = 0;
	x.xf_ptr.esize = sizeof null_ptr;
	x.xf_ptr.nelem = 0;
	x.xf_fmt.esize = sizeof null_ptr;
	x.xf_fmt.nelem = 0;
	x.e1904 = 0;

	tab_alloc(&x.fmt, elemof(t) - 1, &default_fmt);
	struct fmt *tab = x.fmt.tab;
	for (int i = 0; i < elemof(t); i++) {
		tab[i].type = t[i] >> 4;
		tab[i].arg = t[i] & 0xF;
	}
}

static void getstr(u16 *d, u8 *p, int l)
{
	int v = 0;
	if (x.biffv >= BIFF8)
		v = *p++ & 1;
	// XXX
	if (v) {
		while (--l >= 0)
			d[l] = g16(p + 2*l);
	} else {
		while (--l >= 0)
			d[l] = p[l];
	}
}

static void parse_fmt(struct fmt *f, u16 *p, int l)
{
	f->type = Unknown;
	f->arg = 0;

	u16 *e = p + l;
	if (e == p)
		return;

	while (*p == '[') {
		do {
			if (++p == e)
				return;
		} while (*p != ']');
		if (++p == e)
			return;
		if (*p == ':')
			f->type = Time;
	}

	if (*p == 'h' && p + 2 < e && (p[1] == ':' || p[1] == 'h' && p[2] == ':')) {
		f->type = Time;
	} else if (*p <= 'z' && strchr("YyMmDd", *p)) {
		f->type = DateTime;
	}

	if (f->type != Unknown)
		return;

	u16 *d = 0;
	u16 *q = p;
	for (;;) {
		if (*q == '.') {
			d = q;
			break;
		}
		if (*q >= 128 || !strchr("0#?, ", *q) || ++q == e)
			break;
	}
	if (!d) {
		if (p != q && (q == e || *q != '/')) {
//			f->arg = 0;
			f->type = Number;
		}
		return;
	}
	while (++q < e)
		if (*q != '0' && *q != '#')
			break;

	f->arg = q - d - 1;
	f->type = Number;
}

static void set_fmt(u8 *p)
{
	u8 *q = p + 1;
	if (x.biffv >= BIFF4)
		q += 2;
	int n = x.biffv < BIFF5 ? x.fmt.nelem : g16(p);
	int l = q[-1];
	if (x.biffv >= BIFF8) {
		l = g16(p + 2);
		q++;
	}

	u16 t[128];
	if (l > elemof(t))
		return;

	getstr(t, q, l);
	struct fmt *fmt = (struct fmt *)tab_alloc(&x.fmt, n, &default_fmt);
	parse_fmt(fmt, t, l);
}

static const struct fmt *fmt_from_xf(int xf)
{
	const struct fmt *fmt = &default_fmt;

//	assert(x.biffv != BIFF2);

	if (xf >= x.xf_ptr.nelem) {
bad_xf:
		warnx("Strange XF index %u -- ignored", xf);
		return fmt;
	}

	int org_xf = xf;

	u8 *p;
again:
	p = TAB(x.xf_ptr, u8*, xf);
	if (!p)
		goto bad_xf;

	int n, st, ua;
	if (x.biffv < BIFF5) { // 0x02
		n = p[1];
		st = 2;
		ua = x.biffv < BIFF4 ? 3 : 5;
	} else { // 0xE0
		n = g16(p + 2);
		st = 4;
		ua = x.biffv < BIFF8 ? 7 : 9;
	}
	st = p[st];
	ua = p[ua];

	if (!((st ^ ua) & 4)) { // Format not present
		if (!(st & 4) || xf != org_xf) { // Not a style or loop
			p += x.biffv != BIFF4 ? 4 : 2;
			xf = g16(p) >> 4;
			if (xf != org_xf && xf < x.xf_ptr.nelem)
				goto again;
		}
	} else if (n < x.fmt.nelem)
		fmt = &TAB(x.fmt, struct fmt, n);

	*(const struct fmt **)tab_alloc(&x.xf_fmt, org_xf, &null_ptr) = fmt;
	return fmt;
}

static void output_time(const struct fmt *, double);

static void output_fmt(u8 *xfp, double v)
{
	const struct fmt *f;
	unsigned xf;

	if (g.nofmt) {
		output("%f", v);
		return;
	}

	if (x.biffv == BIFF2) {
		int n = xfp[1] & 63;
		f = &default_fmt;
		if (n < x.fmt.nelem)
			f = &TAB(x.fmt, struct fmt, n);
		goto have_fmt;
	}

	xf = g16(xfp);
	if (xf < x.xf_fmt.nelem) {
		f = TAB(x.xf_fmt, struct fmt*, xf);
		if (f)
			goto have_fmt;
	}
	f = fmt_from_xf(xf);

have_fmt:
	switch (f->type) {
	case Unknown:
		if (ceil(v) == v) {
			output("%.f", v);
			break;
		}
	default:
		output("%f", v);
		break;
	case Number:
		output("%.*f", f->arg, v);
		break;
	case Scientific:
		output("%.*E", f->arg, v);
		break;
	case Time:
	case Date:
	case DateTime:
		output_time(f, v);
		break;
	}
}

static void output_time(const struct fmt *fmt, double v)
{
	int type = fmt->type;
	int f = fmt->arg;
	int d = v;
	v -= d;
	if (x.e1904)
		d += 4*365;
	else if (d <= 60)
		d++;
	d -= 25569;
	time_t t = d*24*60*60 + (unsigned)(v*24*60*60);
	struct tm *tm = gmtime(&t);
	if (!tm) {
		output("#BAD"); // XXX
		return;
	}
	if (type == DateTime && !f && !v)
		type = Date;
	if (type == Date || type == DateTime) {
		output("%04u-%02u-%02u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
		if (type == Date)
			return;
		output(" ");
	}
	output("%02u:%02u:%02u", tm->tm_hour, tm->tm_min, tm->tm_sec);
}

static void output_rk(u8 *xfp, u32 rk)
{
	double v;
	if (rk & 2)
		v = (s32)rk>>2;
	else
		v = ieee754((u64)(rk & ~3) << 32);
	if (rk & 1)
		v /= 100;
	output_fmt(xfp, v);
}

struct rr {
	int o, l, id;
};

#define GETRR(P) \
	if (4 > x.map.len - rr.o) TRUNC; \
	rr.l = g16(x.map.ptr + rr.o + 2); \
	rr.id = x.map.ptr[rr.o]; \
	rr.o += 4; \
	if (rr.l > x.map.len - rr.o) TRUNC; \
	(P) = x.map.ptr + rr.o; \
	rr.o += rr.l;

#define EXPLEN(L) if (rr.l < (L)) errx(1, "Record too short  &%d", __LINE__);

static int skip_substream(int o)
{
	struct rr rr;
	int d = 1;
	rr.o = o;
	for (;;) {
		u8 *p, sv;
		GETRR(p)
		sv = p[-3];
		switch (rr.id) {
		case 0x09:
			if (sv < 0x10)
				d++;
			break;
		case 0x0A:
			if (!sv && !--d)
				return rr.o;
		}
	}
	TRUNC;
}

static int read_init_rr(int o)
{
	xls_init_struc();

	struct rr rr;
	rr.o = o;
	int nr = g.nr;
	int sh = 0;

	u8 *p;
	for (;;) {
		GETRR(p)

		switch (rr.id) {
		case 0x42: // CODEPAGE
			set_codepage(g16(p));
			break;
		case 0xFC: // SST
			rr.o = read_sst(p, x.map.ptr+rr.o, x.end) - x.map.ptr;
			break;
		case 0x1E: // FORMAT
			set_fmt(p);
			break;
		case 0x43:
		case 0xE0: // XF
			*(u8 **)tab_alloc(&x.xf_ptr, x.xf_ptr.nelem, &null_ptr) = p;
			break;
		case 0x04: // LABEL
		case 0x03: // NUMBER
		case 0x06: // FORMULA
		case 0x07: // STRING
		case 0x7E: // RK
			return sh;
		case 0x09: // BOF
			if (p[-3] >= 0x10)
				break;
			rr.o = skip_substream(rr.o);
			break;
		case 0x0A: // EOF
			if (p[-3])
				break;
			return sh;
		case 0x85: // SHEET
			if (!nr--)
				sh = p - 4 - x.map.ptr;
			break;
		case 0x22: // DATEMODE
			x.e1904 = p[0];
			break;
		}
	}
}

int to_cell(int r, int c)
{
	if (r < g.top || r > g.bottom) {
		g.row = r;
		return 0;
	}
	if (g.row < g.top)
		g.row = g.top;
	if (g.row < r) {
		g.col = 0;
		do {
			output("\n");
			g.row++;
		} while (g.row < r);
	}
	if (c < g.left || c > g.right) {
		g.col = c;
		return 0;
	}
	if (g.col < g.left)
		g.col = g.left;
	while (g.col < c) {
		output("\t");
		g.col++;
	}
	return 1;
}

static int to_cell_p(u8 *p)
{
	return to_cell(g16(p), g16(p + 2));
}

static int to_nx_cell()
{
	return to_cell(g.row, g.col + 1);
}

void output_sheet(int o, u8 *name, int nr)
{
	struct rr rr;
	u8 pvrec;

	if (g.titles) {
		if (nr)
			output("\f");
		if (name)
			output_string(name + 1, *name);
		output("\n");
	}

	rr.o = o;
	g.col = g.row = 0;
	pvrec = 0;

	do {
		u8 *p;

		GETRR(p)
		if (rr.id == 0x0A && !p[-3]) // EOF
			break;

		switch (rr.id) {
		case 0x09: // BOF
			if (p[-3] >= 0x10)
				break;
			rr.o = skip_substream(rr.o);
			break;
		case 0x04: // LABEL
			if (to_cell_p(p))
				output_string(p + 8, x.biffv == BIFF2 ? p[7] : g16(p + 6));
			break;
		case 0xFD: // LABELSST
			if (to_cell_p(p))
				output_sst(g32(p + 6));
			break;
		case 0x7E: // RK
			if (to_cell_p(p))
				output_rk(p + 4, g32(p + 6));
			break;
		case 0xBD: { // MULRK
				u8 *q = p + rr.l - 11;
				int f = to_cell_p(p);
				for (;;) {
					p += 6;
					if (f)
						output_rk(p - 2, g32(p));
					if (p >= q)
						break;
					f = to_nx_cell();
				}
			} break;
		case 0x02: // INTEGER
			if (to_cell_p(p))
				output_fmt(p + 4, g16(p + 7));
			break;
		case 0x03: // NUMBER
			if (!to_cell_p(p))
				break;
number:
			output_fmt(p + 4, ieee754(g64(x.biffv == BIFF2 ? p + 7 : p + 6)));
			break;
		case 0x06: // FORMULA
			if (!to_cell_p(p)) {
				pvrec = 0;
				break;
			}
			if (x.biffv == BIFF2 || g16(p + 6 + 6) != 0xFFFF) {
				pvrec = 0;
				goto number;
			}
			// p[6] == 0: STRING follows
			if (p[6] == 1)
				output("%s", p[6 + 2] ? "true" : "false");
			break;
		case 0x07: // STRING
			if (pvrec == 0x06)
				output_string(p + 2, g16(p));
			break;
		case 0xD6: // RSTRING
			if (to_cell_p(p))
				output_string(p + 8, g16(p + 6));
			break;
		}
		pvrec = rr.id;
	} while (g.row <= g.bottom);
	output("\n");
}

void print_xls()
{
	struct rr rr;
	int done;
	u8 *p;

	done = 0;
	rr.o = 0;
	GETRR(p)

	switch (g16(p + 2)) {
	case 0x0010: // single sheet
		if (g.nr)
			goto not_found;
		read_init_rr(rr.o);
		output_sheet(rr.o, 0, 0);
		return;
	case 0x0100:
		goto workbook;
	case 0x0005:
		goto globals;
	default:
		BADF("Bad content");
	}

/* BIFF5+ */
globals:
	rr.o = read_init_rr(rr.o);
	if (!rr.o)
		goto not_found;
	for (;;) {
		u32 o;
		GETRR(p)
		if (rr.id != 0x85) // SHEET
			break;
		o = rr.o;
		rr.o = g32(p);
		if (rr.o >= x.map.len)
			TRUNC;
		if (rr.o <= p - x.map.ptr)
			BADF( );
		if (p[4] == 0) {
			u8 *q;
			GETRR(q)
			(void)q;
			if (rr.id != 0x09)
				BADF( );
			output_sheet(rr.o, p + 6, done++);
			if (!g.all)
				break;
		} else if (g.sel)
			goto not_found;
		rr.o = o;
	}
	return;

/* BIFF4W */
workbook:
	for (;;) {
		GETRR(p)
		switch (rr.id) {
			u32 o;
		case 0x42: // CODEPAGE
			EXPLEN(2)
			set_codepage(g16(p));
			break;
		case 0x8E: // SHEETOFFSET
			EXPLEN(4)
			o = g32(p);
			if (o >= x.map.len)
				TRUNC;
			rr.o = o;
			goto found;
		case 0x0A: // EOF
			if (p[-3])
				break;
			BADF( );
		}
	}
found:
	GETRR(p)
	if (rr.id != 0x8F) // SHEETHDR
		goto not_found;
	EXPLEN(5)
	for (;;) {
		u32 o = g32(p);
		if (o >= x.map.len - rr.o)
			TRUNC;
		o += rr.o;
		if (!g.nr--) {
			u8 *name = p + 4;
			GETRR(p)
			if (rr.id != 0x09) // BOF
				BADF( )
			if (g16(p + 2) == 0x10) {
				read_init_rr(rr.o);
				output_sheet(rr.o, name, done++);
				if (!g.all)
					break;
			} else if (g.sel)
				errx(1, "Not a sheet");
			g.nr = 0;
		}
		rr.o = o;
		GETRR(p)
		if (rr.id != 0x8F) {
			if (!done)
				goto not_found;
			break;
		}
	}
	return;

not_found:
		errx(1, "No such sheet");
}

void list_xls()
{
	struct rr rr;
	u8 *p;

	rr.o = 0;
	GETRR(p)
	if (rr.id != 0x09) // BOF
		BADF( );
	switch (g16(p + 2)) {
	case 0x10:
		output("Single sheet\n");
		return;
	case 5:
	case 0x100:
		break;
	default:
		output("Unknown contents\n");
		return;
	}

	int nr = 0;
	for (;;) {
		GETRR(p)
		switch (rr.id) {
			u8 *q;
			char *k;
		case 0x0A: // EOF
			if (p[-3])
				break;
			return;
		case 0x09: // BOF
			if (p[-3] >= 0x10)
				break;
			rr.o = skip_substream(rr.o);
			break;
		case 0x42: // CODEPAGE
			set_codepage(g16(p));
			break;
		case 0x85: // SHEET
			k = "sheet";
			q = p;
			if (x.biffv > BIFF4) {
				switch (q[5]) {
				case 0: break;
				case 2: k = "chart"; break;
				case 6: k = "vbasic"; break;
				default: k = ""; break;
				}
				q += 6;
			}
			output("%2u. %-8s ", nr++, k);
			output_string(q + 1, q[0]);
			output("\n");
			break;
		}
	}
}

static char *parse_cell(char *s, unsigned *r, unsigned *c)
{
	unsigned a = *s - 'A';
	if (a < 26) {
		unsigned v = a;
		for (;;) {
			a = *++s - 'A';
			if (a >= 26)
				break;
			v = 26*v + a;
		}
		*c = v;
	}
	a += 'A' - '0';
	if (a < 10) {
		unsigned v = a;
		for (;;) {
			a = *++s - '0';
			if (a >= 10)
				break;
			v = 10*v + a;
		}
		*r = v - 1;
	}
	return s;
}

void parse_range(char *s)
{
	s = parse_cell(s, &g.top, &g.left);
	if (!*s)
		return;
	if (*s == ':') {
		s = parse_cell(s + 1, &g.bottom, &g.right);
		if (!*s)
			return;
	}
	errx(1, "Unexpected char '%c' in cell range", *s);
}

int main(int argc, char *argv[])
{
	char o = 0;

	for (;;) switch (getopt(argc, argv, "n:AlP:fdhV?-")) {
		int n;
	case -1:
		goto endopt;
	case 'n':
		g.sel = 1;
		g.nr = atoi(optarg);
		break;
	case 'A':
		g.sel = 0;
		g.all = 1;
		g.titles = 1;
		break;
	case 'l':
		o = 'l';
		break;
	case 'P':
		n = atoi(optarg);
		if (n)
			set_codepage(n);
		break;
	case 'f':
		g.nofmt = 1;
		break;
	case 'd':
		g.biff2ok = 1;
		break;
	case '?':
		if (optopt != '?')
			break;
	case '-':
	case 'h':
	case 'V':
#define _STR(T) #T
#define STR(T) _STR(T)
		output("xls2txt " STR(VERSION) " / "
			"Copyright 2011–2025 Jan Bobrowski / GPL\n");
		goto usage;
	}
endopt:

	g.right = g.bottom = 0xFFFF;
	switch (argc - optind) {
	default:
usage:
		output(
			"usage: xls2txt [-n sheetnum|-A] [-f] file.xls [X:X]\n"
			"       xls2txt -l file.xls\n"
			" X:X\tCell range (eg. A1:C5, D2:E)\n"
			" -l\tList sheets\n"
			" -n num\tSelect sheet\n"
			" -A\tAll sheets (\\f separated)\n"
			" -f\tDon't try to format numbers\n"
		);
		return 1;
	case 1:
		break;
	case 2:
		parse_range(argv[argc - 1]);
		break;
	}

	ole_open(argv[optind]);
	x.map = get_workbook();
	x.end = x.map.ptr + x.map.len;
	check_biffv(x.map.ptr);
	if (o)
		list_xls();
	else
		print_xls();

	if (fflush(stdout) == EOF)
		perror(0);
	return 0;
}
