Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 12x 3x 12x 7x 9x 1x 1x 1x 1x 12x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 4x 4x 4x 6x 6x 6x 6x 6x 6x 14x 8x 8x 8x 14x 4x 4x 4x 4x 14x 6x 4x 4x 4x 4x 4x 4x 2x 2x 2x 2x 2x 2x 2x 2x 7x 7x 7x 7x 7x 11x 11x 11x 11x 11x 11x 18x 15x 15x 15x 18x 7x 18x 8x 8x 18x 11x 7x 7x 6x 6x 6x 7x 2x 2x 2x 2x 2x 2x 2x 2x 2x 33x 33x 33x 33x 33x 33x 374x 374x 374x 374x 2x 2x 374x 4x 372x 39x 39x 368x 329x 329x 374x 33x 33x 33x 33x | /**
* Parses CSV from a string or iterables of strings.
* @overload
* @param {string | Iterable<string>} input - CSV source.
* @param {string} [delimiter] - CSV delimiter.
* @returns {Generator<{[field: string]: string}>} generator that yields a parsed CSV row
* @example
* for (const row of parseCSV("name,age\nJohn,30\nJane,25")) {
* console.log(row);
* }
*
* @example
* const chunkArray = ['name,age\n', 'John,30'];
* for (const row of parseCSV(chunkArray)) {
* console.log(row);
* }
*/
/**
* Parses CSV from async string iterables.
*
* @example
* async function* chunks() {
* yield 'name,age\nJo';
* yield 'hn,30\n';
* }
* for await (const row of parseCSV(chunks())) {
* console.log(row);
* }
* @overload
* @param {AsyncIterable<string>} input - CSV source.
* @param {string} [delimiter] - CSV delimiter.
* @returns {AsyncGenerator<{[field: string]: string}>} generator that yields a parsed CSV row
*/
/**
* @param {string | Iterable<string> | AsyncIterable<string>} input - CSV source.
* @param {string} [delimiter] - CSV delimiter.
* @returns {Generator<{[field: string]: string}> | AsyncGenerator<{[field: string]: string}>} generator that yields a parsed CSV row
*/
export function parseCSV (input, delimiter = ',') {
if (typeof input === 'string') {
return parseCSVSync([input], delimiter)
} else if (isAsyncIterable(input)) {
return parseCSVAsync(input, delimiter)
} else if (isIterable(input)) {
return parseCSVSync(input, delimiter)
} else {
throw new TypeError('Input must be a string, Iterable, or AsyncIterable')
}
}
/**
* @param {*} object - target object
* @returns {object is Iterable<*>} - true if iterable, false otherwise
*/
const isIterable = (object) => typeof object?.[Symbol.iterator] === 'function'
/**
* @param {*} object - target object
* @returns {object is AsyncIterable<*>} - true if iterable, false otherwise
*/
const isAsyncIterable = (object) => typeof object?.[Symbol.asyncIterator] === 'function'
/**
* Parses CSV from a synchronous iterable of strings.
*
* @param {Iterable<string>} iterable - Iterable of string chunks.
* @param {string} [delimiter] - CSV delimiter.
* @yields {{[field: string]: string}} parsed CSV row
*/
function * parseCSVSync (iterable, delimiter = ',') {
let buffer = ''
let headers = null
for (const chunk of iterable) {
buffer += chunk.toString()
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() ?? ''// keep last partial line
for (const line of lines) {
if (!line.trim()) { continue }
const fields = parseCSVLine(line, delimiter)
if (!headers) {
headers = fields
} else {
yield Object.fromEntries(headers.map((h, i) => [h, fields[i] ?? '']))
}
}
}
if (buffer.trim() && headers) {
const fields = parseCSVLine(buffer, delimiter)
yield Object.fromEntries(headers.map((h, i) => [h, fields[i] ?? '']))
}
}
/**
* Parses CSV from an async iterable of string chunks.
*
* @param {AsyncIterable<string>} asyncIterable - Async iterable of chunks.
* @param {string} [delimiter] - CSV delimiter.
* @yields {{[field: string]: string}} parsed CSV row
*/
async function * parseCSVAsync (asyncIterable, delimiter = ',') {
let buffer = ''
let headers = null
for await (const chunk of asyncIterable) {
buffer += chunk.toString()
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() ?? ''// keep last partial line
for (const line of lines) {
if (!line.trim()) { continue }
const fields = parseCSVLine(line, delimiter)
if (!headers) {
headers = fields
} else {
yield Object.fromEntries(headers.map((h, i) => [h, fields[i] ?? '']))
}
}
}
if (buffer.trim() && headers) {
const fields = parseCSVLine(buffer, delimiter)
yield Object.fromEntries(headers.map((h, i) => [h, fields[i] ?? '']))
}
}
/**
* Parses a single CSV line into an array of fields.
* Handles quoted fields and escaped quotes.
*
* @param {string} line - A single line of CSV text.
* @param {string} [delimiter] - The delimiter character (default is comma).
* @returns {string[]} - Array of parsed fields.
*/
function parseCSVLine (line, delimiter = ',') {
const result = []
let current = ''
let inQuotes = false
for (let i = 0; i < line.length; i++) {
const char = line[i]
const nextChar = line[i + 1]
if (char === '"' && inQuotes && nextChar === '"') {
current += '"'
i++ // skip next quote
} else if (char === '"') {
inQuotes = !inQuotes
} else if (char === delimiter && !inQuotes) {
result.push(current)
current = ''
} else {
current += char
}
}
result.push(current)
return result
}
|