Каталог решений - Парсер строки по шаблону с помощью RegExp 

Парсер строки по шаблону с помощью RegExp 

Парсер строки по шаблону с помощью RegExp 

В наличии

Возникла задача загрузки отчетов по эквайрингу в формате Экселя. Так как юр. лицо не одно, то информация, на какую организацию загружать, находится в самом файле в виде строки в определённом формате. Так как мне было лень писать парсер для конкретной строки с данными организации, написал более-менее универсальный парсер, который позволяет вытянуть данные по заданному шаблону.

Категория:

Описание

ВНИМАНИЕ!!!! Требуется понимать, что такое регулярные выражения. Если данного знания нет, то гугл подскажет.

Сразу на примере. Есть строка типа.

ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "Ромашка", ИНН 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 строкой, где шаблон по умолчанию. Для каждого шаблона этого тз строю паттерн с разделителями. Если паттерн нашёлся, то вставляю имя параметра, значение паттерна, при этом со значение убираю последний разделитель, если он есть, а из разбираемой строки само значение.

То есть сначала программа пытается  найти текст по следующему паттерну "^ *[0-9A-Za-zА-Яа-я\_\"\’\(\)\ ]*", +ИНН + * *", находит

"ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "Ромашка", ИНН"

Видит, что ", ИНН " — это разделитель, удаляет его из результата, и результат удаляется из строки.

На следующем шаге в строке ", ИНН 1233455671, БИК 032405676, р/с — 10203810565780991778" ищет "^ *, +ИНН + *\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А-Яа-я_\[\]\(\)\.\^\\\*\+\{\}\|]*\)\%";
		Поиск = 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);				
		иначе
			МассивШаблонов = новый ТаблицаЗначений;
		КонецЕсли; 
		
		Для каждого шаблон из МассивШаблонов цикл
			СтрШаблон = "^ *";//начало строки
			Если ПодСтр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 цикл
		текст = СтрЗаменить(текст,"  "," ");
	КонецЦикла;  	
КонецПроцедуры

Пример использования: СтруктураРеквизитов = РазложитьСтрокуПоШаблону(Шаблон,текст);

has been added to your cart:
Оформление заказа