feat(functions): add text functions
This commit is contained in:
1 parent
7c2df12a72
commit
6442c8f575
1 file changed
+230
@@ -0,0 +1,230 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import '../evaluator.dart';
|
||||||
|
import '../format.dart';
|
||||||
|
import '../values.dart';
|
||||||
|
import 'helpers.dart';
|
||||||
|
|
||||||
|
void registerText(Map<String, CellFunction> registry) {
|
||||||
|
registry['CONCAT'] = (args, ctx) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (final value in ctx.flatten(args)) {
|
||||||
|
buffer.write(ctx.asText(value));
|
||||||
|
}
|
||||||
|
return TextValue(buffer.toString());
|
||||||
|
};
|
||||||
|
registry['CONCATENATE'] = registry['CONCAT']!;
|
||||||
|
|
||||||
|
registry['TEXTJOIN'] = (args, ctx) {
|
||||||
|
arity(args, 3);
|
||||||
|
final delimiter = textArg(args[0], ctx);
|
||||||
|
final ignoreEmpty = boolArg(args[1], ctx);
|
||||||
|
final parts = <String>[];
|
||||||
|
for (final value in ctx.flatten(args.sublist(2))) {
|
||||||
|
final text = ctx.asText(value);
|
||||||
|
if (ignoreEmpty && text.isEmpty) continue;
|
||||||
|
parts.add(text);
|
||||||
|
}
|
||||||
|
return TextValue(parts.join(delimiter));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['LEN'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
return NumberValue(textArg(args[0], ctx).length.toDouble());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['LEFT'] = (args, ctx) {
|
||||||
|
arity(args, 1, 2);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final count = args.length > 1 ? intArg(args[1], ctx) : 1;
|
||||||
|
if (count < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return TextValue(text.substring(0, math.min(count, text.length)));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['RIGHT'] = (args, ctx) {
|
||||||
|
arity(args, 1, 2);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final count = args.length > 1 ? intArg(args[1], ctx) : 1;
|
||||||
|
if (count < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
final take = math.min(count, text.length);
|
||||||
|
return TextValue(text.substring(text.length - take));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['MID'] = (args, ctx) {
|
||||||
|
arity(args, 3, 3);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final start = intArg(args[1], ctx);
|
||||||
|
final length = intArg(args[2], ctx);
|
||||||
|
if (start < 1 || length < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
if (start > text.length) return const TextValue('');
|
||||||
|
final begin = start - 1;
|
||||||
|
final end = math.min(begin + length, text.length);
|
||||||
|
return TextValue(text.substring(begin, end));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['UPPER'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
return TextValue(textArg(args[0], ctx).toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['LOWER'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
return TextValue(textArg(args[0], ctx).toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['PROPER'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
var atWordStart = true;
|
||||||
|
for (final rune in text.runes) {
|
||||||
|
final ch = String.fromCharCode(rune);
|
||||||
|
final isLetter = RegExp(r'[A-Za-z]').hasMatch(ch);
|
||||||
|
if (isLetter) {
|
||||||
|
buffer.write(atWordStart ? ch.toUpperCase() : ch.toLowerCase());
|
||||||
|
atWordStart = false;
|
||||||
|
} else {
|
||||||
|
buffer.write(ch);
|
||||||
|
atWordStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TextValue(buffer.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['TRIM'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
return TextValue(textArg(args[0], ctx).trim().replaceAll(RegExp(r'\s+'), ' '));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['REPT'] = (args, ctx) {
|
||||||
|
arity(args, 2, 2);
|
||||||
|
final count = intArg(args[1], ctx);
|
||||||
|
if (count < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return TextValue(textArg(args[0], ctx) * count);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['EXACT'] = (args, ctx) {
|
||||||
|
arity(args, 2, 2);
|
||||||
|
return BooleanValue(textArg(args[0], ctx) == textArg(args[1], ctx));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['SUBSTITUTE'] = (args, ctx) {
|
||||||
|
arity(args, 3, 4);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final oldText = textArg(args[1], ctx);
|
||||||
|
final newText = textArg(args[2], ctx);
|
||||||
|
if (oldText.isEmpty) return TextValue(text);
|
||||||
|
if (args.length == 3) return TextValue(text.replaceAll(oldText, newText));
|
||||||
|
final instance = intArg(args[3], ctx);
|
||||||
|
if (instance < 1) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return TextValue(_replaceNth(text, oldText, newText, instance));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['REPLACE'] = (args, ctx) {
|
||||||
|
arity(args, 4, 4);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
final start = intArg(args[1], ctx);
|
||||||
|
final length = intArg(args[2], ctx);
|
||||||
|
final replacement = textArg(args[3], ctx);
|
||||||
|
if (start < 1 || length < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
final begin = math.min(start - 1, text.length);
|
||||||
|
final end = math.min(begin + length, text.length);
|
||||||
|
return TextValue(text.substring(0, begin) + replacement + text.substring(end));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['FIND'] = (args, ctx) {
|
||||||
|
arity(args, 2, 3);
|
||||||
|
final needle = textArg(args[0], ctx);
|
||||||
|
final haystack = textArg(args[1], ctx);
|
||||||
|
final start = args.length > 2 ? intArg(args[2], ctx) : 1;
|
||||||
|
if (start < 1) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
final index = haystack.indexOf(needle, start - 1);
|
||||||
|
if (index < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return NumberValue((index + 1).toDouble());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['SEARCH'] = (args, ctx) {
|
||||||
|
arity(args, 2, 3);
|
||||||
|
final needle = textArg(args[0], ctx).toLowerCase();
|
||||||
|
final haystack = textArg(args[1], ctx).toLowerCase();
|
||||||
|
final start = args.length > 2 ? intArg(args[2], ctx) : 1;
|
||||||
|
if (start < 1) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
final index = haystack.indexOf(needle, start - 1);
|
||||||
|
if (index < 0) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return NumberValue((index + 1).toDouble());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['CHAR'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
final code = intArg(args[0], ctx);
|
||||||
|
if (code < 1 || code > 0x10FFFF) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return TextValue(String.fromCharCode(code));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['CODE'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
final text = textArg(args[0], ctx);
|
||||||
|
if (text.isEmpty) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return NumberValue(text.runes.first.toDouble());
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['VALUE'] = (args, ctx) {
|
||||||
|
arity(args, 1, 1);
|
||||||
|
final parsed = double.tryParse(textArg(args[0], ctx).trim());
|
||||||
|
if (parsed == null) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return NumberValue(parsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['NUMBERVALUE'] = (args, ctx) {
|
||||||
|
arity(args, 1, 3);
|
||||||
|
var text = textArg(args[0], ctx).trim();
|
||||||
|
final decimalSep = args.length > 1 ? textArg(args[1], ctx) : '.';
|
||||||
|
final groupSep = args.length > 2 ? textArg(args[2], ctx) : ',';
|
||||||
|
text = text.replaceAll(groupSep, '');
|
||||||
|
if (decimalSep != '.') text = text.replaceAll(decimalSep, '.');
|
||||||
|
final parsed = double.tryParse(text);
|
||||||
|
if (parsed == null) throw const EvalException(ErrorValue(ErrorKind.value));
|
||||||
|
return NumberValue(parsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['TEXT'] = (args, ctx) {
|
||||||
|
arity(args, 2, 2);
|
||||||
|
final value = ctx.eval(args[0]);
|
||||||
|
final code = textArg(args[1], ctx);
|
||||||
|
return TextValue(NumberFormat.fromCode(code).apply(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['REGEXMATCH'] = (args, ctx) {
|
||||||
|
arity(args, 2, 2);
|
||||||
|
final pattern = RegExp(textArg(args[1], ctx));
|
||||||
|
return BooleanValue(pattern.hasMatch(textArg(args[0], ctx)));
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['REGEXEXTRACT'] = (args, ctx) {
|
||||||
|
arity(args, 2, 2);
|
||||||
|
final pattern = RegExp(textArg(args[1], ctx));
|
||||||
|
final match = pattern.firstMatch(textArg(args[0], ctx));
|
||||||
|
if (match == null) throw const EvalException(ErrorValue(ErrorKind.na));
|
||||||
|
return TextValue(match.groupCount >= 1 ? (match.group(1) ?? '') : match.group(0)!);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry['REGEXREPLACE'] = (args, ctx) {
|
||||||
|
arity(args, 3, 3);
|
||||||
|
final pattern = RegExp(textArg(args[1], ctx));
|
||||||
|
return TextValue(textArg(args[0], ctx).replaceAll(pattern, textArg(args[2], ctx)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String _replaceNth(String text, String oldText, String newText, int instance) {
|
||||||
|
var searchFrom = 0;
|
||||||
|
var seen = 0;
|
||||||
|
while (true) {
|
||||||
|
final index = text.indexOf(oldText, searchFrom);
|
||||||
|
if (index < 0) return text;
|
||||||
|
seen++;
|
||||||
|
if (seen == instance) {
|
||||||
|
return text.substring(0, index) + newText + text.substring(index + oldText.length);
|
||||||
|
}
|
||||||
|
searchFrom = index + oldText.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user