@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
101 lines (89 loc) • 3.37 kB
JavaScript
const { createReadStream, createWriteStream, promises: fsp } = require('fs')
const { Readable } = require('stream')
const cds = require('..')
const SEPARATOR = /[,;\t]/
exports.parse = cds.parse.csv
exports.serialize = function (rows, columns, bom = '\ufeff') {
let csv = bom + (columns || Object.keys(rows[0])).join(';') + "\n"
for (let key in rows) csv += `${key};${rows[key]}\r\n`
return csv
}
exports.readHeader = async function (inStream, o = { ignoreComments: true }) {
let delimiter = ';'
let cols = []
let filtered = false
await _filterLines({ delimiter }, inStream, null, (line, readLine) => {
if (!cols.length) {
if (o.ignoreComments && _ignoreLine(line)) {
filtered = true
return false
}
[delimiter] = SEPARATOR.exec(line) || [';']
cols = line.split(delimiter).map(each => each.trim()).filter(each => each.length)
readLine.close() // signal that we have seen enough --> this only ends the readLine interface
}
return true
})
inStream.destroy() // destroy the stream to avoid leaks of file descriptors
return { cols, delimiter, filtered }
}
exports.stripComments = async function (file, outStream, trimWhitespaces = false) {
const { delimiter } = await exports.readHeader(createReadStream(file))
// buffer whole content so that we can write the out file
const inStream = Readable.from([await fsp.readFile(file)])
// clears the output file
outStream = outStream || createWriteStream(file)
let prelude = true
await _filterLines({ delimiter, trimWhitespaces }, inStream, outStream, line => {
if (prelude) {
if (_ignoreLine(line)) return false
prelude = false
}
// skip empty lines - HANA cannot handle them, e.g. at end of the file
return line.trim().length > 0
})
return true
}
function _ignoreLine(line) {
return line[0] === '#' || !line.trim().length
}
function _filterLines({ delimiter, trimWhitespaces }, input, out, filter) {
return new Promise((resolve, reject) => {
const rl = require('readline').createInterface({ input, crlfDelay: Infinity })
const resumeOnDrain = () => rl.resume()
let filtered = false
rl.on('line', line => {
if (filter(line, rl)) {
// Process the line character by character to handle quoted fields properly
if (trimWhitespaces) {
const result = []
let field = ''
let quoted = false
for (const char of line) {
if (char === '"') {
quoted = !quoted
field += char
} else if (char === delimiter && !quoted) {
result.push(field.startsWith('"') ? field : field.trim())
field = ''
} else {
field += char
}
}
// Don't forget the last field
result.push(field.startsWith('"') ? field : field.trim())
line = result.join(delimiter)
}
if (out && !out.write(line + '\n')) {
rl.pause() // pause when writable signals so
out.removeListener('drain', resumeOnDrain) // avoid too many listeners
out.once('drain', resumeOnDrain)
}
}
else filtered |= true
})
rl.on('error', reject)
rl.on('close', () => out ? out.end() : resolve(filtered))
if (out) out.on('finish', () => resolve(filtered))
})
}