У меня есть вариант использования, когда у меня есть строка текста, содержащая токены вложенности (например, {
и }
), и я хочу преобразовать определенные подстроки, вложенные на определенной глубине.
Например, заглавное слово moo на глубине 1:
му [му [му му]] му ->
му [му [му му]] му
Достигается:
replaceTokens(input, 1, "[", "]", "moo", String::toUpperCase);
Или пример из реального мира, поставьте "--options", еще не окрашенные в цветовой последовательности голубой:
@ | blue --ignoreLog | @ работает, но --ignoreOutput заставляет все замолчать. ->
@ | blue --ignoreLog | @ работает, но @ | cyan --ignoreOutput | @ заставляет все замолчать.
Достигается:
replaceTokens(input, 0, "@|", "|@", "--\\w*", s -> format("@|cyan %s|@", s));
Я реализовал эту логику, и хотя я чувствую себя довольно хорошо (за исключением производительности, вероятно), я также чувствую, что заново изобрел колесо. Вот как я это реализовал:
set currentPos to zero
while (input line not fully consumed) {
take the remaining line
if the open token is matched, add to output, increase counter and advance pos accordingly
else if the close token is matched, add to output, decrease counter and advance pos accordingly
else if the counter matches provided depth and given regex matches, invoke replacer function and advance pos accordingly
else just record the next character and advance pos by 1
}
Вот фактическая реализация:
public static String replaceNestedTokens(String lineWithTokens, int nestingDepth, String tokenOpen, String tokenClose, String tokenRegexToReplace, Function<String, String> tokenReplacer) {
final Pattern startsWithOpen = compile(quote(tokenOpen));
final Pattern startsWithClose = compile(quote(tokenClose));
final Pattern startsWithTokenToReplace = compile(format("(?<token>%s)", tokenRegexToReplace));
final StringBuilder lineWithTokensReplaced = new StringBuilder();
int countOpenTokens = 0;
int pos = 0;
while (pos < lineWithTokens.length()) {
final String remainingLine = lineWithTokens.substring(pos);
if (startsWithOpen.matcher(remainingLine).lookingAt()) {
countOpenTokens++;
lineWithTokensReplaced.append(tokenOpen);
pos += tokenOpen.length();
} else if (startsWithClose.matcher(remainingLine).lookingAt()) {
countOpenTokens--;
lineWithTokensReplaced.append(tokenClose);
pos += tokenClose.length();
} else if (countOpenTokens == nestingDepth) {
Matcher startsWithTokenMatcher = startsWithTokenToReplace.matcher(remainingLine);
if (startsWithTokenMatcher.lookingAt()) {
String matchedToken = startsWithTokenMatcher.group("token");
lineWithTokensReplaced.append(tokenReplacer.apply(matchedToken));
pos += matchedToken.length();
} else {
lineWithTokensReplaced.append(lineWithTokens.charAt(pos++));
}
} else {
lineWithTokensReplaced.append(lineWithTokens.charAt(pos++));
}
assumeTrue(countOpenTokens >= 0, "Unbalanced token sets: closed token without open token\n\t" + lineWithTokens);
}
assumeTrue(countOpenTokens == 0, "Unbalanced token sets: open token without closed token\n\t" + lineWithTokens);
return lineWithTokensReplaced.toString();
}
Я не мог заставить его работать с регулярным выражением, таким как это или это (или Сканер), но я чувствую, что заново изобретаю колесо и могу решить эту проблему с помощью ( готовые классы с меньшим количеством кода. Кроме того, я почти уверен, что это кошмар производительности со всеми встроенными экземплярами шаблонов / совпадений и подстрок.
Предложения