Как сделать простой парсер сложных запросов.
Первым делом скажу, что опишу не столько парсер запросов, сколько анализатор круглых скобок (). Все очень просто, каждый вложенный запрос берется в круглые скобки, поэтому нам остается проверить на наличие внутри скобок ключевого слова «ВЫБРАТЬ».
Для поиска скобок и ключевого слова частично был задействован механизм регулярных выражений. Все вложенные запросы в Дереве значений добавляются дочерними узлами к ветви запроса. Имя ветви – это имя вложенного запроса. Все параметры запроса будут, для удобства, копироваться в ветви вложенных запросов.
Для распарсивания используется процедура РазобратьЗапрос(), описанная ниже. Обратная процедура СобратьЗапрос() работает следующим образом. В текущем запросе вложенные запросы заменяются запросами из дочерних ветвей дерева запросов. Замена происходит только на текущем уровне, без рекурсии в глубину или на вершину, поэтому сборка происходит последовательно, начиная с нижнего уровня. Это сделано умышленно, давая возможность на каждом уровне проверить после сборки правильность выполнения запроса. Основное требование перед сборкой вложенных запросов – наличие тех же возвращаемых переменных которые были при разборке.
Процедуры и функции я тщательно прокомментировал, так что трудностей с кодом не должно возникнуть. Данный механизм был использован в сторонних консолях запросов. http://forum.infostart.ru/forum24/topic20163/message861068/#message861068
и для управляемых форм http://forum.infostart.ru/forum24/topic61272/message934313/#message934313
В статье приводится код, адаптированный для управляемых форм.
&НаКлиенте
Процедура РазобратьЗапрос(Команда)
// строка запроса
мТекущаяСтрока = ЭтаФорма.Элементы.ДеревоЗапросов.ТекущиеДанные;
//сам текст запроса
ПолныйТекстЗапроса = мТекущаяСтрока.ТекстЗапроса;
//Формирование массива с адресами скобок и ключевого слова "ВЫБРАТЬ"
Массив = ПозиционныйМассив(ПолныйТекстЗапроса);
//формирование дочерних ветвей строки нашего запроса
строки = мТекущаяСтрока.ПолучитьЭлементы();
строки.Очистить();
Для индекс = 0 по Массив.ВГраница() цикл
если Массив[индекс][1] = "(" тогда
индекс = СозданиеВетви(Массив, индекс, мТекущаяСтрока, ПолныйТекстЗапроса);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура СобратьЗапрос(Команда)
мТекущаяСтрока = ЭтаФорма.Элементы.ДеревоЗапросов.ТекущиеДанные;
ПолныйТекстЗапроса = мТекущаяСтрока.ТекстЗапроса;
//Формирование массива с с адресами скобок и ключевого слова "ВЫБРАТЬ"
Массив = ПозиционныйМассив(ПолныйТекстЗапроса);
строки = мТекущаяСтрока.ПолучитьЭлементы();
РазмерМассива = строки.Количество();
Если РазмерМассива > 0 тогда
//формируем массив вложенных запросов
Фрагмент = Новый Массив(РазмерМассива) ;
номерВетви = 0;
Для каждого строка из строки цикл
Фрагмент[номерВетви] = строка.ТекстЗапроса;
номерВетви = номерВетви + 1;
КонецЦикла;
//заменяем подзапрос в запросе на запрос из массива дочерних запросов
номерВетви = 0;
сдвиг = 0;
длинаТекста = СтрДлина(ПолныйТекстЗапроса);
Для индекс = 0 по Массив.ВГраница() цикл
если Массив[индекс][1] = "(" тогда
//поиск индекса соответствующей закрывающейся скобки
индексВозврата = ВГлубину(Массив, индекс);
//замена вложеного запроса
ПолныйТекстЗапроса = Лев(ПолныйТекстЗапроса,Массив[индекс][0]+1+сдвиг) + Фрагмент[номерВетви] + Прав(ПолныйТекстЗапроса, длинаТекста-Массив[индексВозврата][0]);
//вычисление смещения адресации
сдвиг = сдвиг + СтрДлина(Фрагмент[номерВетви]) - (Массив[индексВозврата][0]-Массив[индекс][0]-1);
номерВетви = номерВетви + 1;
индекс = индексВозврата;
КонецЕсли;
КонецЦикла;
мТекущаяСтрока.ТекстЗапроса = ПолныйТекстЗапроса;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ПозиционныйМассив(ПолныйТекстЗапроса)
//формирование массивов с адресами скобок и ключевого слова
Открытие = МассивВхождений(ПолныйТекстЗапроса, "\(");
Закрытие = МассивВхождений(ПолныйТекстЗапроса, "\)");
Выбрать = МассивВхождений(ПолныйТекстЗапроса, "ВЫБРАТЬ");
//объединение трех массивов в один с сортировкой адресов по возрастанию
Массив = СложитьМассивы(Открытие, Закрытие);
Массив = СложитьМассивы(Массив, Выбрать);
//удаление информации о не значимых скобках
УбратьВсеДоСкобки(Массив);
удалено = 1;
Пока удалено > 0 цикл
удалено = УбратьЛишниеСкобки(Массив);
КонецЦикла;
Возврат Массив;
КонецФункции // ПозиционныйМассив()
&НаКлиенте
Функция МассивВхождений(текст, подстрока, предел = Неопределено)
//для поиска используем регулярные выражения
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина; //Игнорировать регистр
RegExp.Global = Истина; //Поиск всех вхождений шаблона
RegExp.MultiLine = Истина; //Многострочный режим
RegExp.Pattern = подстрока;
Matches=RegExp.Execute(текст);
ЧислоВхождений=Matches.Count();
//определяемся с длиной массива вхождений
если предел = Неопределено тогда
предел = ЧислоВхождений;
иначеесли ЧислоВхождений < предел тогда
предел = ЧислоВхождений;
КонецЕсли;
Если предел > 0 тогда
//формирование массива с адресами вхождений подстроки
Массив = Новый Массив(предел,2);
Для СубСчетчик = 0 По предел-1 Цикл
SubMatch = Matches.Item(СубСчетчик);
Массив[СубСчетчик][0] = SubMatch.FirstIndex; //адрес
Массив[СубСчетчик][1] = SubMatch.Value; //значение
КонецЦикла;
иначе
Массив = Неопределено;
КонецЕсли;
Возврат Массив;
КонецФункции // МассивВхождений()
// объединение массивов с сортировкой по возрастанию
&НаКлиенте
Функция СложитьМассивы(Массив1, Массив2)
Если Массив1 = Неопределено тогда
ВГраница1 = -1;
иначе
ВГраница1 = Массив1.ВГраница();
КонецЕсли;
Если Массив2 = Неопределено тогда
ВГраница2 = -1;
иначе
ВГраница2 = Массив2.ВГраница();
КонецЕсли;
РазмерМассива = ВГраница1+ВГраница2+2;
Если РазмерМассива > 0 тогда
Массив = Новый Массив(ВГраница1+ВГраница2+2,2);
индекс1 = 0;
индекс2 = 0;
Для индекс = 0 по Массив.ВГраница() цикл
если индекс1 > ВГраница1 тогда
Массив[индекс][0] = Массив2[индекс2][0];
Массив[индекс][1] = Массив2[индекс2][1];
индекс2 = индекс2+1;
иначеесли индекс2 > ВГраница2 тогда
Массив[индекс][0] = Массив1[индекс1][0];
Массив[индекс][1] = Массив1[индекс1][1];
индекс1 = индекс1+1;
иначеесли Массив1[индекс1][0] < Массив2[индекс2][0] тогда
Массив[индекс][0] = Массив1[индекс1][0];
Массив[индекс][1] = Массив1[индекс1][1];
индекс1 = индекс1+1;
иначе
Массив[индекс][0] = Массив2[индекс2][0];
Массив[индекс][1] = Массив2[индекс2][1];
индекс2 = индекс2+1;
КонецЕсли;
КонецЦикла;
Возврат Массив;
иначе
Возврат Неопределено;
КонецЕсли;
КонецФункции // СложитьМассивы()
//убираем все до первой открывающейся скобки
&НаКлиенте
Функция УбратьВсеДоСкобки(Массив)
индекс = 0;
удалено = 0;
Пока индекс если Массив[индекс][1] = "(" тогда
Прервать;
иначе
Массив.Удалить(индекс);
удалено = удалено + 1;
КонецЕсли;
КонецЦикла;
Возврат удалено;
КонецФункции // УбратьВсеДоСкобки()
// удаление пар скобок не содержащих ключевого слова «ВЫБРАТЬ»
&НаКлиенте
Функция УбратьЛишниеСкобки(Массив)
индекс = 0;
удалено = 0;
Пока индекс если Массив[индекс][1] = "(" и Массив[индекс+1][1] = ")" тогда
Массив.Удалить(индекс);
Массив.Удалить(индекс);
удалено = удалено + 1;
иначе
индекс = индекс + 1;
КонецЕсли;
КонецЦикла;
Возврат удалено;
КонецФункции // УбратьЛишниеСкобки()
// рекурсивная функция для создания ветвей вложенных запросов
&НаКлиенте
Функция СозданиеВетви(Массив, ВходящийИндекс, Родитель, ПолныйТекстЗапроса)
Ветви = Родитель.ПолучитьЭлементы();
мТекущаяСтрока = Ветви.Добавить(); //ветвь подзапроса
СкопироватьПараметры(Родитель, мТекущаяСтрока); //копирование параметров запроса
Для индекс = ВходящийИндекс+1 по Массив.ВГраница() цикл
если Массив[индекс][1] = "(" тогда
//если у текущего запроса есть вложеный запрос, то делаем рекурсивно дочернюю ветвь
индекс = СозданиеВетви(Массив, индекс, мТекущаяСтрока, ПолныйТекстЗапроса);
иначеесли Массив[индекс][1] = ")" тогда
//встречен конец текущего запроса
Прервать;
КонецЕсли;
КонецЦикла;
запрос = Сред(ПолныйТекстЗапроса, Массив[ВходящийИндекс][0]+2, Массив[индекс][0]-Массив[ВходящийИндекс][0]-1);
//формирование имени подзапроса
если индекс < Массив.ВГраница() тогда
//фрагмент между закрывающейся и следующей скобкой
строка = Сред(ПолныйТекстЗапроса, Массив[индекс][0]+1, Массив[индекс+1][0]-Массив[индекс][0]);
иначе
//фрагмент запроса за закрывающейся скобкой
строка = Сред(ПолныйТекстЗапроса, Массив[индекс][0]+1, СтрДлина(ПолныйТекстЗапроса)-Массив[индекс][0]);
КонецЕсли;
имя = НайтиИмя(строка);
если имя = "БезИмени" тогда
имя = имя+индекс;
КонецЕсли;
мТекущаяСтрока.ИмяЗапроса = имя;
мТекущаяСтрока.ТекстЗапроса = запрос;
Возврат индекс;
КонецФункции // СозданиеВетви()
&НаКлиенте
Процедура СкопироватьПараметры(Родитель, мТекущаяСтрока)
мТекущаяСтрока.ПараметрыЗапроса.Очистить();
Для каждого парам из Родитель.ПараметрыЗапроса цикл
новыйПарам = мТекущаяСтрока.ПараметрыЗапроса.Добавить();
новыйПарам.ИмяПараметра = парам.ИмяПараметра;
новыйПарам.СпособУстановки = парам.СпособУстановки;
новыйПарам.ЗначениеПараметра = парам.ЗначениеПараметра;
новыйПарам.ТипЗначения = парам.ТипЗначения;
КонецЦикла;
КонецПроцедуры
//вычисление имени подзапроса по фрагменту за закрывающейся скобкой
&НаКлиенте
Функция НайтиИмя(текст)
если СтрДлина(СокрЛП(текст)) > 0 тогда
//анализ связки пробел-неПробел до 3 элементов
//идея следующая
//если 0-й адрес за закрывающейся скобкой ключевое слово "КАК"
//значит имя между 1-ым и 2-ым адресами
Массив = МассивВхождений(текст, "\s\S", 3);
если Массив = Неопределено тогда
имя = "БезИмени";
иначеесли Массив.ВГраница() = 2 тогда
как = ВРег(СокрЛП(Сред(текст, Массив[0][0]+1, Массив[1][0]-Массив[0][0])));
если как = "КАК" тогда
имя = СокрЛП(Сред(текст, Массив[1][0]+2, Массив[2][0]-Массив[1][0]-1));
иначе
имя = "БезИмени";
КонецЕсли;
иначеесли Массив.ВГраница() = 1 тогда
имя = СокрЛП(Сред(текст, Массив[1][0]+2, СтрДлина(текст)-Массив[1][0]-1));
иначе
имя = "БезИмени";
КонецЕсли;
иначе
имя = "БезИмени";
КонецЕсли;
Возврат имя;
КонецФункции // НайтиИмя()
//рекурсивная функция для поиска закрывающейся скобки, соответствующей скобке и индексом «ВходящийИндекс»
&НаКлиенте
Функция ВГлубину(Массив, ВходящийИндекс)
Для индекс = ВходящийИндекс+1 по Массив.ВГраница() цикл
если Массив[индекс][1] = "(" тогда
индекс = ВГлубину(Массив, индекс);
иначеесли Массив[индекс][1] = ")" тогда
Прервать;
КонецЕсли;
КонецЦикла;
Возврат индекс;
КонецФункции // ВГлубину()