Comparing version 0.23.0 to 0.24.0
@@ -1,1 +0,3 @@ | ||
module.exports = require('./lib/parser'); | ||
import parser from "./lib/parser.js"; | ||
export default parser; |
@@ -6,34 +6,31 @@ /* | ||
const renderedMaps = require('./mappings.json'); | ||
import renderedMaps from "./renderedMaps.js"; | ||
class Mappings { | ||
keyByRegex(maps, label) { | ||
if (!maps) { | ||
return null; | ||
} | ||
keyByRegex(maps, label) { | ||
if (!maps) { | ||
return null; | ||
} | ||
let key = Object.keys(maps) | ||
.sort((a, b) => a.length - b.length) | ||
.find(key => new RegExp(key, 'i').test(label)); | ||
let key = Object.keys(maps) | ||
.sort((a, b) => a.length - b.length) | ||
.find((key) => new RegExp(key, "i").test(label)); | ||
let result = key ? maps[key] : null; | ||
let result = key ? maps[key] : null; | ||
// for compatibility | ||
if ( | ||
Array.isArray(result) && | ||
result.includes('beginning_image_number') | ||
) { | ||
result = result.map(value => | ||
value.replace('beginning_image_number', 'begin_image_number') | ||
); | ||
} | ||
return result; | ||
// for compatibility | ||
if (Array.isArray(result) && result.includes("beginning_image_number")) { | ||
result = result.map((value) => | ||
value.replace("beginning_image_number", "begin_image_number") | ||
); | ||
} | ||
lookup(type, version) { | ||
return this.keyByRegex(this.keyByRegex(renderedMaps, type), version); | ||
} | ||
return result; | ||
} | ||
lookup(type, version) { | ||
return this.keyByRegex(this.keyByRegex(renderedMaps, type), version); | ||
} | ||
} | ||
module.exports = new Mappings(); | ||
export default new Mappings(); |
@@ -6,39 +6,32 @@ /* | ||
var stream = require('stream'), | ||
inherits = require('inherits'), | ||
genobj = require('generate-object-property'), | ||
genfun = require('generate-function'), | ||
mappings = require('./mappings'); | ||
import { Transform } from "stream"; | ||
var quote = new Buffer.from('"')[0], | ||
comma = new Buffer.from(',')[0], | ||
cr = new Buffer.from('\r')[0], | ||
lf = new Buffer.from('\n')[0], | ||
fs = new Buffer.from('\u001C')[0]; | ||
import genobj from "generate-object-property"; | ||
import genfun from "generate-function"; | ||
import mappings from "./mappings.js"; | ||
const quote = new Buffer.from('"')[0]; | ||
const comma = new Buffer.from(",")[0]; | ||
const cr = new Buffer.from("\r")[0]; | ||
const lf = new Buffer.from("\n")[0]; | ||
const fs = new Buffer.from("\u001C")[0]; | ||
/** | ||
* Sets up the parser, stream and instance variables. Instantiated internally. | ||
* | ||
* @constructor | ||
* @param {Object} opts No options, yet. | ||
*/ | ||
* Sets up the parser, stream and instance variables. Instantiated internally. | ||
* | ||
* @constructor | ||
* @param {Object} opts No options, yet. | ||
*/ | ||
var Parser = function(opts) { | ||
if (!opts) { | ||
opts = {}; | ||
} | ||
stream.Transform.call(this, { | ||
objectMode: true, | ||
highWaterMark: 16 | ||
class Parser extends Transform { | ||
constructor(opts = { map: true }) { | ||
super({ | ||
objectMode: true, | ||
highWaterMark: 16, | ||
}); | ||
this.version = '8.2'; // default filing version | ||
this.version = "8.2"; // default filing version | ||
if ((!opts.map && opts.map !== false) || (opts.map && opts.map === true)) { | ||
this.map = true; | ||
} | ||
else { | ||
this.map = false; | ||
} | ||
this.map = !opts.map && opts.map !== false ? true : opts.map; | ||
@@ -53,47 +46,44 @@ this._prev = null; // already buffered stream data | ||
this._f99 = false; // is an F99 form with a block of free text input | ||
}; | ||
} | ||
inherits(Parser, stream.Transform); | ||
/** | ||
* Called automatically by the incoming stream, does the first level of parsing. | ||
* | ||
* @param {Buffer/string} data The incoming data. | ||
* @param {string?} enc The encoding. | ||
* @param {function} cb A callback for when it's done. | ||
*/ | ||
/** | ||
* Called automatically by the incoming stream, does the first level of parsing. | ||
* | ||
* @param {Buffer/string} data The incoming data. | ||
* @param {string?} enc The encoding. | ||
* @param {function} cb A callback for when it's done. | ||
*/ | ||
Parser.prototype._transform = function(data, enc, cb) { | ||
if (typeof data === 'string') { | ||
data = new Buffer(data); | ||
_transform(data, enc, cb) { | ||
if (typeof data === "string") { | ||
data = new Buffer(data); | ||
} | ||
var start = 0; | ||
var buf = data; | ||
let start = 0; | ||
let buf = data; | ||
// if previous buffered data exists, pick up where it left off | ||
if (this._prev) { | ||
start = this._prev.length; | ||
buf = Buffer.concat([this._prev, data]); | ||
this._prev = null; | ||
start = this._prev.length; | ||
buf = Buffer.concat([this._prev, data]); | ||
this._prev = null; | ||
} | ||
for (var i = start; i < buf.length; i++) { | ||
// auto-detect the line feed character | ||
if (this._first) { | ||
if (buf[i] === lf) { | ||
this._newline = lf; | ||
} | ||
else if (buf[i] === cr) { | ||
if (buf[i + 1] !== lf) { | ||
this._newline = cr; | ||
} | ||
} | ||
for (let i = start; i < buf.length; i++) { | ||
// auto-detect the line feed character | ||
if (this._first) { | ||
if (buf[i] === lf) { | ||
this._newline = lf; | ||
} else if (buf[i] === cr) { | ||
if (buf[i + 1] !== lf) { | ||
this._newline = cr; | ||
} | ||
} | ||
} | ||
// parse each line | ||
if (buf[i] === this._newline) { | ||
this._online(buf, this._prevEnd, i + 1); | ||
this._prevEnd = i + 1; | ||
} | ||
// parse each line | ||
if (buf[i] === this._newline) { | ||
this._online(buf, this._prevEnd, i + 1); | ||
this._prevEnd = i + 1; | ||
} | ||
} | ||
@@ -103,10 +93,10 @@ | ||
if (this._prevEnd === buf.length) { | ||
this._prevEnd = 0; | ||
return cb(); | ||
this._prevEnd = 0; | ||
return cb(); | ||
} | ||
if (buf.length - this._prevEnd < data.length) { | ||
this._prev = data; | ||
this._prevEnd -= (buf.length - data.length); | ||
return cb(); | ||
this._prev = data; | ||
this._prevEnd -= buf.length - data.length; | ||
return cb(); | ||
} | ||
@@ -116,53 +106,53 @@ | ||
cb(); | ||
}; | ||
} | ||
/** | ||
* Handles the stream closing. | ||
* | ||
* @param {function} cb A callback for when it's done. | ||
*/ | ||
/** | ||
* Handles the stream closing. | ||
* | ||
* @param {function} cb A callback for when it's done. | ||
*/ | ||
Parser.prototype._flush = function(cb) { | ||
_flush(cb) { | ||
if (this._quoting || !this._prev) { | ||
return cb(); | ||
return cb(); | ||
} | ||
this._online(this._prev, this._prevEnd, this._prev.length + 1); // plus since online -1s | ||
cb(); | ||
}; | ||
} | ||
/** | ||
* Parses each line. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the line. | ||
* @param {integer} end Index of the end of the line. | ||
*/ | ||
/** | ||
* Parses each line. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the line. | ||
* @param {integer} end Index of the end of the line. | ||
*/ | ||
Parser.prototype._online = function(buf, start, end) { | ||
_online(buf, start, end) { | ||
end--; // trim newline | ||
if (buf.length && buf[end - 1] === cr) { | ||
end--; | ||
end--; | ||
} | ||
var sep = this._separator, | ||
cells = [], | ||
inQuotes = false, | ||
offset = start; | ||
const sep = this._separator; | ||
let cells = []; | ||
let inQuotes = false; | ||
let offset = start; | ||
// parses each cell | ||
for (var i = start; i < end; i++) { | ||
if (buf[i] === quote) { // " | ||
if (i < end - 1 && buf[i + 1] === quote) { // "" | ||
i++; | ||
} | ||
else { | ||
inQuotes = !inQuotes; | ||
} | ||
continue; | ||
for (let i = start; i < end; i++) { | ||
if (buf[i] === quote) { | ||
// " | ||
if (i < end - 1 && buf[i + 1] === quote) { | ||
// "" | ||
i++; | ||
} else { | ||
inQuotes = !inQuotes; | ||
} | ||
if (buf[i] === sep && (!inQuotes || sep === fs)) { | ||
cells.push(this._oncell(buf, offset, i)); | ||
offset = i + 1; | ||
} | ||
continue; | ||
} | ||
if (buf[i] === sep && (!inQuotes || sep === fs)) { | ||
cells.push(this._oncell(buf, offset, i)); | ||
offset = i + 1; | ||
} | ||
} | ||
@@ -172,3 +162,3 @@ | ||
if (!this.version) { | ||
return; | ||
return; | ||
} | ||
@@ -178,18 +168,17 @@ | ||
if (this._first && cells.length === 0 && this._separator !== comma) { | ||
this._separator = comma; | ||
this._online(buf,start,end+1); | ||
return; | ||
this._separator = comma; | ||
this._online(buf, start, end + 1); | ||
return; | ||
} else if (this._first && cells.length === 0) { | ||
this.emit("error", new Error("Cannot parse header row")); | ||
this.version = null; | ||
return; | ||
} | ||
else if (this._first && cells.length === 0) { | ||
this.emit('error', new Error('Cannot parse header row')); | ||
this.version = null; | ||
return; | ||
} | ||
// take care of any trailing data on the line | ||
if (offset < end) { | ||
cells.push(this._oncell(buf, offset, end)); | ||
cells.push(this._oncell(buf, offset, end)); | ||
} | ||
if (buf[end - 1] === sep) { | ||
cells.push(this._empty); | ||
cells.push(this._empty); | ||
} | ||
@@ -199,73 +188,74 @@ | ||
if (this._first) { | ||
this._first = false; | ||
this.version = cells[2]; | ||
this._first = false; | ||
this.version = cells[2]; | ||
// try to detect whether this is an electronic filing, look in different location for version if not | ||
if (cells[1] !== 'FEC') { | ||
this.version = cells[1]; | ||
} | ||
// try to detect whether this is an electronic filing, look in different location for version if not | ||
if (cells[1] !== "FEC") { | ||
this.version = cells[1]; | ||
} | ||
} | ||
// handle form 99s | ||
if (cells[0] && cells[0].toLowerCase() == 'f99') { | ||
this._f99 = true; | ||
cells.push(''); | ||
this._cells = cells; | ||
return; | ||
if (cells[0] && cells[0].toLowerCase() == "f99") { | ||
this._f99 = true; | ||
cells.push(""); | ||
this._cells = cells; | ||
return; | ||
} | ||
if (this._f99) { | ||
var textIndex = this._cells.length-1; | ||
if (cells[0] && cells[0].toUpperCase() == '[BEGINTEXT]') { | ||
return; | ||
} | ||
else if (cells[0] && cells[0].toUpperCase() == '[ENDTEXT]') { | ||
cells = this._cells; | ||
cells[textIndex] = cells[textIndex].substr(0,cells[textIndex].length-1); | ||
this._f99 = false; | ||
} | ||
else { | ||
this._cells[textIndex] = this._cells[textIndex] | ||
.concat(this._onvalue(buf,start,end) + '\n'); | ||
return; | ||
} | ||
const textIndex = this._cells.length - 1; | ||
if (cells[0] && cells[0].toUpperCase() == "[BEGINTEXT]") { | ||
return; | ||
} else if (cells[0] && cells[0].toUpperCase() == "[ENDTEXT]") { | ||
cells = this._cells; | ||
cells[textIndex] = cells[textIndex].substr( | ||
0, | ||
cells[textIndex].length - 1 | ||
); | ||
this._f99 = false; | ||
} else { | ||
this._cells[textIndex] = this._cells[textIndex].concat( | ||
`${this._onvalue(buf, start, end)}\n` | ||
); | ||
return; | ||
} | ||
} | ||
// compile the Row class with field names for the form type and filing version | ||
var res = this._lookup(cells[0],this.version); | ||
const res = this._lookup(cells[0], this.version); | ||
// if there are more field names for the row than cells, add empties | ||
if (res.headers && cells.length !== res.headers.length) { | ||
for (var num = cells.length; num <= res.headers.length; num++) { | ||
cells.push(this._empty); | ||
} | ||
for (let num = cells.length; num <= res.headers.length; num++) { | ||
cells.push(this._empty); | ||
} | ||
} | ||
if (!this.map && res.headers) { | ||
this.push({row: cells, headers: res.headers }); | ||
return; | ||
this.push({ row: cells, headers: res.headers }); | ||
return; | ||
} else if (res._Row) { | ||
// send out the cells and the compiled Row class | ||
this._emit(res._Row, cells); | ||
} | ||
else if (res._Row) { | ||
// send out the cells and the compiled Row class | ||
this._emit(res._Row, cells); | ||
} | ||
}; | ||
} | ||
/** | ||
* Looks up the field names based on the form type and filing version. | ||
* | ||
* @param {string} type Form type, the first value on a line. | ||
* @param {string} version The filing version from the filing header. | ||
*/ | ||
/** | ||
* Looks up the field names based on the form type and filing version. | ||
* | ||
* @param {string} type Form type, the first value on a line. | ||
* @param {string} version The filing version from the filing header. | ||
*/ | ||
Parser.prototype._lookup = function(type,version) { | ||
var res = { | ||
_Row: null, | ||
headers: null | ||
_lookup(type, version) { | ||
const res = { | ||
_Row: null, | ||
headers: null, | ||
}; | ||
if (typeof type === 'undefined' || !type) { | ||
// errors appear to (eventually?) derail streams, so commenting out recoverable errors for now | ||
// this.emit('error', new Error('Row type was undefined')); | ||
return res; | ||
if (typeof type === "undefined" || !type) { | ||
// errors appear to (eventually?) derail streams, so commenting out recoverable errors for now | ||
// this.emit('error', new Error('Row type was undefined')); | ||
return res; | ||
} | ||
@@ -277,15 +267,14 @@ | ||
if (type in this._Rows && version in this._Rows[type]) { | ||
return this._Rows[type][this.version]; | ||
return this._Rows[type][this.version]; | ||
} else if (!(type in this._Rows)) { | ||
this._Rows[type] = {}; | ||
} | ||
else if (!(type in this._Rows)) { | ||
this._Rows[type] = {}; | ||
} | ||
// get the appropriate field names from the mappings | ||
res.headers = mappings.lookup(type,version); | ||
res.headers = mappings.lookup(type, version); | ||
if (!res.headers) { | ||
// errors appear to (eventually?) derail streams, so commenting out recoverable errors for now | ||
// this.emit('error', new Error('Couldn\'t find header mapping')); | ||
return res; | ||
// errors appear to (eventually?) derail streams, so commenting out recoverable errors for now | ||
// this.emit('error', new Error('Couldn\'t find header mapping')); | ||
return res; | ||
} | ||
@@ -299,48 +288,47 @@ | ||
return res; | ||
} | ||
}; | ||
/** | ||
* Generates a Row class to return. | ||
* | ||
* @param {array} headers The fields names for the row. | ||
*/ | ||
/** | ||
* Generates a Row class to return. | ||
* | ||
* @param {array} headers The fields names for the row. | ||
*/ | ||
Parser.prototype._compile = function(headers) { | ||
_compile(headers) { | ||
// generate a Row class on the fly | ||
var Row = genfun()('function Row (cells) {'); | ||
const Row = genfun()("function Row (cells) {"); | ||
headers.forEach(function(cell, i) { | ||
Row('%s = cells[%d]', genobj('this', cell), i); | ||
headers.forEach((cell, i) => { | ||
Row("%s = cells[%d]", genobj("this", cell), i); | ||
}); | ||
Row('}'); | ||
Row("}"); | ||
var _Row = Row.toFunction(); | ||
const _Row = Row.toFunction(); | ||
return _Row; | ||
}; | ||
} | ||
/** | ||
* Instantiates a new Row object with the cells and sends it to the pipe. | ||
* | ||
* @param {function} Row The Row class for this line. | ||
* @param {array} cells The data in parsed cells. | ||
*/ | ||
/** | ||
* Instantiates a new Row object with the cells and sends it to the pipe. | ||
* | ||
* @param {function} Row The Row class for this line. | ||
* @param {array} cells The data in parsed cells. | ||
*/ | ||
Parser.prototype._emit = function(Row, cells) { | ||
_emit(Row, cells) { | ||
this.push(new Row(cells)); | ||
}; | ||
} | ||
/** | ||
* Processes a single cell. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the cell. | ||
* @param {integer} end Index of the end of the cell. | ||
*/ | ||
/** | ||
* Processes a single cell. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the cell. | ||
* @param {integer} end Index of the end of the cell. | ||
*/ | ||
Parser.prototype._oncell = function(buf, start, end) { | ||
_oncell(buf, start, end) { | ||
if (start === end) { | ||
return this._empty; | ||
return this._empty; | ||
} | ||
@@ -350,4 +338,4 @@ | ||
if (buf[start] === quote && buf[end - 1] === quote) { | ||
start++; | ||
end--; | ||
start++; | ||
end--; | ||
} | ||
@@ -357,32 +345,32 @@ | ||
if (start === end) { | ||
return ''; | ||
return ""; | ||
} | ||
for (var i = start, y = start; i < end; i++) { | ||
if (buf[i] === quote && buf[i + 1] === quote) { // "" | ||
i++; | ||
} | ||
if (y !== i) { | ||
buf[y] = buf[i]; | ||
} | ||
y++; | ||
if (buf[i] === quote && buf[i + 1] === quote) { | ||
// "" | ||
i++; | ||
} | ||
if (y !== i) { | ||
buf[y] = buf[i]; | ||
} | ||
y++; | ||
} | ||
return this._onvalue(buf, start, y); | ||
}; | ||
} | ||
/** | ||
* Convert the buffered value to UTF-8. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the value. | ||
* @param {integer} end Index of the end of the value. | ||
*/ | ||
/** | ||
* Convert the buffered value to UTF-8. | ||
* | ||
* @param {Buffer} buf The buffered data. | ||
* @param {integer} start Index of the start of the value. | ||
* @param {integer} end Index of the end of the value. | ||
*/ | ||
Parser.prototype._onvalue = function(buf, start, end) { | ||
return buf.toString('utf8', start, end).replace('\ufffd',''); | ||
}; | ||
_onvalue(buf, start, end) { | ||
return buf.toString("utf8", start, end).replace("\ufffd", ""); | ||
} | ||
} | ||
module.exports = function(opts) { | ||
return new Parser(opts); | ||
}; | ||
export default (opts) => new Parser(opts); |
{ | ||
"name": "fec-parse", | ||
"version": "0.23.0", | ||
"version": "0.24.0", | ||
"description": "A Node module to parse raw FEC electronic filings.", | ||
@@ -10,2 +10,3 @@ "main": "index.js", | ||
"license": "(Apache-2.0 AND MIT)", | ||
"type": "module", | ||
"repository": { | ||
@@ -16,10 +17,10 @@ "type": "git", | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^19.0.0", | ||
"@rollup/plugin-json": "^4.1.0", | ||
"@rollup/plugin-node-resolve": "^13.0.0", | ||
"@stream-io/rollup-plugin-node-builtins": "^2.1.5", | ||
"chai": "^4.3.4", | ||
"mocha": "^9.0.1", | ||
"mocha": "^9.1.1", | ||
"rollup": "^2.52.2", | ||
"@rollup/plugin-commonjs": "^19.0.0", | ||
"@rollup/plugin-json": "^4.1.0", | ||
"rollup-plugin-node-globals": "^1.4.0", | ||
"@rollup/plugin-node-resolve": "^13.0.0" | ||
"rollup-plugin-node-globals": "^1.4.0" | ||
}, | ||
@@ -26,0 +27,0 @@ "scripts": { |
Sorry, the diff of this file is too big to display
1020591
Yes
27944