Регулярные выражения действительно эффективны и удобны, но для "серьезного бизнеса" часто рекомендуется использовать более надежный подход и избегать регулярных выражений для важных сложных операций (*), т.е. лучше всего реализовать что-то вроде простой конечный автомат парсер.
В качестве упражнения я попытался сделать что-то похожее на этот подход, с правилами, абстрагированными из примеров OP, где только «корневой уровень, слова, заключающие кавычки», считаются релевантными, а «висячие» кавычки отбрасываются. Итак, правила:
- первое вхождение любой кавычки после перехода символа, отличного от слова, в состояние "кавычка", где:
- остальные цитаты передаются дословно,
- те же кавычки, за которыми не следуют символы, не являющиеся словами, передаются дословно
- та же кавычка, за которой следует символ, не являющийся словом, завершает кавычку и выходит из состояния кавычки.
- Открытая, но незаконченная цитата отбрасывается. (Это нарушает вариант использования OP, потому что отбрасываются все оборванные «вложенные» кавычки, что, по ИМО, значительно усложнит конечный автомат. И я оставил его, чтобы сделать этот случай более очевидным. Такое явление обнаружено и сообщается в консоли.)
В качестве примера я намеренно избегал регулярных выражений даже для определения несловного символа, который я сделал коротким (пробел и ,.!?\/-
, плюс кавычки "
и '
, которые обрабатываются отдельно).
Пожалуйста, примите это скорее как POC: я раньше не писал FSM, так что, скорее всего, это не лучший учебный материал.
// @ts-check
function state_machine(input_str) {
var input_buffer = [...input_str];
var out = [];
var char = '';
var start_quote_adept_index = -1;
var quote_kind;
function is_word_char() {
if (!char) {
return false;
}
if (" ,.!?\\/-".includes(char)) {
return false;
}
return true;
}
function is_quote() {
if (char == "'" || char == '"') {
return true
}
return false
}
function add(ch) {
out.push(ch);
}
var states = {
outside_word: function() {
if (is_quote()) {
state = states.in_quote;
quote_kind = char;
start_quote_adept_index = i;
add('');
} else if (is_word_char()) {
state = states.in_word
add(char);
} else {
add(char);
}
},
in_quote: function() {
if (char == quote_kind) {
state = states.maybe_after_quote;
} else {
add(char);
}
},
maybe_after_quote: function() {
if (is_word_char()) {
// false alarm: quote inside word
state = states.in_quote;
add(quote_kind);
add(char);
} else {
// yup, "proper" quote
state = states.outside_word;
out[start_quote_adept_index] = '<i>';
add('</i>');
start_quote_adept_index = -1;
add(char);
}
},
in_word: function() {
if (is_word_char()) {
add(char);
} else {
state = states.outside_word;
add(char);
}
}
}
var state = states.outside_word;
var i = -1;
while (++i <= input_buffer.length) {
// <= for one extra assignment of `undefined` for finishing state
char = input_buffer[i];
state();
}
if (start_quote_adept_index > -1) {
console.warn('Non-balanced quote `%s` at index %s', quote_kind, start_quote_adept_index);
var err_info = [...input_str];
err_info.splice(start_quote_adept_index + 1, 0, '%c');
err_info.splice(start_quote_adept_index, 0, '%c');
console.info(err_info.join(''), 'border: 1em solid red');
}
return out.join('');
}
input.oninput()
<input id="input" oninput="console.clear();output.value=state_machine(this.value)" size="80" value="that hat's "that hat's" 'that hat's'. Hanna "loves" '' 'ice "cream">
<br><output id="output" style="white-space: pre-wrap"></output>
Обратите внимание, что обработка последнего "
из начального значения нарушает правила OP. Он уже находится в состоянии «одинарной кавычки» (незавершенное, поэтому отбрасывается, поэтому '
не попадает в вывод) и где двойные кавычки просто рассматриваются как символы слова, поэтому "
проскальзывает в вывод.
OTOH, этот подход без проблем обрабатывает слова и даже предложения внутри кавычек.
Основой этого подхода является цикл, который постепенно вызывает функцию состояния для каждого символа из входной строки (плюс один дополнительный), который проверяет символ, в конечном итоге добавляет его в выходной буфер и устанавливает следующее состояние. Для открывающих кавычек он просто сохраняет свой индекс и добавляет ''
к выводу до тех пор, пока не будет соответствующего закрывающего аналога, затем открывающий тег вставляется в сохраненную позицию индекса. Я не уверен, что это лучшая практика, но я думаю, что любой другой способ приведет к ненужным осмотрам. Таким образом, он остается в (я думаю) желанной O(n)
земле.
(*) Основными преимуществами простого FSM по сравнению с регулярным выражением являются, в основном, предотвращение различных катастрофических опасностей возврата и очень трудная читаемость и "отладка" литералов регулярных выражений.
Как вы определяете разницу между
'
для курсива и'
для грамматики (например,that's
)?I have seen the answers on similar problems but it doesn't work
что является примером таких ответов здесь, на SO, и почему они не сработали для вас?@evolutionxbox это просто возможное состояние ошибки. вы можете изменить слово на не
Но почему
's "that'
не должно быть строкой? Но'test'
является допустимым. И почему значение для'that's'
должно бытьthat's
, а не простоthat
?@t.niese, это проблема, с которой я столкнулся. как получить вывод, как я ожидаю в таком состоянии.
В показанном тексте нет правила, и это неоднозначно. Вы не можете определить для этого текста, является ли кавычка частью текста или предназначена для цитирования строки. Вот почему вы не увидите такой конструкции в языке программирования. В JavaScript у вас может быть
let str ="something ' something ' something";
илиlet str ='something " something " something';
, ноlet str ='that's something';
не является допустимым JavaScript.Точно. Ваши примеры ввода и вывода не могут быть преобразованы логикой, основанной только на символах кавычек. Вы можете расширить эту логику, например, правилом, говорящим, что «символ кавычки, окруженный буквенно-цифровыми символами, следует игнорировать и передавать в вывод дословно», но без точного описания вы не можете получить надежное решение.
Вы можете делать наилучшие предположения, но не создавать надежный парсер для таких строк. За исключением случаев, когда вы определяете для них очень четкие ограничения, но даже в этом случае это может быть проблематично. Вот почему языки программирования используют управляющие последовательности. Если вы хотите использовать
"
в строковых кавычках с"
, вам нужно экранировать его с помощью\
. Это, например. одна из причин, по которой синтаксический анализ естественных языков, таких как английский, настолько проблематичен.это для функции распознавания ошибок в моем редакторе кода. поэтому я создал ситуацию хаоса и убедился, что мое приложение справится с этим. спасибо