-
Notifications
You must be signed in to change notification settings - Fork 217
Expand file tree
/
Copy pathQuotedColumnParser.ts
More file actions
109 lines (102 loc) · 4.9 KB
/
QuotedColumnParser.ts
File metadata and controls
109 lines (102 loc) · 4.9 KB
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
import { ColumnFormatter } from './ColumnFormatter';
import { ParserOptions } from '../../ParserOptions';
import { Scanner } from '../Scanner';
import { Token } from '../Token';
interface DataBetweenQuotes {
foundClosingQuote: boolean;
col: string;
}
export class QuotedColumnParser {
private readonly parserOptions: ParserOptions;
private readonly columnFormatter: ColumnFormatter;
public constructor(parserOptions: ParserOptions) {
this.parserOptions = parserOptions;
this.columnFormatter = new ColumnFormatter(parserOptions);
}
public parse(scanner: Scanner): string | null {
if (!scanner.hasMoreCharacters) {
return null;
}
const originalCursor = scanner.cursor;
const { foundClosingQuote, col } = this.gatherDataBetweenQuotes(scanner);
if (!foundClosingQuote) {
// reset the cursor to the original
scanner.advanceTo(originalCursor);
// if we didnt find a closing quote but we potentially have more data then skip the parsing
// and return the original scanner.
if (!scanner.hasMoreData) {
throw new Error(
`Parse Error: missing closing: '${
this.parserOptions.quote || ''
}' in line: at '${scanner.lineFromCursor.replace(/[\r\n]/g, "\\n'")}'`,
);
}
return null;
}
this.checkForMalformedColumn(scanner);
return col;
}
private gatherDataBetweenQuotes(scanner: Scanner): DataBetweenQuotes {
const { parserOptions } = this;
let foundStartingQuote = false;
let foundClosingQuote = false;
const characters = [];
let nextToken: Token | null = scanner.nextCharacterToken;
for (; !foundClosingQuote && nextToken !== null; nextToken = scanner.nextCharacterToken) {
const isQuote = Token.isTokenQuote(nextToken, parserOptions);
// ignore first quote
if (!foundStartingQuote && isQuote) {
foundStartingQuote = true;
} else if (foundStartingQuote) {
if (Token.isTokenEscapeCharacter(nextToken, parserOptions)) {
// advance past the escape character so we can get the next one in line
scanner.advancePastToken(nextToken);
const tokenFollowingEscape = scanner.nextCharacterToken;
// if the character following the escape is a quote character then just add
// the quote and advance to that character
if (
tokenFollowingEscape !== null &&
(Token.isTokenQuote(tokenFollowingEscape, parserOptions) ||
Token.isTokenEscapeCharacter(tokenFollowingEscape, parserOptions))
) {
characters.push(tokenFollowingEscape.token);
nextToken = tokenFollowingEscape;
} else if (isQuote) {
// if the escape is also a quote then we found our closing quote and finish early
foundClosingQuote = true;
} else {
// other wise add the escape token to the characters since it wast escaping anything
characters.push(nextToken.token);
}
} else if (isQuote) {
// we found our closing quote!
foundClosingQuote = true;
} else {
// add the token to the characters
characters.push(nextToken.token);
}
}
scanner.advancePastToken(nextToken);
}
return { col: this.columnFormatter.format(characters.join('')), foundClosingQuote };
}
private checkForMalformedColumn(scanner: Scanner): void {
const { parserOptions } = this;
const { nextNonSpaceToken } = scanner;
if (nextNonSpaceToken) {
const isNextTokenADelimiter = Token.isTokenDelimiter(nextNonSpaceToken, parserOptions);
const isNextTokenARowDelimiter = Token.isTokenRowDelimiter(nextNonSpaceToken);
if (!(isNextTokenADelimiter || isNextTokenARowDelimiter)) {
// if the final quote was NOT followed by a column (,) or row(\n) delimiter then its a bad column
// tldr: only part of the column was quoted
const linePreview = scanner.lineFromCursor.substr(0, 10).replace(/[\r\n]/g, "\\n'");
throw new Error(
`Parse Error: expected: '${parserOptions.escapedDelimiter.join(',')}' OR new line got: '${nextNonSpaceToken.token}'. at '${linePreview}'`,
);
}
scanner.advanceToToken(nextNonSpaceToken);
} else if (!scanner.hasMoreData) {
scanner.advancePastLine();
}
}
}