ВНИМАНИЕ!!!! Требуется понимать, что такое регулярные выражения. Если данного знания нет, то гугл подскажет.
Сразу на примере. Есть строка типа.
ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "Ромашка", ИНН 1233455671, БИК 032405676, р/с — 10203810565780991778
Из неё нужно вытащить наименование организации, инн, бик и р/с. Вполне очевидно, представить себе строку в следующем виде
"Наименование организации", ИНН "ИНН", БИК "БИК", р/с — "Расчетный счет"
Теперь надо перевести это в более понятный машине формат
%[Наименование]%, ИНН %[ИНН]%, БИК %[БИК]%, р/с — %[РасчетныйСчет]%"
В квадратных скобках назовём параметром, остальное — разделители. Замечу, что подряд 2 разделителя или параметра быть не может (разделителем может быть и один пробел).
Тут вроде бы всё хорошо, но возникает вопрос, а что если у самого параметра определённый формат? или есть строки, в целом, одинаковые, но определённый параметр отличается, типа формата даты? Делать кучу шаблонов — не вариант. А как вообще вытащить данные, которые относятся к тому или иному параметру?? Нужны ответы, их есть у меня. Достаточно сделать так, что бы пользователь сам описал нужный формат параметра, а если он не указан, то указать какой либо по умолчанию, и пустить через RegExp.
Я это реализовал так.
%[Наименование]%, ИНН %[%(ИНН,d+)%]%, БИК %[%(БИК,d+)%]%, р/с — %[%(РасчетныйСчет,d+)%]%
Если в %[]% указаны скобки %()% то в них прописывается наименование параметра и формат параметра в тексте. По умолчанию, формат [0-9A-Za-zА-Яа-я\_"’ ()]*. Это значит что берутся все числа, английские буквы, русские буквы, разные символы и их может быть от 0 до много. При необходимости формат можно подправить. Более того в %()% могут идти через запятую. К примеру %[%(ИНН_Юр,d{10})%,%(ИНН_Ип,d{12})%]%. То есть, если в инн 12 чисел, то вернёт параметр ИНН_Ип, если 10, то ИНН_Юр.
Самое время начать вытягивать из текста информацию. Сделаю массив из параметров и разделителей. В данном случае получиться так.
%[Наименование]%
, ИНН
%[%(ИНН,d+)%]%
, БИК
%[%(БИК,d+)%]%
, р/с —
%[%(РасчетныйСчет,d+)%]%
Теперь беру по 3 элемента массива. Именно 3, потому что при сборке паттерна после описания формата параметра обязательно должен быть разделитель. А так как параметр либо 1й, либо 2й элемент массива, то смотрю ещё и 3й.
Теперь, если в параметре прописаны отдельные форматы, название переменных, то парсю их через тот же RegExp, создаю тз с параметрами и шаблоном к ним, если отдельного формата нет, то тз будет с 1 строкой, где шаблон по умолчанию. Для каждого шаблона этого тз строю паттерн с разделителями. Если паттерн нашёлся, то вставляю имя параметра, значение паттерна, при этом со значение убираю последний разделитель, если он есть, а из разбираемой строки само значение.
То есть сначала программа пытается найти текст по следующему паттерну "#k8SjZc9Dxk *[0-9A-Za-zА-Яа-я\_"'() ]*", +ИНН + * *", находит
"ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "Ромашка", ИНН"
Видит, что ", ИНН " — это разделитель, удаляет его из результата, и результат удаляется из строки.
На следующем шаге в строке ", ИНН 1233455671, БИК 032405676, р/с — 10203810565780991778" ищет "#k8SjZc9Dxk *, +ИНН + *d+, +БИК + *", находит
", ИНН 1233455671, БИК"
удаляет что надо и так далее. На выходе получаю структуру
Наименование: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "Ромашка"
ИНН: 1233455671
БИК: 032405676
РасчетныйСчет: 10203810565780991778
Задача решена!
Конечно, упущены нюансы, что не ищу точное количество пробелов, а допускаю что их может быть не один, спец символы в разделителях заменяю и прочее. Но на описание основной идеи это не влияет.
функция ПараметрВТЗШаблонов(знач Параметр, ИсключатьПробел, RegExp, Все = ложь)
тз = новый ТаблицаЗначений;
тз.Колонки.Добавить("Параметр");
тз.Колонки.Добавить("Шаблон");
RegExp.pattern = "\%[[0-9A-Za-zА-Яа-я\_]*]\%";
Поиск = RegExp.Execute(Параметр);
если Поиск.count>0 тогда
//обычное название параметра
Параметр = СтрЗаменить(Параметр,"%[","");
Параметр = СтрЗаменить(Параметр,"]%","");
нов = тз.Добавить();
нов.Параметр = Параметр;
нов.Шаблон = ?(Все,".+","[0-9A-Za-zА-Яа-я\_.,""'()"+?(не ИсключатьПробел," ","")+"]*");
иначе
RegExp.pattern = "\%([0-9A-Za-zА-Яа-я_*+]*,[0-9A-Za-zА-Яа-я_[]().#k8SjZc9Dxk\*+{}|]*)\%";
Поиск = RegExp.Execute(Параметр);
Для инт=0 по Поиск.count-1 цикл
ПараметрШаблон = Поиск.item(инт).Value;
//формат такой (Параметр,шаблон), при этом запятые могут быть и в шаблоне
мас = СтрРазделить(ПараметрШаблон,",");
Параметр = СокрЛП(мас[0]);
Параметр = прав(Параметр,СтрДлина(Параметр)-2);
//Шаблон, слева %(, справа )%
Шаблон = сокрЛП(СтрЗаменить(ПараметрШаблон,Параметр,""));
//убирваю скобки
Шаблон = сред(Шаблон,3,СтрДлина(Шаблон)-4);
//убираю ,
Шаблон = сокрЛП(Шаблон);
Шаблон = прав(Шаблон,СтрДлина(Шаблон)-1);
нов = тз.Добавить();
нов.Параметр = Параметр;
нов.Шаблон = Шаблон;
КонецЦикла;
КонецЕсли;
возврат тз;
КонецФункции
функция ВставитьСлужебныеСимволы(Знач СтрокаШаблон)
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"","\");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"/","/");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"+","+");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"[","[");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"]","]");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"{","{");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"}","}");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"*","*");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,".",".");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон,"|","|");
СтрокаШаблон = СтрЗаменить(СтрокаШаблон," "," +");
возврат СтрокаШаблон;
КонецФункции
функция РазложитьШаблонПоПараметрам(Шаблон,RegExp)
RegExp.pattern = "\%[.+?]%";
Поиск = RegExp.Execute(Шаблон);
Мас = новый Массив;
если Поиск.count>0 тогда
Для инт = 0 по Поиск.count-1 цикл
ТекЭл = Поиск.item(инт);
если инт <= Поиск.count-2 тогда
//беру текущий и следущий параметр, на основе их длины и начала заполняю массив
СледЭл = Поиск.item(инт+1);
если инт = 0 и ТекЭл.FirstIndex <> 0 тогда
Мас.Добавить(лев(Шаблон,ТекЭл.FirstIndex));
КонецЕсли;
Мас.Добавить(ТекЭл.value);
//рассчитываем разделитель
разделитель = сред(Шаблон,(ТекЭл.FirstIndex+ТекЭл.Length+1),(СледЭл.FirstIndex-(ТекЭл.FirstIndex+ТекЭл.Length)));
если не разделитель = "" тогда
мас.Добавить(разделитель);
КонецЕсли;
иначе
//добавляю сам параметр и остаток строки, если он есть
Мас.Добавить(ТекЭл.value);
если ТекЭл.FirstIndex+ТекЭл.Length < СтрДлина(шаблон) тогда
Мас.Добавить(прав(шаблон,СтрДлина(шаблон)-(ТекЭл.FirstIndex+ТекЭл.Length)));
КонецЕсли;
КонецЕсли;
КонецЦикла;
иначе
Мас.Добавить(Шаблон);
КонецЕсли;
возврат мас;
КонецФункции
Функция РазложитьСтрокуПоШаблону(Знач Шаблон,Знач Текст, RegExpКомп = Неопределено) Экспорт
если RegExpКомп = Неопределено тогда
RegExp = новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Истина;
RegExp.MultiLine = Истина;
иначе
RegExp = RegExpКомп;
КонецЕсли;
//1 выделяю параметры в квадратных скобках
//нужна правильная скобочная запись, посчитаем
МассивРазделенныхПараметров = РазложитьШаблонПоПараметрам(Шаблон,RegExp);
//беру 3 элемента массива, если это начало строки или конец, то может быть и 2, и 1 элемент
//смотрю, какие элементы являются разделителем параметров
//возможны случаи: ПРР, ПРП, РПР - остальное ошибка и не обрабатывается
РасмТекст = Текст;
РасмТекст = СтрЗаменить(РасмТекст,Символы.ПС," ");
РасмТекст = СтрЗаменить(РасмТекст,Символы.ВТаб," ");
РасмТекст = СтрЗаменить(РасмТекст,Символы.ВК," ");
РасмТекст = СтрЗаменить(РасмТекст,Символы.НПП," ");
УбратьДвойныеПробелы(РасмТекст);
УбратьДвойныеПробелы(Шаблон);
СтруктураРеквизитов = новый Структура;
Пока МассивРазделенныхПараметров.Количество()<>0 цикл
Если МассивРазделенныхПараметров.Количество()>2 тогда
ПодСтр1 = МассивРазделенныхПараметров[0];
ПодСтр2 = МассивРазделенныхПараметров[1];
ПодСтр3 = МассивРазделенныхПараметров[2];
иначеЕсли МассивРазделенныхПараметров.Количество()>1 тогда
ПодСтр1 = МассивРазделенныхПараметров[0];
ПодСтр2 = МассивРазделенныхПараметров[1];
ПодСтр3 = "";
иначе
ПодСтр1 = МассивРазделенныхПараметров[0];
ПодСтр2 = "";
ПодСтр3 = "";
КонецЕсли;
//нужно определить, какая подстрока разделитель. Так как если разделитель " ", и идет за параметром,
//и параметр без отдельного шаблона, то в шаблоне параметра нужно удалить пробел.
ПодСтр1Разделитель = не (лев(ПодСтр1,2) = "%[" и прав(ПодСтр1,2)="]%");
ПодСтр2Разделитель = не (лев(ПодСтр2,2) = "%[" и прав(ПодСтр2,2)="]%");
ПодСтр3Разделитель = не (лев(ПодСтр3,2) = "%[" и прав(ПодСтр3,2)="]%");
//элементы не могут быть одновременно ни разделителями, ни параметрами
если МассивРазделенныхПараметров.Количество()>1 и не ((ПодСтр1Разделитель и ПодСтр2Разделитель) или не (ПодСтр1Разделитель и ПодСтр2Разделитель)) тогда
//ошибка
Найден = ложь;
Прервать;
ИначеЕсли МассивРазделенныхПараметров.Количество()>2 и не ((ПодСтр1Разделитель и ПодСтр2Разделитель) или не (ПодСтр1Разделитель и ПодСтр2Разделитель))
и не ((ПодСтр2Разделитель и ПодСтр3Разделитель) или не (ПодСтр2Разделитель и ПодСтр3Разделитель)) тогда
Найден = ложь;
Прервать;
КонецЕсли;
если не ПодСтр3Разделитель тогда
//нужно убрать этот параметр
ПодСтр3 = "";
ПодСтр3Разделитель = истина;
КонецЕсли;
//так как на одну позицию параметра возможны несколько вариантов отображения, то парсим варианты и проходим последователдьно
Если не ПодСтр1Разделитель тогда
МассивШаблонов = ПараметрВТЗШаблонов(ПодСтр1,СокрЛП(ПодСтр1)="",RegExp,МассивРазделенныхПараметров.Количество()=1);
ИначеЕсли не ПодСтр2Разделитель тогда
МассивШаблонов = ПараметрВТЗШаблонов(ПодСтр2,СокрЛП(ПодСтр2)="",RegExp);
иначе
МассивШаблонов = новый ТаблицаЗначений;
КонецЕсли;
Для каждого шаблон из МассивШаблонов цикл
СтрШаблон = "#k8SjZc9Dxk *";//начало строки
Если ПодСтр1Разделитель тогда
СтрШаблон = СтрШаблон + ВставитьСлужебныесимволы(ПодСтр1)+" *";
КонецЕсли;
СтрШаблон = СтрШаблон + шаблон.Шаблон;
Если ПодСтр2Разделитель тогда
СтрШаблон = СтрШаблон + ВставитьСлужебныесимволы(ПодСтр2)+" *";
КонецЕсли;
Если ПодСтр3Разделитель тогда
СтрШаблон = СтрШаблон + ВставитьСлужебныесимволы(ПодСтр3)+" *";
КонецЕсли;
RegExp.pattern = СтрШаблон;
Поиск = RegExp.Execute(РасмТекст);
если Поиск.count>0 тогда
НайдЗнач = Поиск.item(0).value;
//нужно удалить разделители.
Если ПодСтр1Разделитель тогда
НайдЗнач = прав(сокрЛП(НайдЗнач),СтрДлина(сокрЛП(НайдЗнач)) - СтрДлина(сокрЛП(ПодСтр1)));
КонецЕсли;
Если ПодСтр2Разделитель тогда
НайдЗнач = Лев(сокрЛП(НайдЗнач),СтрДлина(сокрЛП(НайдЗнач)) - СтрДлина(сокрЛП(ПодСтр2)));
КонецЕсли;
Если ПодСтр3Разделитель тогда
НайдЗнач = Лев(сокрЛП(НайдЗнач),СтрДлина(сокрЛП(НайдЗнач)) - СтрДлина(сокрЛП(ПодСтр3)));
КонецЕсли;
СтруктураРеквизитов.Вставить(шаблон.Параметр,сокрЛП(НайдЗнач));
РасмТекст = прав(РасмТекст,СтрДлина(РасмТекст)-(Поиск.item(0).Length - ?(ПодСтр2Разделитель,СтрДлина(ПодСтр2),?(ПодСтр3Разделитель,СтрДлина(ПодСтр3),0))));
иначе
Найден = ложь;
КонецЕсли;
КонецЦикла;
Если МассивРазделенныхПараметров.Количество()>1 и не ПодСтр2Разделитель тогда
//значит параметр в середине, удалить первый разделитель и параметр
МассивРазделенныхПараметров.Удалить(1);
МассивРазделенныхПараметров.Удалить(0);
Иначе
МассивРазделенныхПараметров.Удалить(0);
КонецЕсли;
если Найден = ложь тогда
прервать;
КонецЕсли;
КонецЦикла;
если Найден = ложь тогда
возврат новый Структура;
иначе
возврат СтруктураРеквизитов;
КонецЕсли;
КонецФункции
Процедура УбратьДвойныеПробелы(текст) Экспорт
Пока найти(текст," ")>0 цикл
текст = СтрЗаменить(текст," "," ");
КонецЦикла;
КонецПроцедуры
Пример использования: СтруктураРеквизитов = РазложитьСтрокуПоШаблону(Шаблон,текст);
за труд спасибо.
Но зачем эта возня с регуляркой в простейшем парсинге?
Что касается заявления об универсальности, то уже по структуре метода и комментариям понятно — вряд ли это будет работать стабильно.
Как по мне то проще:
РазделителиАтомов = » ,;:-«; // разделители пробел, запятая, точка с запятой и т.д.
мАтомыСтроки = СтрРазделить(СтрокаПарсинга, РазделителиТермов, Ложь); // Получаем атомы строки
// Обходим атомы и помещаем в структуру свойств, то что ищем
//…
Примерно так же я выдёргивал данные с Авито.
(1) Интересно где та, грань после которой лучше использовать регулярные выражения, а когда проще обойтись встроенными функциями 1С для работы со строками? Поделитесь опытом?
(3) составляющие «грани» :
1. простота чтения кода;
2. простота расширения кода;
3. стабильность результата;
4. отсутствие обещаний универсальности.
Если составляющие «грани» разъедены сложностью, то на месте грани образуется дыра.
(1)что касается универсальности, написал же, более-меннее. Естественно, есть контрпримеры, но для обычных задач хватит. И поподробнее что видно по структуре и комментариям?
Зачем нужно? Что бы простейший парсинг был ещё проще.
Что касается атомов и прочего, вот как раз таки это очень узкий способ, который подбирается для каждой строки отдельно, от этого и хотел уйти. Особенно когда нужно проверить является ли параметр числом, или записывается определенным форматом. Рег выражения позволяют универсиализировать и упростить этот момент.
Для тех, кто считает, что регулярки — это не от мира сего, есть вот такое:https://github.com/oscript-library/verbal-expressions
(6) .ЧтоНибудьНоНе(» «) — очуметь :о)
Может на первых порах это и проще дня начала понимания регулярных выражений, но лучше потренироватья день на кошечках в виде онлайн парсеров и тогда всё становится проще и понятней.