При обработке текстовых файлов, выгрузок и HTML-разметки разработчики часто сталкиваются с нестандартными форматами строк. Стандартных функций платформы вроде Лев, Прав или СтрНайти в таких сценариях бывает недостаточно. В этой статье мы подробно разберем, как корректно извлекать данные по шаблону, почему стандартная процедура ЗаполнитьЗначенияСвойств не работает с объектами регулярных выражений, а также выясним, как оптимизировать цикл обхода найденных совпадений.
Рассмотрим типовую задачу: из текстового документа требуется найти все артикулы определенного формата (например, две группы по 4 цифры, разделенные косой чертой, вида 1234/5678) и выгрузить их в колонку таблицы значений.
В исходном примере разработчик попытался прочитать файл через ТекстовыйДокумент, применить COM-объект VBScript.RegExp и обойти коллекцию совпадений следующим образом:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Артикул");
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать("C:\Users\User\Desktop\BT.htm");
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.MultiLine = Истина;
RegExp.Global = Истина;
RegExp.IgnoreCase = Истина;
RegExp.Pattern = "[0-9]{4}/[0-9]{4}";
Совпадения = RegExp.Execute(Текст.ПолучитьТекст());
Для Каждого Совпадение Из Совпадения Цикл
НовСтр = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр.Артикул, Совпадение);
КонецЦикла;
В результате таблица значений осталась пустой либо колонка заполнилась некорректными данными. Выясним причину этой ошибки:
ЗаполнитьЗначенияСвойств: процедура принимает в качестве первого параметра объект-приемник целиком (в данном случае строку таблицы НовСтр), а не конкретное поле НовСтр.Артикул.ЗаполнитьЗначенияСвойств копирует значения только между одноименными реквизитами. Элемент коллекции Match COM-объекта VBScript.RegExp содержит свойства Value (найденный текст), FirstIndex (позиция вхождения) и Length (длина). Свойства с именем Артикул у COM-объекта нет, поэтому копирование не происходит.Для Каждого для внешних коллекций: при работе со сторонними COM-объектами перебор через интерфейс итерации не всегда работает корректно в платформе 1С. Надежнее обращаться к элементам коллекции по числовому индексу.Чтобы извлечь само строковое значение найденного фрагмента, необходимо обращаться напрямую к свойству Value каждого элемента коллекции совпадений.
Посмотрим на рабочий вариант с использованием классического цикла Для:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(20)));
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать("C:\Users\User\Desktop\BT.htm");
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.MultiLine = Истина;
RegExp.Global = Истина;
RegExp.IgnoreCase = Истина;
RegExp.Pattern = "\b\d{4}/\d{4}\b";
Совпадения = RegExp.Execute(Текст.ПолучитьТекст());
// Нумерация элементов COM-коллекции начинается с 0
КоличествоСовпадений = Совпадения.Count;
Для Индекс = 0 По КоличествоСовпадений - 1 Цикл
ЭлементСовпадения = Совпадения.Item(Индекс);
НовСтр = ТЗ.Добавить();
НовСтр.Артикул = ЭлементСовпадения.Value;
КонецЦикла;
Обратите внимание на регулярное выражение: вместо [0-9]{4}/[0-9]{4} можно использовать более лаконичную запись \d{4}/\d{4}. Также мы добавили границы слов \b в начале и конце шаблона. Это исключит ложные срабатывания, если в тексте встретится длинная последовательность цифр, например 991234/567899.
В ходе обсуждения часто возникает вопрос производительности: нужно ли заранее выносить выражение границы цикла (например, Совпадения.Count - 1) в отдельную переменную перед началом цикла, или платформа вычисляет его на каждой итерации?
Разберем поведение платформы 1С:Предприятие. Синтаксис цикла с параметром устроен так, что выражение после ключевого слова По вычисляется строго один раз — в момент инициализации цикла. Значение фиксируется во внутренней памяти, и повторного обращения к свойству COM-объекта или вызова функции не происходит.
Проверим это на простом тестовом примере с выводом сообщения из функции:
Функция ПолучитьКоличествоИтераций()
Сообщить("Вызов функции границы цикла!");
Возврат 5;
КонецФункции
Процедура ТестВычисленияГраницы()
Для Счетчик = 0 По ПолучитьКоличествоИтераций() - 1 Цикл
// Тело цикла выполнится 5 раз
КонецЦикла;
КонецПроцедуры
При выполнении этой процедуры сообщение «Вызов функции границы цикла!» появится в окне сообщений ровно один раз. Следовательно, синтаксис Для Индекс = 0 По Совпадения.Count - 1 Цикл является полностью безопасным с точки зрения накладных расходов, хотя предварительное сохранение значения в отдельную переменную делает код более наглядным при отладке.
Использование COM-объекта VBScript.RegExp имеет критические ограничения: технология COM работает только в операционных системах семейства Windows (не поддерживается на серверах под управлением Linux), а сама библиотека VBScript объявлена устаревшей компанией Microsoft.
Если ваша база работает на платформе 1С:Предприятие версии 8.3.23 и выше, настоятельно рекомендуется использовать встроенные методы работы с регулярными выражениями (стандарт ICU). Они работают быстро, полностью кроссплатформенно и не зависят от внешних библиотек.
Рассмотрим, как решить ту же задачу встроенными функциями платформы:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(20)));
ТекстДокумент = Новый ТекстовыйДокумент;
ТекстДокумент.Прочитать("C:\Users\User\Desktop\BT.htm");
ИсходныйТекст = ТекстДокумент.ПолучитьТекст();
ШаблонПоиска = "\b\d{4}/\d{4}\b";
// Метод возвращает массив объектов типа РезультатПоискаПоРегулярномуВыражению
МассивРезультатов = СтрНайтиВсеПоРегулярномуВыражению(ИсходныйТекст, ШаблонПоиска);
Для Каждого Вхождение Из МассивРезультатов Цикл
// Извлекаем точную подстроку по начальной позиции и длине
НайденнаяПодстрока = Сред(ИсходныйТекст, Вхождение.НачальнаяПозиция, Вхождение.Длина);
НовСтр = ТЗ.Добавить();
НовСтр.Артикул = НайденнаяПодстрока;
КонецЦикла;
Объект типа РезультатПоискаПоРегулярномуВыражению хранит координаты найденного блока: свойства НачальнаяПозиция и Длина. С помощью стандартной функции Сред мы получаем исходное строковое значение артикула и записываем его в строку таблицы значений без создания сторонних COM-объектов.