Так что поосторожнее с глобальными переменными-регулярками. В случаях, когда они необходимы – вызовы replace или места, где вы специально используете lastIndex – пожалуй и все случаи, в которых их следует применять.

<p>Циклы по вхождениям</p>

Типичная задача – пройти по всем вхождениям шаблона в строку так, чтобы иметь доступ к объекту match в теле цикла, используя lastIndex и exec.

var input = "Строчка с 3 числами в ней... 42 и 88.";

var number = /\b(\d+)\b/g;

var match;

while (match = number.exec(input))

  console.log("Нашёл ", match[1], " на ", match.index);

// → Нашёл 3 на 10

//   Нашёл 42 на 29

//   Нашёл 88 на 34

Используется тот факт, что значением присвоения является присваиваемое значение. Используя конструкцию match = re.exec(input) в качестве условия в цикле while, мы производим поиск в начале каждой итерации, сохраняем результат в переменной, и заканчиваем цикл, когда все совпадения найдены.

<p>Разбор INI файлы</p>

В заключение главы рассмотрим задачу с использованием регулярок. Представьте, что мы пишем программу, собирающую сведения о наших врагах через интернет в автоматическом режиме. (Всю программу писать не будем, только ту часть, которая читает файл с настройками. Извините.) Файл выглядит так:

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, а два — \r\n. Так как метод split принимает регулярки в качестве аргумента, мы можем делить линии при помощи выражения /\r?\n/, разрешающего и одиночные \n и \r\n между строками.

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(;.)?$/. Соображаете, как это работает? Часть между скобок совпадает с комментариями, а ? делает так, что регулярка совпадёт и со строчками, состоящими из одних пробелов.

Если строка – не комментарий, код проверяет, начинает ли она новую секцию. Если да, он создаёт новый объект для текущей секции, к которому добавляются последующие настройки.

Последняя осмысленная возможность – строка является обычной настройкой, и в этом случае она добавляется к текущему объекту.

Перейти на страницу:

Похожие книги