Книга: Выразительный JavaScript
Разбор INI файлы
Разбор INI файлы
В заключение главы рассмотрим задачу с использованием регулярок. Представьте, что мы пишем программу, собирающую сведения о наших врагах через интернет в автоматическом режиме. (Всю программу писать не будем, только ту часть, которая читает файл с настройками. Извините.) Файл выглядит так:
searchengine=http://www.google.com/search?q=$1
spitefulness=9.7
; перед комментариями ставится точка с запятой
; каждая секция относится к отдельному врагу
[larry]
fullname=Larry Doe
type=бычара из детсада
website=http://www.geocities.com/CapeCanaveral/11451
[gargamel]
fullname=Gargamel
type=злой волшебник
outputdir=/home/marijn/enemies/gargamel
Точный формат файла (который довольно широко используется, и обычно называется INI), следующий:
• Пустые строки и строки, начинающиеся с точки с запятой, игнорируются.
• Строки, заключённые в квадратные скобки, начинают новую секцию.
• Строки, содержащие алфавитно-цифровой идентификатор, за которым следует =
, добавляют настройку в данной секции.
• Всё остальное – неверные данные.
Наша задача – преобразовать такую строку в массив объектов, каждый со свойством name
и массивом настроек. Для каждой секции нужен один объект, и ещё один – для глобальных настроек сверху файла.
Так как файл надо разбирать построчно, неплохо начать с разбиения файла на строки. Для этого в главе 6 мы использовали string.split("n")
. Некоторые операционки используют для перевода строки не один символ n, а два — rn
. Так как метод split
принимает регулярки в качестве аргумента, мы можем делить линии при помощи выражения /r?n/
, разрешающего и одиночные n и rn
между строками.
function parseINI(string) {
// Начнём с объекта, содержащего настройки верхнего уровня
var currentSection = {name: null, fields: []};
var categories = [currentSection];
string.split(/r?n/).forEach(function(line) {
var match;
if (/^s*(;.*)?$/.test(line)) {
return;
} else if (match = line.match(/^[(.*)]$/)) {
currentSection = {name: match[1], fields: []};
categories.push(currentSection);
} else if (match = line.match(/^(w+)=(.*)$/)) {
currentSection.fields.push({name: match[1],
value: match[2]});
} else {
throw new Error("Строчка '" + line + "' содержит неверные данные.");
}
});
return categories;
}
Код проходит все строки, обновляя объект текущей секции (current section). Сначала он проверяет, можно ли игнорировать строчку, при помощи регулярки /^s(;.)?$/
. Соображаете, как это работает? Часть между скобок совпадает с комментариями, а ?
делает так, что регулярка совпадёт и со строчками, состоящими из одних пробелов.
Если строка – не комментарий, код проверяет, начинает ли она новую секцию. Если да, он создаёт новый объект для текущей секции, к которому добавляются последующие настройки.
Последняя осмысленная возможность – строка является обычной настройкой, и в этом случае она добавляется к текущему объекту.
Если ни один вариант не сработал, функция выдаёт ошибку.
Заметьте, как частое использование ^
и $
заботится о том, что выражение совпадает со всей строкой целиком, а не с частью. Если их не использовать, код в целом будет работать, но иногда будет выдавать странные результаты, и такую ошибку будет трудно отследить.
Конструкция if (match = string.match(...))
похожа на трюк, использующий присвоение как условие в цикле while
. Часто вы не уверены, что вызов match
будет успешным, поэтому вы можете получить доступ к результирующему объекту только внутри блока if
, который это проверяет. Чтобы не разбивать красивую цепочку проверок if
, мы присваиваем результат поиска переменной, и сразу используем это присвоение как проверку.
- Создаём регулярное выражение
- Проверяем на совпадения
- Ищем набор символов
- Повторяем части шаблона
- Группировка подвыражений
- Совпадения и группы
- Тип даты
- Границы слова и строки
- Шаблоны с выбором
- Механизм поиска
- Откаты
- Метод replace
- Жадность
- Динамическое создание объектов RegExp
- Метод search
- Свойство lastIndex
- Циклы по вхождениям
- Разбор INI файлы
- Международные символы
- Итог
- Упражнения
- Файлы базы данных InterBase
- Файлы *.GDB изнутри
- 3.3.4 init.c
- Initialization and association
- SCTP INIT chunk
- SCTP INIT ACK chunk
- Initial loading of extra modules
- 1. APPLICABILITY AND DEFINITIONS
- 5. COMBINING DOCUMENTS
- Глава 8 Технологии IP Storage и InfiniBand
- 12.2. Файлы конфигураци исервера
- 8.2 Стандарт InfiniBand