Анализатор CSV, используемый в плагине jquery-csv
Это базовый синтаксический анализатор Chomsky Type III .
Токенайзер регулярных выражений используется для оценки данных по типу за символом. Когда встречается контрольный символ, код передается в оператор switch для дальнейшей оценки на основе начального состояния. Неуправляемые символы группируются и копируются в массовом порядке, чтобы уменьшить количество необходимых операций копирования строки.
Токенизатор:
var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;
Первый набор совпадений - это управляющие символы: разделитель значений ("), разделитель значений (,) и разделитель ввода (все варианты новой строки). Последнее совпадение обрабатывает неуправляемую группировку символов.
Есть 10 правил, которым должен удовлетворять парсер:
- Правило № 1 - Одна запись в каждой строке, каждая строка заканчивается новой строкой
- Правило № 2 - завершающий символ новой строки в конце файла опущен
- Правило № 3 - Первая строка содержит данные заголовка
- Правило № 4 - пробелы считаются данными, а записи не должны содержать запятой
- Правило № 5 - Строки могут или не могут быть разделены двойными кавычками
- Правило № 6 - Поля, содержащие разрывы строк, двойные кавычки и запятые, должны быть заключены в двойные кавычки
- Правило № 7 - Если для заключения полей используются двойные кавычки, то двойная кавычка, появляющаяся внутри поля, должна быть экранирована, предшествуя другой двойной кавычкой
- Поправка № 1 - Поле без кавычек может или может
- Поправка № 2 - Цитируемое поле может или не может
- Поправка № 3 - Последнее поле в записи может содержать или не содержать нулевое значение
Примечание: 7 верхних правил получены непосредственно из IETF RFC 4180 . Последние 3 были добавлены для покрытия крайних случаев, представленных современными приложениями для работы с электронными таблицами (например, Excel, Google Spreadsheet), которые по умолчанию не разделяют (т.е. заключают в кавычки) все значения. Я попытался внести изменения в RFC, но пока не получил ответа на свой запрос.
Хватит заводить, вот схема:
Состояния:
- начальное состояние для записи и / или значения
- открыта цитата
- вторая цитата была обнаружена
- встречается значение без кавычек
Переходы:
- а. проверяет оба указанных значения (1), значения без кавычек (3), нулевые значения (0), нулевые записи (0) и новые записи (0)
- б. проверяет вторую цитату char (2)
- с. проверяет экранированную кавычку (1), конец значения (0) и конец записи (0)
- д. проверяет конец значения (0) и конец записи (0)
Примечание: на самом деле отсутствует состояние. Должна быть строка от 'c' -> 'b', помеченная состоянием '1', потому что экранированный второй разделитель означает, что первый разделитель все еще открыт. На самом деле, вероятно, было бы лучше представить его как еще один переход. Создание их - это искусство, единого правильного пути не существует.
Примечание: в нем также отсутствует состояние выхода, но на допустимых данных анализатор всегда заканчивается при переходе 'a', и ни одно из состояний невозможно, потому что ничего не осталось для анализа.
Разница между состояниями и переходами:
Состояние является конечным, то есть оно может означать только одно.
Переход представляет собой поток между состояниями, поэтому он может означать много вещей.
По сути, отношение состояние-> переход - 1 -> * (то есть один ко многим). Состояние определяет «что это такое», а переход определяет «как оно обрабатывается».
Примечание: не беспокойтесь, если приложение состояний / переходов не кажется интуитивно понятным, оно не интуитивно понятно. Потребовалась обширная переписка с кем-то, кто был намного умнее меня, прежде чем я, наконец, получил идею придерживаться.
Псевдокод:
csv = // csv input string
// init all state & data
state = 0
value = ""
entry = []
output = []
endOfValue() {
entry.push(value)
value = ""
}
endOfEntry() {
endOfValue()
output.push(entry)
entry = []
}
tokenizer = /("|,|\n|\r|[^",\r\n]+)/gm
// using the match extension of string.replace. string.exec can also be used in a similar manner
csv.replace(tokenizer, function (match) {
switch(state) {
case 0:
if(opening delimiter)
state = 1
break
if(new-line)
endOfEntry()
state = 0
break
if(un-delimited data)
value += match
state = 3
break
case 1:
if(second delimiter encountered)
state = 2
break
if(non-control char data)
value += match
state = 1
break
case 2:
if(escaped delimiter)
state = 1
break
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
case 3:
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
}
}
Примечание: это суть, на практике гораздо больше, чтобы рассмотреть. Например, проверка ошибок, нулевые значения, завершающая пустая строка (т. Е. Которая действительна) и т. Д.
В этом случае состояние - это состояние вещей, когда блок соответствия регулярному выражению завершает итерацию. Переход представляется в виде падежных операторов.
Как люди, мы имеем тенденцию к упрощению операций низкого уровня на более высокие авторефераты уровня , но работа с FSM будет работать с операциями низкого уровня. В то время как с состояниями и переходами очень легко работать по отдельности, по своей сути сложно визуализировать все сразу. Мне было легче повторять отдельные пути выполнения снова и снова, пока я не смогу интуитивно понять, как происходит переход. Это похоже на изучение базовой математики, вы не сможете оценить код с более высокого уровня, пока детали низкого уровня не станут автоматическими.
В сторону: если вы посмотрите на фактическую реализацию, там пропущено много деталей. Во-первых, все невозможные пути приведут к определенным исключениям. Удар по ним должен быть невозможным, но если что-то сломается, они будут вызывать исключения в тестовом средстве. Во-вторых, правила синтаксического анализа для того, что разрешено в «допустимой» строке данных CSV, довольно свободны, поэтому код, необходимый для обработки множества конкретных крайних случаев. Независимо от этого факта, это был процесс, который использовался для насмешки FSM перед всеми исправлениями ошибок, расширениями и тонкой настройкой.
Как и в большинстве проектов, это не точное представление реализации, но оно описывает важные части. На практике на самом деле существует 3 различных функции синтаксического анализатора, полученных из этого проекта: специфичный для csv разделитель строк, однострочный анализатор и полный многострочный анализатор. Все они работают одинаково, они отличаются тем, как они обрабатывают символы новой строки.