Skip to content

Commit

Permalink
Add encoding/csv (denoland/std#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
zekth authored and ry committed May 24, 2019
1 parent 31db7c4 commit aed65ff
Show file tree
Hide file tree
Showing 3 changed files with 612 additions and 0 deletions.
151 changes: 151 additions & 0 deletions encoding/csv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Ported from Go:
// https://github.com/golang/go/blob/go1.12.5/src/encoding/csv/
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { BufReader, BufState } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";

const INVALID_RUNE = ["\r", "\n", '"'];

export class ParseError extends Error {
StartLine: number;
Line: number;
constructor(start: number, line: number, message: string) {
super(message);
this.StartLine = start;
this.Line = line;
}
}

export interface ParseOptions {
comma: string;
comment?: string;
trimLeadingSpace: boolean;
lazyQuotes?: boolean;
fieldsPerRecord?: number;
}

function chkOptions(opt: ParseOptions): Error | null {
if (
INVALID_RUNE.includes(opt.comma) ||
INVALID_RUNE.includes(opt.comment) ||
opt.comma === opt.comment
) {
return Error("Invalid Delimiter");
}
return null;
}

export async function read(
Startline: number,
reader: BufReader,
opt: ParseOptions = { comma: ",", comment: "#", trimLeadingSpace: false }
): Promise<[string[], BufState]> {
const tp = new TextProtoReader(reader);
let err: BufState;
let line: string;
let result: string[] = [];
let lineIndex = Startline;

[line, err] = await tp.readLine();

// Normalize \r\n to \n on all input lines.
if (
line.length >= 2 &&
line[line.length - 2] === "\r" &&
line[line.length - 1] === "\n"
) {
line = line.substring(0, line.length - 2);
line = line + "\n";
}

const trimmedLine = line.trimLeft();
if (trimmedLine.length === 0) {
return [[], err];
}

// line starting with comment character is ignored
if (opt.comment && trimmedLine[0] === opt.comment) {
return [result, err];
}

result = line.split(opt.comma);

let quoteError = false;
result = result.map(
(r): string => {
if (opt.trimLeadingSpace) {
r = r.trimLeft();
}
if (r[0] === '"' && r[r.length - 1] === '"') {
r = r.substring(1, r.length - 1);
} else if (r[0] === '"') {
r = r.substring(1, r.length);
}

if (!opt.lazyQuotes) {
if (r[0] !== '"' && r.indexOf('"') !== -1) {
quoteError = true;
}
}
return r;
}
);
if (quoteError) {
return [
[],
new ParseError(Startline, lineIndex, 'bare " in non-quoted-field')
];
}
return [result, err];
}

export async function readAll(
reader: BufReader,
opt: ParseOptions = {
comma: ",",
trimLeadingSpace: false,
lazyQuotes: false
}
): Promise<[string[][], BufState]> {
const result: string[][] = [];
let _nbFields: number;
let err: BufState;
let lineResult: string[];
let first = true;
let lineIndex = 0;
err = chkOptions(opt);
if (err) return [result, err];

for (;;) {
[lineResult, err] = await read(lineIndex, reader, opt);
if (err) break;
lineIndex++;
// If fieldsPerRecord is 0, Read sets it to
// the number of fields in the first record
if (first) {
first = false;
if (opt.fieldsPerRecord !== undefined) {
if (opt.fieldsPerRecord === 0) {
_nbFields = lineResult.length;
} else {
_nbFields = opt.fieldsPerRecord;
}
}
}

if (lineResult.length > 0) {
if (_nbFields && _nbFields !== lineResult.length) {
return [
null,
new ParseError(lineIndex, lineIndex, "wrong number of fields")
];
}
result.push(lineResult);
}
}
if (err !== "EOF") {
return [result, err];
}
return [result, null];
}
Loading

0 comments on commit aed65ff

Please sign in to comment.