jtc-utils
Version:
Utilities for Japanese Traditional Companies
316 lines (315 loc) • 12.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FixlenWriter = void 0;
const utf8_cjs_1 = require("./charset/utf8.cjs");
class FixlenWriter {
writer;
encoder;
ebcdic;
shift;
filler;
lineSeparator;
columns;
lineLength;
bom;
fatal;
index = 0;
constructor(dest, config) {
const charset = config.charset ?? utf8_cjs_1.utf8;
this.encoder = charset.createEncoder();
this.ebcdic = charset.isEbcdic();
this.bom = charset.isUnicode() ? (config.bom ?? false) : false;
this.shift = config.shift ?? false;
this.filler = this.encoder.encode(config.filler || " ", {
shift: this.shift,
});
if (config.lineSeparator) {
this.lineSeparator = this.encoder.encode(config.lineSeparator, {
shift: this.shift,
});
}
this.fatal = config.fatal ?? true;
this.lineLength = 0;
this.columns = [];
for (let pos = 0; pos < config.columns.length; pos++) {
const col = config.columns[pos];
if (col.length <= 0) {
throw new RangeError(`column length must be positive: ${col.length}`);
}
this.lineLength += col.length;
const colShift = col.shift ?? this.shift;
this.columns.push({
length: col.length,
shift: colShift,
fill: col.fill,
fillerBytes: col.filler
? this.encoder.encode(col.filler, { shift: colShift })
: this.filler,
type: col.type,
});
}
if (this.lineSeparator) {
this.lineLength += this.lineSeparator.length;
}
let stream;
if (dest instanceof WritableStream) {
stream = Promise.resolve(dest);
}
else {
const writable = "createWriteStream" in dest ? dest.createWriteStream() : dest;
if ("constructor" in writable &&
"toWeb" in writable.constructor &&
typeof writable.constructor.toWeb === "function") {
stream = Promise.resolve(writable.constructor.toWeb(writable));
}
else {
throw new TypeError(`Unsuppoted destination: ${dest}`);
}
}
this.writer = stream.then((value) => value.getWriter());
}
async write(record, options) {
let shift = this.shift;
let filler = this.filler;
let lineSeparator = this.lineSeparator;
let columns = this.columns;
let lineLength = this.lineLength;
if (options?.shift != null) {
shift = options.shift;
}
if (options?.filler != null) {
filler = this.encoder.encode(options.filler, { shift });
}
if (options?.lineSeparator != null) {
lineSeparator = this.encoder.encode(options.lineSeparator);
}
if (options?.columns) {
columns = [];
lineLength = 0;
for (let pos = 0; pos < options.columns.length; pos++) {
const col = options.columns[pos];
if (col.length <= 0) {
throw new RangeError(`column length must be positive: ${col.length}`);
}
lineLength += col.length;
const colShift = col.shift ?? this.shift;
columns.push({
length: col.length,
shift: col.shift ?? false,
fill: col.fill,
fillerBytes: col.filler
? this.encoder.encode(col.filler, { shift: colShift })
: filler,
type: col.type,
});
}
if (lineSeparator) {
lineLength += lineSeparator.length;
}
}
let buf;
let start = 0;
if (this.bom) {
const bomBytes = this.encoder.encode("\uFEFF");
buf = new Uint8Array(bomBytes.length + lineLength);
buf.set(bomBytes, 0);
start += bomBytes.length;
this.bom = false;
}
else {
buf = new Uint8Array(lineLength);
}
for (let pos = 0; pos < columns.length; pos++) {
const col = columns[pos];
const value = record[pos];
const isNumber = typeof value === "number" && Number.isFinite(value);
const type = isNumber ? col.type : undefined;
const fill = col.fill ?? (isNumber ? "right" : "left");
if (!type) {
const text = value != null ? value.toString() : "";
let encoded = this.encoder.encode(text, { shift: col.shift });
if (encoded.length > col.length) {
if (this.fatal) {
throw new RangeError("overflow error");
}
encoded = this.encoder.encode(text, {
shift: col.shift,
limit: col.length,
});
}
if (encoded.length === col.length) {
buf.set(encoded, start);
}
else {
if ((col.length - encoded.length) % col.fillerBytes.length !== 0) {
throw new RangeError(`filler length is mismatched: ${col.fillerBytes.length}`);
}
if (fill === "right") {
for (let i = 0; i < col.length - encoded.length; i += col.fillerBytes.length) {
buf.set(col.fillerBytes, start + i);
}
buf.set(encoded, col.length - encoded.length);
}
else {
for (let i = 0; i < col.length - encoded.length; i += col.fillerBytes.length) {
buf.set(col.fillerBytes, start + encoded.length + i);
}
buf.set(encoded, start);
}
}
}
else if (!Number.isInteger(value)) {
if (this.fatal) {
throw new RangeError(`value must be integer: ${value}`);
}
else {
buf.fill(0, start, start + col.length);
}
}
else if (type === "zerofill") {
const num = value;
const encoded = new Uint8Array(col.length);
let pos = 0;
const sign = Math.sign(num) < 0;
const text = Math.abs(num).toFixed();
if (sign) {
const minus = this.encoder.encode("-", { shift: col.shift });
encoded.set(minus, pos);
pos += minus.length;
}
const content = this.encoder.encode(text, { shift: col.shift });
if (pos + content.length < col.length) {
const zero = this.encoder.encode("0", { shift: col.shift });
for (; pos < col.length - content.length; pos += zero.length) {
encoded.set(zero, pos);
}
}
if (pos + content.length === col.length) {
encoded.set(content, col.length - content.length);
buf.set(encoded, start);
}
else if (this.fatal) {
throw new RangeError(`overflow error: ${value}`);
}
else {
buf.fill(0, start, start + col.length);
}
}
else if (type.startsWith("int-")) {
const num = value;
const view = new DataView(buf.buffer);
const littleEndien = type === "int-le";
if (col.length === 4) {
view.setInt32(start, num, littleEndien);
}
else if (col.length === 2) {
view.setInt16(start, num, littleEndien);
}
else if (col.length === 1) {
view.setInt8(start, num);
}
else if (this.fatal) {
throw new RangeError("byte length must be 1, 2 or 4.");
}
else {
buf.fill(0, start, start + col.length);
}
}
else if (type.startsWith("uint-")) {
const num = value;
const view = new DataView(buf.buffer);
const littleEndien = type === "uint-le";
if (col.length === 4) {
view.setUint32(start, num, littleEndien);
}
else if (col.length === 2) {
view.setUint16(start, num, littleEndien);
}
else if (col.length === 1) {
view.setUint8(start, num);
}
else if (this.fatal) {
throw new RangeError("length must be 1, 2 or 4.");
}
else {
buf.fill(0, start, start + col.length);
}
}
else if (type === "zoned") {
const num = value;
const sign = Math.sign(num) < 0;
const text = Math.abs(num).toFixed();
if (this.fatal && col.length < text.length) {
throw new RangeError(`length is too short: ${col.length}`);
}
for (let i = 0; i < col.length; i++) {
let n = i < col.length - text.length
? 0
: text.charCodeAt(i - (col.length - text.length)) - 0x30;
if (n > 0xa) {
if (this.fatal) {
throw new RangeError(`Invalid value: ${n}`);
}
else {
n = 0;
}
}
if (i === col.length - 1) {
buf[start + i] = (sign ? 0xd0 : 0xc0) | n;
}
else {
buf[start + i] = (this.ebcdic ? 0xf0 : 0x30) | n;
}
}
}
else if (type === "packed") {
const num = value;
const sign = Math.sign(num) < 0;
const text = Math.abs(num).toFixed();
if (this.fatal && col.length < Math.ceil((text.length + 1) / 2)) {
throw new RangeError(`length is too short: ${col.length}`);
}
buf[start + col.length - 1] = sign ? 0x0d : 0x0c;
for (let i = 0; i < text.length; i++) {
let n = text.charCodeAt(text.length - 1 - i) - 0x30;
if (n > 0xa) {
if (this.fatal) {
throw new RangeError(`Invalid value: ${n}`);
}
else {
n = 0;
}
}
if (i % 2 === 1) {
buf[start + col.length - 1 - ((i + 1) >>> 1)] = n;
}
else {
buf[start + col.length - 1 - (i >>> 1)] |= n << 4;
}
}
}
else {
throw new RangeError(`unknown column type: ${type}`);
}
start += col.length;
}
if (lineSeparator) {
buf.set(lineSeparator, buf.length - lineSeparator.length);
}
this.index++;
const writer = await this.writer;
await writer.write(buf);
}
get count() {
return this.index;
}
async close() {
const writer = await this.writer;
if (this.bom) {
await writer.write(this.encoder.encode("\uFEFF"));
this.bom = false;
}
await writer.close();
}
}
exports.FixlenWriter = FixlenWriter;