diff --git a/plugins/dbgate-plugin-csv/src/backend/writer.js b/plugins/dbgate-plugin-csv/src/backend/writer.js index dd6dd63f2..7e65cb6ae 100644 --- a/plugins/dbgate-plugin-csv/src/backend/writer.js +++ b/plugins/dbgate-plugin-csv/src/backend/writer.js @@ -6,6 +6,54 @@ const { getLogger } = global.DBGATE_PACKAGES['dbgate-tools']; const logger = getLogger('csvWriter'); +class RecodeTransform extends stream.Transform { + constructor(toEncoding = 'utf8') { + super({ readableObjectMode: false, writableObjectMode: false }); + this.to = String(toEncoding).toLowerCase(); + this.decoder = new (global.TextDecoder || require('util').TextDecoder)('utf-8', { fatal: false }); + } + + _encodeString(str) { + if (this.to === 'utf8' || this.to === 'utf-8') { + return Buffer.from(str, 'utf8'); + } + if (this.to === 'utf16le' || this.to === 'ucs2' || this.to === 'utf-16le') { + return Buffer.from(str, 'utf16le'); + } + if (this.to === 'utf16be' || this.to === 'utf-16be') { + const le = Buffer.from(str, 'utf16le'); + for (let i = 0; i + 1 < le.length; i += 2) { + const a = le[i]; + le[i] = le[i + 1]; + le[i + 1] = a; + } + return le; + } + throw new Error(`Unsupported target encoding: ${this.to}`); + } + + _transform(chunk, enc, cb) { + try { + if (!Buffer.isBuffer(chunk)) chunk = Buffer.from(chunk, enc); + const part = this.decoder.decode(chunk, { stream: true }); + if (part.length) this.push(this._encodeString(part)); + cb(); + } catch (e) { + cb(e); + } + } + + _flush(cb) { + try { + const rest = this.decoder.decode(); + if (rest.length) this.push(this._encodeString(rest)); + cb(); + } catch (e) { + cb(e); + } + } +} + class CsvPrepareStream extends stream.Transform { constructor({ header }) { super({ objectMode: true }); @@ -26,18 +74,59 @@ class CsvPrepareStream extends stream.Transform { } } -async function writer({ fileName, encoding = 'utf-8', header = true, delimiter, quoted }) { +async function writer({ + fileName, + encoding = 'utf-8', + header = true, + delimiter, + quoted, + writeBom, + writeSepHeader, + recordDelimiter, +}) { logger.info(`DBGM-00133 Writing file ${fileName}`); const csvPrepare = new CsvPrepareStream({ header }); - const csvStream = csv.stringify({ delimiter, quoted }); + const csvStream = csv.stringify({ delimiter, quoted, record_delimiter: recordDelimiter }); const fileStream = fs.createWriteStream(fileName, encoding); - // csvPrepare.pipe(csvStream); - // csvStream.pipe(fileStream); - // csvPrepare['finisher'] = fileStream; + if (writeBom) { + switch (encoding.toLowerCase()) { + case 'utf-8': + case 'utf8': + fileStream.write(Buffer.from([0xef, 0xbb, 0xbf])); + break; + case 'utf-16': + case 'utf16': + fileStream.write(Buffer.from([0xff, 0xfe])); + break; + case 'utf-16le': + case 'utf16le': + fileStream.write(Buffer.from([0xff, 0xfe])); + break; + case 'utf-16be': + case 'utf16be': + fileStream.write(Buffer.from([0xfe, 0xff])); + break; + case 'utf-32le': + case 'utf32le': + fileStream.write(Buffer.from([0xff, 0xfe, 0x00, 0x00])); + break; + case 'utf-32be': + case 'utf32be': + fileStream.write(Buffer.from([0x00, 0x00, 0xfe, 0xff])); + break; + default: + break; + } + } + if (writeSepHeader) { + fileStream.write(`sep=${delimiter}${recordDelimiter || '\n'}`); + } + csvPrepare.requireFixedStructure = true; - return [csvPrepare, csvStream, fileStream]; - // return csvPrepare; + return encoding.toLowerCase() === 'utf8' || encoding.toLowerCase() === 'utf-8' + ? [csvPrepare, csvStream, fileStream] + : [csvPrepare, csvStream, new RecodeTransform(encoding), fileStream]; } module.exports = writer; diff --git a/plugins/dbgate-plugin-csv/src/frontend/index.js b/plugins/dbgate-plugin-csv/src/frontend/index.js index ef43d8ded..e0616449b 100644 --- a/plugins/dbgate-plugin-csv/src/frontend/index.js +++ b/plugins/dbgate-plugin-csv/src/frontend/index.js @@ -68,5 +68,31 @@ export default { }, }), }, + { + label: 'CSV file for MS Excel', + extension: 'csv', + createWriter: (fileName) => ({ + functionName: 'writer@dbgate-plugin-csv', + props: { + fileName, + delimiter: ';', + recordDelimiter: '\r\n', + encoding: 'utf16le', + writeSepHeader: true, + writeBom: true, + }, + }), + }, + { + label: 'TSV file (tab separated)', + extension: 'tsv', + createWriter: (fileName) => ({ + functionName: 'writer@dbgate-plugin-csv', + props: { + fileName, + delimiter: '\t', + }, + }), + }, ], };