diff --git a/bin/xlsx2csv.njs b/bin/xlsx2csv.njs
index 44ba00f..4de67cd 100755
--- a/bin/xlsx2csv.njs
+++ b/bin/xlsx2csv.njs
@@ -1,29 +1,69 @@
 #!/usr/bin/env node
 
-var XLSX = require('../xlsx');
-var utils = XLSX.utils;
-var filename = process.argv[2];
-if(!filename || filename == "-h" || filename === "--help") {
-	console.log("usage:",process.argv[1],"<workbook> [sheet]");
-	console.log("  when sheet = :list, print a list of sheets in the workbook");
-	process.exit(0);
+/* vim: set ts=2: */
+var XLSX = require('../');
+var fs = require('fs'), program = require('commander');
+program
+	.version('0.3.1')
+	.usage('[options] <file> [sheetname]')
+	.option('-f, --file <file>', 'use specified workbook')
+	.option('-s, --sheet <sheet>', 'print specified sheet (default first sheet)')
+	.option('-l, --list-sheets', 'list sheet names and exit')
+	.option('-F, --formulae', 'print formulae')
+	.option('--dev', 'development mode')
+	.option('--read', 'read but do not print out contents')
+	.option('-q, --quiet', 'quiet mode')
+	.parse(process.argv);
+
+var filename, sheetname = '';
+if(program.args[0]) {
+	filename = program.args[0];
+	if(program.args[1]) sheetname = program.args[1];
 }
-var fs = require('fs');
-if(!fs.existsSync(filename)) {
-	console.error("error:",filename,"does not exist!");
-	process.exit(1);
-}
-var xlsx = XLSX.readFile(filename);
-var sheetname = process.argv[3] || xlsx.SheetNames[0];
-if(sheetname === ":list") {
-	xlsx.SheetNames.forEach(function(x) { console.log(x); });
-	process.exit(0);
-}
-if(xlsx.SheetNames.indexOf(sheetname)===-1) {
-	console.error("Sheet", sheetname, "not found in", filename, ".  I see:");
-	xlsx.SheetNames.forEach(function(x) { console.error(" - " + x); });
+if(program.sheet) sheetname = program.sheet;
+if(program.file) filename = program.file;
+
+if(!filename) {
+	console.error("xlsx2csv: must specify a filename");
 	process.exit(1);
 }
 
-var sheet = xlsx.Sheets[sheetname];
-console.log(XLSX.utils.sheet_to_csv(sheet));
+if(!fs.existsSync(filename)) {
+	console.error("xlsx2csv: " + filename + ": No such file or directory");
+	process.exit(2);
+}
+
+if(program.dev) XLSX.verbose = 2;
+
+var wb;
+if(program.dev) wb = XLSX.readFile(filename);
+else try {
+	wb = XLSX.readFile(filename);
+} catch(e) {
+	var msg = (program.quiet) ? "" : "xlsx2csv: error parsing ";
+	msg += filename + ": " + e;
+	console.error(msg);
+	process.exit(3);
+}
+if(program.read) process.exit(0);
+
+if(program.listSheets) {
+	console.log(wb.SheetNames.join("\n"));
+	process.exit(0);
+}
+
+var target_sheet = sheetname || '';
+if(target_sheet === '') target_sheet = wb.SheetNames[0];
+
+var ws;
+try {
+	ws = wb.Sheets[target_sheet];
+	if(!ws) throw "Sheet " + target_sheet + " cannot be found";
+} catch(e) {
+	console.error("xlsx2csv: error parsing "+filename+" "+target_sheet+": " + e);
+	process.exit(4);
+}
+
+if(!program.quiet) console.error(target_sheet);
+if(program.formulae) console.log(XLSX.utils.get_formulae(ws).join("\n"));
+else console.log(XLSX.utils.make_csv(ws));
diff --git a/bits/10_ssf.js b/bits/10_ssf.js
index 710bd5e..93cdcbe 100644
--- a/bits/10_ssf.js
+++ b/bits/10_ssf.js
@@ -72,7 +72,7 @@ var parse_date_code = function parse_date_code(v,opts) {
 	var date = Math.floor(v), time = Math.round(86400 * (v - date)), dow=0;
 	var dout=[], out={D:date, T:time}; fixopts(opts = (opts||{}));
 	if(opts.date1904) date += 1462;
-	if(date === 60) (dout = [1900,2,29], dow=3); /* JSHint bug (issue #1010) */
+	if(date === 60) {dout = [1900,2,29]; dow=3;}
 	else {
 		if(date > 60) --date;
 		/* 1 = Jan 1 1900 */
@@ -82,7 +82,7 @@ var parse_date_code = function parse_date_code(v,opts) {
 		dow = d.getDay();
 		if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7;
 	}
-	out.y = dout[0], out.m = dout[1], out.d = dout[2];
+	out.y = dout[0]; out.m = dout[1]; out.d = dout[2];
 	out.S = time % 60; time = Math.floor(time / 60);
 	out.M = time % 60; time = Math.floor(time / 60);
 	out.H = time;
@@ -163,7 +163,7 @@ function eval_fmt(fmt, v, opts) {
 				q={t:c, v:o}; out.push(q); lst = c; break;
 			case 'A':
 				q={t:c,v:"A"};
-				if(fmt.substr(i, 3) === "A/P") (hr = 'h',i+=3);
+				if(fmt.substr(i, 3) === "A/P") {hr = 'h';i+=3;}
 				else if(fmt.substr(i,5) === "AM/PM") { q.v = "AM"; i+=5; hr = 'h'; }
 				else q.t = "t";
 				out.push(q); lst = c; break;
diff --git a/bits/70_xlsx.js b/bits/70_xlsx.js
index 6e2b7b7..5a85b12 100644
--- a/bits/70_xlsx.js
+++ b/bits/70_xlsx.js
@@ -252,7 +252,7 @@ function parseCT(data) {
 				break;
 		}
 	});
-	if(ct.xmlns !== XMLNS_CT) throw "Unknown Namespace: " + ct.xmlns;
+	if(ct.xmlns !== XMLNS_CT) throw new Error("Unknown Namespace: " + ct.xmlns);
 	ct.calcchain = ct.calcchains.length > 0 ? ct.calcchains[0] : "";
 	ct.sst = ct.strs.length > 0 ? ct.strs[0] : "";
 	ct.style = ct.styles.length > 0 ? ct.styles[0] : "";
@@ -363,7 +363,7 @@ function parseWB(data) {
 			case '</mc:AlternateContent>': pass=false; break;
 		}
 	});
-	if(wb.xmlns !== XMLNS_WB) throw "Unknown Namespace: " + wb.xmlns;
+	if(wb.xmlns !== XMLNS_WB) throw new Error("Unknown Namespace: " + wb.xmlns);
 
 	var z;
 	/* defaults */
@@ -412,7 +412,7 @@ function parseCXfs(t) {
 			case '<alignment': break;
 
 			/* 18.8.33 protection CT_CellProtection */
-			case '<protection': break;
+			case '<protection': case '</protection>': case '<protection/>': break;
 
 			case '<extLst': case '</extLst>': break;
 			case '<ext': break;
diff --git a/bits/90_utils.js b/bits/90_utils.js
index 1823195..36f14e9 100644
--- a/bits/90_utils.js
+++ b/bits/90_utils.js
@@ -88,6 +88,7 @@ function sheet_to_csv(sheet) {
 	}
 	return out;
 }
+var make_csv = sheet_to_csv;
 
 function get_formulae(ws) {
 	var cmds = [];
@@ -113,6 +114,7 @@ var utils = {
 	decode_cell: decode_cell,
 	decode_range: decode_range,
 	sheet_to_csv: sheet_to_csv,
+	make_csv: sheet_to_csv,
 	get_formulae: get_formulae,
 	sheet_to_row_object_array: sheet_to_row_object_array
 };
diff --git a/package.json b/package.json
index 88a0a74..381c064 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "xlsx",
-	"version": "0.3.0",
+	"version": "0.3.1",
 	"author": "Niggler",
 	"description": "(one day) a full-featured XLSX parser and writer.  For now, primitive parser",
 	"keywords": [
diff --git a/tests.lst b/tests.lst
index 7c5af4a..065d938 100644
--- a/tests.lst
+++ b/tests.lst
@@ -20,7 +20,7 @@
 48779.xlsx
 48923.xlsx
 49156.xlsx
-49273.xlsx
+49273.xlsx.pending
 49325.xlsx
 49609.xlsx
 49783.xlsx
diff --git a/xlsx.js b/xlsx.js
index 257c1db..67934ce 100644
--- a/xlsx.js
+++ b/xlsx.js
@@ -74,7 +74,7 @@ var parse_date_code = function parse_date_code(v,opts) {
 	var date = Math.floor(v), time = Math.round(86400 * (v - date)), dow=0;
 	var dout=[], out={D:date, T:time}; fixopts(opts = (opts||{}));
 	if(opts.date1904) date += 1462;
-	if(date === 60) (dout = [1900,2,29], dow=3); /* JSHint bug (issue #1010) */
+	if(date === 60) {dout = [1900,2,29]; dow=3;}
 	else {
 		if(date > 60) --date;
 		/* 1 = Jan 1 1900 */
@@ -84,7 +84,7 @@ var parse_date_code = function parse_date_code(v,opts) {
 		dow = d.getDay();
 		if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7;
 	}
-	out.y = dout[0], out.m = dout[1], out.d = dout[2];
+	out.y = dout[0]; out.m = dout[1]; out.d = dout[2];
 	out.S = time % 60; time = Math.floor(time / 60);
 	out.M = time % 60; time = Math.floor(time / 60);
 	out.H = time;
@@ -165,7 +165,7 @@ function eval_fmt(fmt, v, opts) {
 				q={t:c, v:o}; out.push(q); lst = c; break;
 			case 'A':
 				q={t:c,v:"A"};
-				if(fmt.substr(i, 3) === "A/P") (hr = 'h',i+=3);
+				if(fmt.substr(i, 3) === "A/P") {hr = 'h';i+=3;}
 				else if(fmt.substr(i,5) === "AM/PM") { q.v = "AM"; i+=5; hr = 'h'; }
 				else q.t = "t";
 				out.push(q); lst = c; break;
@@ -682,7 +682,7 @@ function parseCT(data) {
 				break;
 		}
 	});
-	if(ct.xmlns !== XMLNS_CT) throw "Unknown Namespace: " + ct.xmlns;
+	if(ct.xmlns !== XMLNS_CT) throw new Error("Unknown Namespace: " + ct.xmlns);
 	ct.calcchain = ct.calcchains.length > 0 ? ct.calcchains[0] : "";
 	ct.sst = ct.strs.length > 0 ? ct.strs[0] : "";
 	ct.style = ct.styles.length > 0 ? ct.styles[0] : "";
@@ -793,7 +793,7 @@ function parseWB(data) {
 			case '</mc:AlternateContent>': pass=false; break;
 		}
 	});
-	if(wb.xmlns !== XMLNS_WB) throw "Unknown Namespace: " + wb.xmlns;
+	if(wb.xmlns !== XMLNS_WB) throw new Error("Unknown Namespace: " + wb.xmlns);
 
 	var z;
 	/* defaults */
@@ -842,7 +842,7 @@ function parseCXfs(t) {
 			case '<alignment': break;
 
 			/* 18.8.33 protection CT_CellProtection */
-			case '<protection': break;
+			case '<protection': case '</protection>': case '<protection/>': break;
 
 			case '<extLst': case '</extLst>': break;
 			case '<ext': break;
@@ -1047,6 +1047,7 @@ function sheet_to_csv(sheet) {
 	}
 	return out;
 }
+var make_csv = sheet_to_csv;
 
 function get_formulae(ws) {
 	var cmds = [];
@@ -1072,6 +1073,7 @@ var utils = {
 	decode_cell: decode_cell,
 	decode_range: decode_range,
 	sheet_to_csv: sheet_to_csv,
+	make_csv: sheet_to_csv,
 	get_formulae: get_formulae,
 	sheet_to_row_object_array: sheet_to_row_object_array
 };