mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
0d460ac2d4
Summary: Added ChoiceListParser capable of parsing JSON, CSVs, and other strings containing user-configured choices (e.g. separated by spaces) I got a little carried away here. It works, and I can't think of any bugs, but it's complicated enough that there could be hidden edge cases or difficulties maintaining it in the future. The advantage of the current method is that it should work well for ambiguous or poorly formatted inputs, e.g. choices separated only by spaces or choices containing commas which are not escaped/quoted properly. The code can be vastly simplified if we don't try to support that and require that users paste proper JSON or CSVs. Test Plan: Added a new file test/common/ChoiceListParser.ts with pure unit tests. Waiting for approval of the overall approach before adding to the nbrowser CopyPaste test. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D3141
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
import {csvDecodeRow} from 'app/common/csvFormat';
|
|
import {DocumentSettings} from 'app/common/DocumentSettings';
|
|
import * as gristTypes from 'app/common/gristTypes';
|
|
import * as gutil from 'app/common/gutil';
|
|
import {safeJsonParse} from 'app/common/gutil';
|
|
import {getCurrency, NumberFormatOptions} from 'app/common/NumberFormat';
|
|
import NumberParse from 'app/common/NumberParse';
|
|
import {parseDateStrict} from 'app/common/parseDate';
|
|
import {DateFormatOptions, DateTimeFormatOptions, formatDecoded, FormatOptions} from 'app/common/ValueFormatter';
|
|
import flatMap = require('lodash/flatMap');
|
|
|
|
|
|
export class ValueParser {
|
|
constructor(public type: string, public widgetOpts: object, public docSettings: DocumentSettings) {
|
|
}
|
|
|
|
public cleanParse(value: string): any {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
return this.parse(value) ?? value;
|
|
}
|
|
|
|
public parse(value: string): any {
|
|
return value;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
export class NumericParser extends ValueParser {
|
|
private _parse: NumberParse;
|
|
|
|
constructor(type: string, options: NumberFormatOptions, docSettings: DocumentSettings) {
|
|
super(type, options, docSettings);
|
|
this._parse = new NumberParse(docSettings.locale, getCurrency(options, docSettings));
|
|
}
|
|
|
|
public parse(value: string): number | null {
|
|
return this._parse.parse(value);
|
|
}
|
|
}
|
|
|
|
class DateParser extends ValueParser {
|
|
public parse(value: string): any {
|
|
return parseDateStrict(value, (this.widgetOpts as DateFormatOptions).dateFormat!);
|
|
}
|
|
}
|
|
|
|
class DateTimeParser extends DateParser {
|
|
constructor(type: string, widgetOpts: DateTimeFormatOptions, docSettings: DocumentSettings) {
|
|
super(type, widgetOpts, docSettings);
|
|
const timezone = gutil.removePrefix(type, "DateTime:") || '';
|
|
this.widgetOpts = {...widgetOpts, timezone};
|
|
}
|
|
}
|
|
|
|
|
|
class ChoiceListParser extends ValueParser {
|
|
public cleanParse(value: string): string[] | null {
|
|
value = value.trim();
|
|
const result = (
|
|
this._parseJson(value) ||
|
|
this._parseCsv(value)
|
|
).map(v => v.trim())
|
|
.filter(v => v);
|
|
if (!result.length) {
|
|
return null;
|
|
}
|
|
return ["L", ...result];
|
|
}
|
|
|
|
private _parseJson(value: string): string[] | undefined {
|
|
// Don't parse JSON non-arrays
|
|
if (value[0] === "[") {
|
|
const arr: unknown[] | null = safeJsonParse(value, null);
|
|
return arr
|
|
// Remove nulls and empty strings
|
|
?.filter(v => v || v === 0)
|
|
// Convert values to strings, formatting nested JSON objects/arrays as JSON
|
|
.map(v => formatDecoded(v));
|
|
}
|
|
}
|
|
|
|
private _parseCsv(value: string): string[] {
|
|
// Split everything on newlines which are not allowed by the choice editor.
|
|
return flatMap(value.split(/[\n\r]+/), row => {
|
|
return csvDecodeRow(row)
|
|
.map(v => v.trim());
|
|
});
|
|
}
|
|
}
|
|
|
|
const parsers: { [type: string]: typeof ValueParser } = {
|
|
Numeric: NumericParser,
|
|
Int: NumericParser,
|
|
Date: DateParser,
|
|
DateTime: DateTimeParser,
|
|
ChoiceList: ChoiceListParser,
|
|
};
|
|
|
|
// TODO these are not ready yet
|
|
delete parsers.DateTime;
|
|
|
|
export function createParser(
|
|
type: string, widgetOpts: FormatOptions, docSettings: DocumentSettings
|
|
): (value: string) => any {
|
|
const cls = parsers[gristTypes.extractTypeFromColType(type)];
|
|
if (cls) {
|
|
const parser = new cls(type, widgetOpts, docSettings);
|
|
return parser.cleanParse.bind(parser);
|
|
}
|
|
return value => value;
|
|
}
|