В практике разработчика 1С часто встречается задача разбора неструктурированных строк, приходящих из внешних систем (банковские выписки, логи, текстовые файлы). Типичный пример такой строки: "Ключ1: Значение Ключ2: Другое значение". Сложность здесь заключается в том, что пробелы могут присутствовать как в самих значениях, так и разделять пары. Разберем наиболее эффективные способы решения этой задачи без привлечения тяжеловесных инструментов вроде регулярных выражений (RegExp).
Прежде чем приступать к написанию кода, проанализируем входящие данные. В нашем случае строка имеет вид: "Aaaa: ловыа ва лывоа Bbb.bb: sdfkjfsd lksdf Cccc: kjlsdfl lsd". Мы видим, что:
Рассмотрим самый логичный подход. Мы можем разделить всю строку на массив, используя символ ":" в качестве разделителя. Однако при таком подходе имя следующего ключа окажется в конце предыдущего элемента массива. Разберем этот алгоритм по шагам.
Проанализируем программную реализацию этого метода:
Функция РазобратьСтрокуПараметров(Знач ВходнаяСтрока)
Результат = Новый Соответствие;
// 1. Разбиваем строку по двоеточиям
МассивЧастей = СтрРазделить(ВходнаяСтрока, ":");
Если МассивЧастей.Количество() < 2 Тогда
Возврат Результат;
КонецЕсли;
// Первый элемент - это всегда первый Ключ
ТекущийКлюч = СокрЛП(МассивЧастей[0]);
// Перебираем остальные части, кроме первой
Для Инд = 1 По МассивЧастей.Количество() - 1 Цикл
ТекСтрока = МассивЧастей[Инд];
// Если это не последний элемент, значит в конце сидит следующий ключ
Если Инд < МассивЧастей.Количество() - 1 Тогда
// Ищем последний пробел, который отделяет значение от следующего ключа
ПозПробела = СтрНайти(ТекСтрока, " ", НаправлениеПоиска.СКонца);
Если ПозПробела > 0 Тогда
Значение = СокрЛП(Лев(ТекСтрока, ПозПробела - 1));
СледующийКлюч = СокрЛП(Сред(ТекСтрока, ПозПробела + 1));
Иначе
// Если пробела нет, значит значение пустое, а вся строка - это ключ
Значение = "";
СледующийКлюч = СокрЛП(ТекСтрока);
КонецЕсли;
Результат.Вставить(ТекущийКлюч, Значение);
ТекущийКлюч = СледующийКлюч;
Иначе
// Последний элемент - это всегда чистое Значение последнего ключа
Результат.Вставить(ТекущийКлюч, СокрЛП(ТекСтрока));
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Этот метод хорош тем, что он устойчив к наличию точек в именах ключей и пробелов в значениях. Мы используем встроенную функцию СтрНайти с параметром НаправлениеПоиска.СКонца, что позволяет нам эффективно "отрезать" имя ключа от текста значения.
Рассмотрим альтернативный вариант, который предложили участники обсуждения: разбить строку по пробелам и проверять каждое "слово". Если слово заканчивается на двоеточие — перед нами ключ.
Выясним причину, по которой этот метод может быть удобен: он позволяет обрабатывать строку "потоком", не создавая промежуточных больших подстрок. Посмотрим на пример реализации:
Функция РазобратьСтрокуТокенами(Строка)
ПотокТокенов = СтрРазделить(Строка, " ", Ложь);
Результат = Новый Соответствие;
ТекущийКлюч = "";
ТекущееЗначение = "";
Для Каждого Токен Из ПотокТокенов Цикл
// Проверяем, является ли текущее слово ключом
Если СтрЗаканчиваетсяНа(Токен, ":") Тогда
// Если у нас уже был накоплен ключ, сохраняем его значение
Если ЗначениеЗаполнено(ТекущийКлюч) Тогда
Результат.Вставить(ТекущийКлюч, СокрЛП(ТекущееЗначение));
КонецЕсли;
// Отрезаем двоеточие и запоминаем новый ключ
ТекущийКлюч = Лев(Токен, СтрДлина(Токен) - 1);
ТекущееЗначение = "";
Иначе
// Если это не ключ, значит это часть значения
ТекущееЗначение = ТекущееЗначение + " " + Токен;
КонецЕсли;
КонецЦикла;
// Не забываем добавить последнюю пару
Если ЗначениеЗаполнено(ТекущийКлюч) Тогда
Результат.Вставить(ТекущийКлюч, СокрЛП(ТекущееЗначение));
КонецЕсли;
Возврат Результат;
КонецФункции
Важный нюанс: метод токенизации по пробелам (СтрРазделить(Строка, " ")) будет работать корректно только в том случае, если двоеточие всегда приклеено к ключу (например, "Ключ:"), а не отделено пробелом ("Ключ :"). Если в ваших данных возможны пробелы перед двоеточием, используйте предварительную обработку функцией СтрЗаменить(Строка, " :", ":").
Проанализируем ситуацию с большими объемами данных. Если вы обрабатываете строки длиной в мегабайты (например, при загрузке логов):
VBScript.RegExp) могло бы сократить код, но оно создает зависимость от операционной системы Windows и часто работает медленнее стандартных строковых функций 1С на простых паттернах.Рассмотрим подробнее вопрос "особых случаев". Что если значение само содержит двоеточие (например, время 12:00)? В этой ситуации все методы, основанные на простом СтрРазделить по двоеточию, потребуют дополнительной проверки: является ли слово перед двоеточием валидным ключом. В таких случаях метод посимвольного перебора строки оказывается наиболее гибким для доработки логики.
Для большинства задач оптимальным будет Метод 1 (разбиение по двоеточию + СтрНайти с конца). Он сочетает в себе читаемость кода и высокую скорость работы. При написании таких парсеров всегда помните о функции СокрЛП, так как лишние пробелы — главный враг при сопоставлении ключей в Соответствии или Структуре.