При пакетной обработке файлов или разборе внешних выгрузок разработчики регулярно сталкиваются с задачей извлечения полезных данных, ограниченных служебными маркерами. Типичный пример: текстовый блок начинается с заголовка вида Start *число*, за которым следует пустая строка, затем целевой многострочный текст, а завершается блок маркером Finish. При этом количество строк в файлах варьируется, а числовой идентификатор в заголовке постоянно меняется.
Разберем по шагам, как правильно и быстро получить нужную подстроку на платформе 1С:Предприятие, избежав падения производительности при обработке большого объема файлов.
Когда требуется обработать множество файлов в цикле, загрузка их содержимого целиком в память строковыми переменными создает избыточную нагрузку на систему. Оптимальным решением является использование объекта ЧтениеТекста с последовательным чтением через метод ПрочитатьСтроку().
Для корректного отсечения заголовков и маркеров организуем конечный автомат с флагом состояния:
Пока.СтрНачинаетсяС(). Как только строка начинается со слова «Start», переключаем флаг сбора данных.ПустаяСтрока().Закрыть(), чтобы освободить дескриптор файла в операционной системе.Посмотрим на пример реализации потокового чтения:
Чтение = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.UTF8);
МассивРезультата = Новый Массив;
БлокНайден = Ложь;
ПропуститьПустую = Ложь;
Стр = Чтение.ПрочитатьСтроку();
Пока Стр <> Неопределено Цикл
Если Не БлокНайден Тогда
Если СтрНачинаетсяС(СокрЛП(Стр), "Start") Тогда
БлокНайден = Истина;
ПропуститьПустую = Истина;
КонецЕсли;
Иначе
Если СтрНачинаетсяС(СокрЛП(Стр), "Finish") Тогда
Прервать;
КонецЕсли;
Если ПропуститьПустую И ПустаяСтрока(Стр) Тогда
ПропуститьПустую = Ложь;
Иначе
ПропуститьПустую = Ложь;
МассивРезультата.Добавить(Стр);
КонецЕсли;
КонецЕсли;
Стр = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
РезультатТекст = СтрСоединить(МассивРезультата, Символы.ПС);
Если текст уже загружен в память или получен в виде строковой переменной, не следует использовать связку функций СтрПолучитьСтроку() и СтрЧислоСтрок() в цикле. При каждом вызове СтрПолучитьСтроку() платформа сканирует строку заново с первого символа, что приводит к квадратичной сложности алгоритма и резкому падению скорости на больших текстах.
Начиная с версии платформы 8.3.6, гораздо эффективнее разбить весь текст на массив строк с помощью функции СтрРазделить(), где доступ к элементам осуществляется мгновенно.
Рассмотрим алгоритм разбора массива строк:
МассивВсехСтрок = СтрРазделить(ИсходныйТекст, Символы.ПС, Ложь);
РезультатМассив = Новый Массив;
ФлагСбора = Ложь;
ОжидаемПропуск = Ложь;
Для Каждого ТекущаяСтрока Из МассивВсехСтрок Цикл
ОчищеннаяСтрока = СокрЛП(ТекущаяСтрока);
Если Не ФлагСбора Тогда
Если СтрНачинаетсяС(ОчищеннаяСтрока, "Start") Тогда
ФлагСбора = Истина;
ОжидаемПропуск = Истина;
КонецЕсли;
Продолжить;
КонецЕсли;
Если СтрНачинаетсяС(ОчищеннаяСтрока, "Finish") Тогда
Прервать;
КонецЕсли;
Если ОжидаемПропуск И ПустаяСтрока(ОчищеннаяСтрока) Тогда
ОжидаемПропуск = Ложь;
Продолжить;
КонецЕсли;
ОжидаемПропуск = Ложь;
РезультатМассив.Добавить(ТекущаяСтрока);
КонецЦикла;
СтрокаРезультат = СтрСоединить(РезультатМассив, Символы.ПС);
В среде под управлением ОС Windows задачу извлечения фрагмента переменной длины между двумя сигнатурами можно решить в несколько строк без явных циклов с помощью регулярных выражений.
Проанализируем шаблон регулярного выражения:
Start\s+\d+ находит стартовый маркер и следующее за ним число любой разрядности.\r?\n\s*\r?\n учитывают обязательный перевод строки и следующую за ним пустую строку.([\s\S]*?) лениво забирает весь полезный текст до завершающего маркера.\r?\nFinish обозначает границу окончания блока.Посмотрим на пример кода с использованием объекта VBScript.RegExp:
РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Pattern = "Start\s+\d+\s*\r?\n\s*\r?\n([\s\S]*?)\r?\nFinish";
РегВыр.IgnoreCase = Истина;
РегВыр.Multiline = Истина;
Совпадения = РегВыр.Execute(ИсходныйТекст);
Если Совпадения.Count > 0 Тогда
СтрокаРезультат = Совпадения.Item(0).SubMatches.Item(0);
Иначе
СтрокаРезультат = "";
КонецЕсли;
Важное ограничение: данный способ работает исключительно на серверах и клиентах под управлением Windows, так как использует технологию COM.
\r\n) или Linux (LF, \n). При разбиении только по Символы.ПС в конце строк может оставаться невидимый символ возврата каретки Символы.ВК. Для его удаления применяйте функцию СокрЛП() или СтрЗаменить(Стр, Символы.ВК, "").Стр = "" часто дает сбой, если в пустой строке присутствуют пробелы или знаки табуляции. Всегда используйте стандартную функцию платформы ПустаяСтрока(Стр).Найти() рекомендуется использовать строгие проверки СтрНачинаетсяС() или СтрЗаканчиваетсяНа(). Это исключает случайное срабатывание маркера, если слово «Start» или «Finish» встретится внутри самого информационного блока.