/** * класс для работы с регулярными выражениями (сокращение в дальнейшем рег. выр.) содержание: содержит методы трёх поочерёдно идущих групп, в 1 группе методы идут от простых к сложным или составным(использующих простые). во 2 группе от более главных к менее. в 3 группе - по принадлежности к участию в методах из группы 1, а далее скорее по очередности применения итак три группы: 1. три главных интерфейсных метода: execAll, execAllPos и replace. 2. остальные интерфейсные методы: setReadOnly, humanstr_to_source, str_to_regexp, pcreUpd 3. служебные методы: execCond, execOne - методы относящиеся к execAll pos_zakr, create_tree_by_str, create_tree_by_str2, pos_karm, add_pos_karm - относящиеся к execAllPos create_ar_replace_shabl, callback_shabl, _replace - относящиеся к replace глоссарий: 0. вхождение - то что выбрано всем регулярным выражением исключая позитивный/негативный поиск вперед или назад. выдаётся нативным методом exec в массиве по индексу 0. 1. карманы - то что отмечено круглыми скобками в рег. выр. исключая (?:..) (?=...) (?!...) (?<=...) (? pcre, для replace oper.arrez -> o.arrez и oper.arrepl -> o.regshabl) oper - аргумент(через объект) параметр-переменная, для получения промежуточных результатов, или исходных значений, которые лучше сохранить, как например oper.oldpcre - исходное рег. выр. и shabl - текст шаблона замены. 10. Следование принципу совместимости с нативным порядком. Например, была мысль выдавать результаты карманов и и их позиций в отдельном от вхождения массиве(карманы с нулевого индекса без вхождения). Это не соответсвовало нативному порядку выдачи результата методом exec, как один массив, в котором всё в одном массиве вхождение - по индексу ноль, и карманы с индекса 1. Вначале написания так и начиналось, была написана первая верися функции pos_karm и add_pos_karm, но потом было решено сохранить нативный порядок для других функций, с целью не затрачивать время на конвертацию и быть привычней пользователю. Однако в двух вышеприведенных функциях, чтобы их не переделывать - сохранился прежний ненативный порядок отсчета вхождения и карманов, так вхождение обозначено -1 а карманы с 0, разумеется, для корректности происходит преобразование в нативный порядок на выходе. 11. Остальные неглавные интерфейсные методы тоже могут оказаться полезными, особенно setReadOnly, humanstr_to_source, и str_to_regexp, которые имеют прямое отношение к рег. выражениям. Есть также служебные методы, которые могут быть востребованы извне библиотеки: pos_karm - для преобразования RegExp в позициоориентированный, и create_ar_replace_shabl - для получения массива замены по шаблону, эти два метода - выдают промежуточные результаты главных интерфесных методов, и могут быть подставленны в них прямо, чтобы повторно не вычислялись, это актуально если последние в цикле, также могут быть востребованы, но в более узкоспециализрованных задачах: pos_zakr - ищет открывающие и закрывающие скобки по двум соответствующим рег. выр. и выдёт их в массиве особого формата, create_tree_by_str и create_tree_by_str2 - строят древесную структуру по структуре особого формата, что выдает pos_zakr по скобкам. Служебные методы execCond, execOne могут быть полезны для добавления новых фунцкиональностей в библиотеку рег. выр, например при создании полноценных поисков назад(без минусов 1 и 4 в пункте 4.). 12.Намеченная перспектива - пример возможности добавлять группу аргументов для сложных многаргументных функций, пример представлен в методе replace. Группировка служит для лучшей наглядности, и может позволит использовать одинаковые аргументы для разных групп. В этом примере в методе replace можно заменить аргументы во внешней группе frepl и regshabl на имена feach и reg соотвтетсвенно, какие есть во внутренней группе arg_arrez, т.к. те имеют сходный с ними смысл. Принцип и вложенность группировок будет совпадать с вызовом подфункций(дающие промежуточные результаы). Т.е. группа аргументов будет представлять аргументы для подфункции. Продемонстрированная в данном примере группа - arg_arrez вбирает в себя аргументы предназначенные для вызываемой подфункции execAll или execAllPos. Согласно с этим же принципом группируются и промежуточные результаты в подфункциях в промежуточном результате функции, в данном случае промежуточный результат execAll или execAllPos как группа arrez_oper в самом параметре переменной oper. примеры использования библиотеки приведены в конце класса-библиотеки * @author Хоменко Максим Сергеевич * @version 1. * @constructor * @this {cPcre} */ cPcre=function() { //group 1 Главные интерфейсные методы: execAll, execAllPos и replace. /** интерфейсный метод как и this.execOne, по тому же принципу ищет все вхождения после lastIndex в pcre str, но может находить много. еще можно установить limitVh, аргументы можно записать как свойства в одном объекте, на место первого аргумента данной функции. аргументы как и у execOne, плюс: @param {number} lastIndex - с какой позиции начинать, по умолчанию берется у pcre.lastIndex, или 0 если то - отсутсвует. @param {boolean} str_betw - надо ли вставлять строки в результате между вхождениями, по умолчанию false - не надо. @param {number} limitVh - лимит на колличество вхождений если pcre не глобальный то устанавливается в 1, если глобальный то по умолчанию 0 - т.е. анлимитно @return {array} массив из массивов вхождений (возвращаемых execOne), в перемежку со строками между вхождениями если установлен параметр str_betw, эти строки будут представляться как объект с двумя свойствами: str - само значение строки pos - позиция начала строки */ this.execAll=function(str, pcre, lastIndex, str_betw, limitVh, no_new_vh, pos_back, neg_back, feach,reg, oper) { //debugger; if(typeof str=='object') { var o=str; str=o.str; pcre=o.pcre; lastIndex=o.lastIndex; str_betw=o.str_betw; limitVh=o.limitVh; no_new_vh=o.no_new_vh; pos_back=o.pos_back; neg_back=o.neg_back; feach=o.feach; reg=o.reg; oper=o.oper; }; if(typeof pcre=='string') { var strpcre=pcre; pcre=this.str_to_regexp(pcre); if(oper){oper.strpcre=strpcre; oper.pcre=pcre}; }; if(!pcre.global)limitVh=1; if(limitVh==undefined || limitVh==0)limitVh=-1;//безлимитный if(lastIndex!=undefined)pcre.lastIndex=lastIndex; if(no_new_vh==undefined) { if(pcre.nvh==undefined) { no_new_vh=0;//если нигде не определено }else{ no_new_vh=pcre.nvh;//если определено в самом рег. выражении. }; }; pcre.nvh=no_new_vh; //нахождение первого вхождения var arrez=[]; //debugger; var sl=str.length; var count=0; var str_beg=0; //debugger; do{ var ar=this.execOne(str, pcre, no_new_vh, pos_back, neg_back, feach, reg); if(ar==null)break;//нет вхождений - покидаем var arrezl=(arrez.length>0)?arrez[arrez.length-1]:[]; //фиксируем вхождение если в первый раз нахождение, или простое //или следующее по границе //debugger; if(arrez.length==0 || no_new_vh==0 || (no_new_vh!=0 && arrezl.rvh<=ar.lvh )) { //-------записываем в массив var str_end=(no_new_vh>0)?ar.lvh:ar.index; if(str_betw)arrez.push({str:str.substring(str_beg, str_end),pos:str_beg});//добавляем строку перед вхождением arrez.push(ar);// str_beg=ar.rvh;//после вхождения //---------- count++;//считаем вхождения if(count==limitVh)break;//нашли ограниченное лимитом if(no_new_vh==0)continue;//lastIndex продвигается своим путём //pcre.lastIndex=ar.rvh;//ar.lvh+ar[ar.nvh].length+1 //новое вхождение нашлось - двигаем на шаг. }; //в противном случае - вхождение нашлось, //но новое вхождение перекрывается с предыдущим новым - фиктивно, двигаемся дальше. //debugger; var li=ar.index+1; if(li>sl)break; pcre.lastIndex=li; }while(true); if(str_betw)arrez.push({pos:str_beg, str:str.substr(str_beg)}); //arrez.nvh=no_new_vh; //остальные вхождения return arrez; }; /** интерфейсный метод метод такой же как и execAll, но в придачу если pcre-не расширенный, то выполнит преобразование pcre в расширенный формат(для вычисления позиций). @param {object} o - то же что и str у execAll в случае объектного задания аргументов. плюс свойство oper - объект выступающий как параметр-переменная, служит для сохранения промежуточных результатов полученных при выполнении функций, эти промежуточные результаты могут быть использованы в дальнейшем, например, для повторного вызова функции и без повторного вычисления промежуточной.(типа кэширование). в данном случае такой промежуточной операцией является преобразование pcre в модифицированный - позиционально ориентированный - oper.newpcre. Прежний - oper.oldpcre @return {array} что выдает execAll но обязательно с свойствами arpos(массив позиций вхкарманов в строке) в элементах вхождениях */ this.execAllPos=function(o) { if(typeof o.pcre=='string') { var strpcre=o.pcre; o.pcre=this.str_to_regexp(strpcre); if(o.oper){o.oper.strpcre=strpcre;}; }; if(o.pcre.nvh==undefined) { if(o.no_new_vh) { o.pcre.nvh=o.no_new_vh; }else{ o.pcre.nvh=0; }; }; var oldpcre=o.pcre; if(!o.pcre.opos){ o.pcre=this.pos_karm(o.pcre);//pcre уже с добавленными просчитывающими карманами и opos }; o.no_new_vh=o.pcre.nvh; var arrez=this.execAll(o); if(o.oper){//в объект oper как параметр переменную добавить дополнительную информацию о предварительных результатах функции o.oper.oldpcre=oldpcre; o.oper.newpcre=o.pcre;//чтобы выдать новый пкре }; return arrez;//add_pos_karm(arrez, o.pcre.opos);//модифицировать результат с позициями карманов. }; /** интерфейсный метод для выполнения замены, функция соединяющая результаты execAll или execAllPos c _replace. @param {object} o - то же что и str у execAllPos в случае объектного задания аргументов. плюс: str_betw - обязательно устанавливается в true, чтобы была возможность получать строки между вхождениями. bpos - надо ли вычислять позиции вхкарманов, по умолчанию - false не вычислять. при frepl:oPcre.callback_shabl и наличии позиций карманов в regshabl:'$kчисло', bpos устанавливается внутри самой replace в true *regshabl - может быть задан как строка или как объект-регистратор (в частности массив-шаблон). если как строка - то вычисляется по ней массив-шаблон с помощью функции create_ar_replace_shabl если объект-регистратор, передаётся как первый аргумент функции frepl если замена происходит по массиву-шаблону создаваемому функцией create_ar_replace_shabl то может нести массив-шаблон, имеет почти ту же роль, что и аргумент reg для execOne передаваемый как первый параметр функции feach(см. в аргументе функции execOne), но вызывается лишь после создания всего массива вхождений, это для того чтобы иметь возможность обратиться к строке после текущего вхождения и до следующего - $n по умолчанию вычисляется frepl - итератор для замены, по умолчанию используется функция callback_shabl играет почти ту же роль что и feach(см. в аргументе функции execOne). arrez - массив который мог быть получен ранее фунцией execAll или execAllPos или в параметре-переменной oper в свойстве arrez предыдущего вызова replace. По данному массиву-вхождений и строится замена. по умолчанию - этот массив образуется внутри функции вызовом функций execAll или execAllPos в свойстве oper добавляются свойства: arrepl - образованный массив-шаблон shabl - текст исходного шаблона arrez - массив вхождений arg_arrez - если есть то, служит для группировки аргументов в группу тех что были у execAllPos, Это позволяет называть одними именами аргументы в разных группах, в данном случае можно заменить аргументы во внешней группе frepl и regshabl на имена feach и reg соотвтетсвенно, какие есть во внутренней группе arg_arrez, т.к. те имеют сходный с ними смысл. в этом случае, а также если bpos:true к параметру-переменной oper включается свойство arrez_oper - объект, который был получен от промежуточного вычисления функции execAllPos или execAll. @return {string} преобразованная с перестановкой по регулярному выражению и шаблону строка. */ this.replace=function(o) { var ar; if(o.arrez) { ar=o.arrez; }else{ var arg_arrez=o; if(o.arg_arrez) { arg_arrez=o.arg_arrez;//возможность использования аргументов промежуточного вычисления if(o.str==undefined)o.str=arg_arrez.str; if(o.oper && arg_arrez.oper==undefined)arg_arrez.oper={};// надо составлять объект if(o.reg)o.regshabl=o.reg; if(o.feach)o.frepl=o.feach; }; arg_arrez.str_betw=true; if(o.frepl==undefined)o.frepl=this.callback_shabl;//стандартная функция замены. if(o.bpos==false && o.frepl==this.callback_shabl)//нужно ли рассмотреть необходимость менять значение bpos на true { if(typeof o.regshabl == 'string' &&(/\$k\d{1,2}/).test(o.regshabl)) { o.bpos= true; }else{ for(var i=1; i99){o.bpos= true; break;}; }; }; }; if(o.bpos){ ar=this.execAllPos(arg_arrez);//в oper получим новый и старый pcre }else{ ar=this.execAll(arg_arrez); }; if(o.oper){ o.oper.arrez=ar;//массив. if(o.arg_arrez) { o.oper.arrez_oper=arg_arrez.oper;//монтируем к параметру-переменной, //параметр-переменную промежуточного вычисления }; }; }; if(typeof o.regshabl=='string'){//если это шаблон var shabl=o.regshabl; o.regshabl=this.create_ar_replace_shabl(o.regshabl); if(o.oper) { o.oper.shabl=shabl;//текст шаблона o.oper.arrepl=o.regshabl;//массив сформированный по нему }; }; if(o.frepl==undefined)o.frepl=this.callback_shabl;//стандартная функция замены. //debugger; return this._replace(o.str, ar, o.frepl, o.regshabl); }; //group 2 остальные интерфейсные методы: setReadOnly, anystr_to_str, pcreUpd /** * интерфейсный метод * увы, но свойства 'global', 'ignoreCase', 'multiline','source' в сформированном * объекте RegExp изменить нельзя. В качестве обходного пути создаем новый RegExp, * точно такой же, но измененные свойства по массиву ar, * возможна добавления и модификация ненативных свойств для объекта RegExp * @param {RegExp} *regexp исходный объект регулярного выражения. * @param {object} *ar модифицированные свойства - массив свойство:значение. * @return {RegExp} - новый объект регулярного выражения */ this.setReadOnly=function(regexp,ar) { var arprop={global:1, ignoreCase:1, multiline:1,source:1};//все свойства readonly var arregexp=[]; for(var i in arprop ) { arregexp[i]=regexp[i]; }; for(var i in regexp) { arregexp[i]=regexp[i]; };//получили свойства объекта регекспа for(var pr in ar) { arregexp[pr]=ar[pr]; };//заменили их на новые //заполняем строку модификаторов var str_mod=''; for(var i in arregexp) { if(arregexp[i])//если true, включить свойство { switch(i) { case 'global': str_mod+='g'; break; case 'ignoreCase': str_mod+='i'; break; case 'multiline': str_mod+='m'; break; }; }; }; //создаем объект var r=new RegExp(arregexp['source'],str_mod); for(var i in arregexp){//присвоит все свойства не readonly if(!arprop[i])r[i]=arregexp[i]; } return r; }; /** забракованный интерфейсный метод комментирование любой строки для её отображения в "одной строке\n в кавычках", сгодится например для образования source регулярного выражения, но может и для других целей, например для передачи данных по хттп. @param {string} str - исходная строка @return {string} - строка для записи исходной строки в одной строчке */ /* this.anystr_to_str=function(str) { str=str.replace(/\\/g,'\\\\'); //str=str.replace(/"/g,'\\"'); //str=str.replace(/'/g,"\\'"); str=str.replace(/\n/g,'\\n'); str=str.replace(/\r/g,'\\r'); str=str.replace(/\t/g,'\\t'); str=str.replace(/\f/g,'\\f'); str=str.replace(/\v/g,'\\v'); //str=str.replace(/^|$/g,'"'); return str; };*/ /** интерфейсный метод пользователь может писать регулярное выражение в многострочном виде и с комментариями(удобство чтения), комментарий ставиться после # и занимает всё до конца строки, пробельные символы - удаляются. Сделано как на пхп с импользованием модификатора /x., но /x на яваскрипт не работает, поэтому создан метод. В самом тексте рег.выр. на конце строки (только при переходе на другую и после #) должно стоять \n\ в самом коментарии можно ставить #, но в содержимом надо его заменить на \x23 чтобы не был комментарием. Чтобы в рег. выр. были обратные слэши, их надо экранировать ими же, так и для цифры \d в рег. выр. в строке надо написать \\d. в содержимом надо заменить: пробел - \x20, табуляция - \x09, и знак решетки - \x23 @param {string} str - исходная строка @return {string} - строка уже может использоваться в рег. выр. в source */ this.humanstr_to_source=function(str) { return str.replace(/\s+|#[^\n]*(\n|$)/g, '');//удаляем коментарии и пробельные симвовлы }; /** Интерфесный метод Для создания объекта реглулярного выражения по строке, символ разделяющий модификатор включен в рассмотрение и модификатор х @param {string} str - исходная строка @return {RegExp} - регулярное выражение созданное по исходной строке */ this.str_to_regexp=function(str) { var pcre=/^(.)((?:.|\n)+)\1((?:([imgx])(?!(?:.)*?\4)){0,4})$/;//(?:.|\n)+) - т.к. \n не входит в . var ar=pcre.exec(str); if(!ar){ var e= new Error("Oшибка в рег. выр. на уровне ресурса/модификаторов"); e.name='oPcreError'; throw e; }; var source=ar[2]; var mod=ar[3]; var x=false; var f=function(){x=true; return '';}; mod=mod.replace('x',f); if(x)source=this.humanstr_to_source(source); return new RegExp(source, mod); }; /** интерфейсный метод не применяемая в этом классе. Функция служит, чтобы описать какими свойствами наделили объект RegExp добавляет к pcre дополнительную информацию в виде свойств @param *pcre {RegExp} - регулярное выражение @param nvh {number} - нормер кармана который будет новым вхождением, 0 - само вхождение по умолчанию. договорённость: Конец нового вхождение должен находиться на конце старого вхождения. @param pos_back {RegExp} - позитивный поиск назад (от начала нового вхождения) @param neg_back {RegExp} - негативный поиск назад (от начала нового вхождения) @param opos {opos} - вспомогательный объект для вычислений позиций карманов @return {RegExp} - новый объект регулярного выражения */ this.pcreUpd=function(pcre,nvh, pos_back, neg_back, opos) { if(no_new_vh==undefined) { pcre.nvh=0; }else{ pcre.nvh=nvh; }; if(pos_back)pcre.pos_back=pos_back; if(neg_back)pcre.neg_back=neg_back; if(opos)pcre.opos=opos; return pcre; }; //group 3 служебные методы. //subgroup 1 execCond, execOne - методы относящиеся к execAll /** служебный метод * поиск с условиями, слева направо * также как и exec но с добавлением условий в отборочной функции f @param {string} *str - исходная строка @param {RegExp} *pcre - регулярное выражение работает с /g учитывается lastIndex @param {function} *f - функция обработчик, в неё помещаются дополнительно накладываемые условия. следующий парметр reg - первый параметр функции f. 2-4 параметр функции f: str, pcre, массив карманов - результат exec. функция f должна возвращать true если нахождение удовлетворяет условиям, false - если нет, null - нет и нужно прекратить вызывать f вернув функцией null @param {object} reg - регистрационный объект - первый параметр функции f (предыдущий параметр). по умолчанию - пустой объект @return {array} массив что и в exec */ this.execCond=function(str,pcre,f,reg) { var prewind;//позиция вхождения на предыдущем шаге. var sl=str.length; var li; //не вышли ли за конец строки var pr=true;//если выдаст null надо незамедлительно закончить с null if(reg==undefined)reg={}; do{ //var oldLastIndex=pcre.lastIndex;//запомнили позицию var ar=pcre.exec(str); if(ar==null)return null;//надо выйти т.к. вперед искать бесполезно if(pr=f(reg,str,pcre,ar))return ar;//вызвать обработчик условие if(pr===null)return null;//надо срочно закончить //если pr==false то: li=ar.index+1;//на шаг(символ) вперед от найденного if(li>sl)return null;//вышли за рамки pcre.lastIndex=li; }while(true); }; /** служебный метод как execCond ищет одно вхождение, но с более конкретными условиями Начало нахождения с индекса lastIndex и идёт слева направо Условия: 1.удовлетворение поиску назад с условиями pos_back - позитивный, neg_back- негативный 2.удовлетворение условию функции feach играющюю ту же роль что и f в execCond. но в качестве третьего аргумента ar** может быть передан расширенный результат: кроме самих вхкарманов-строк, как значений массива и index-позиции вхождения имеются: lvh - позиция левого края вхождения rvh - позиция правого края вхождения nvh - индекс в массиве нового вхождения, номер кармана с 1, 0 - исходное вхождение. arpos - массив позций вхкарманов, если вычисление с карманами т.е. если есть свойство opos у объекта pcre @param *str {string} - исходная строка @param *pcre {RegExp} - регулярное выражение работает с /g учитывается lastIndex @param {number} no_new_vh - задает номер скобки рассматриваемую как новое вхождение, номера с 1, по умолчанию 0 - остается исходное вхождение. поиск назад производится всегда только от начала нового вхождения, или исходного если последнего нет(no_new_vh=0). при no_new_vh>0 образуется новое вхождение, её конец должен совпадать с концом исходного вхождения, это соглашение - для простого вычисления позиции нового вхождения (без полного вычисления позиций карманов). новое вхождение служит как один дополнительный позитивный поиск назад, его преимущество: возможность ссылаться на карманы до нового вхождения в остальной части рег. выр. @param RegExp} pos_back { - позитивный поиск назад (от начала нового вхождения) по умолчанию - не производится должен должен на конце содержать $ конец строки, и без модификатора global. т.е. /...$/ @param {RegExp} neg_back - негативный поиск назад (от начала нового вхождения) по умолчанию - не производится всё аналогично pos_back @param {function} *feach - функция которая может быть вызвана при нахождении, может использоваться как дополнительное ограничивающее условие или итератор (each). имеет ту же роль, возвращаемое значение и аргументы что и f в execCond. см. execCond, но последний третий аргумент ar может быть расширен: см выше ar** @param {object} reg - регистрационный объект - первый параметр функции feach (предыдущий параметр). по умолчанию пустой объект @return {array} массив что и в exec но с расширением см. выше ar** */ this.execOne=function(str, pcre, no_new_vh, pos_back, neg_back, feach,reg) {//debugger; if(no_new_vh==undefined) { if(pcre.nvh==undefined) { no_new_vh=0; }else{ no_new_vh=pcre.nvh; }; }; if(pos_back==undefined && pcre.pos_back!=undefined) { pos_back=pcre.pos_back; }; if(neg_back==undefined && pcre.neg_back!=undefined) { neg_back=pcre.neg_back; }; var lastIndex_new; var th=this; var f=function(reg, str, pcre, ar) { var ind=ar.index; var prestr; var arnewl= (ar[no_new_vh])? ar[no_new_vh].length :0; if(no_new_vh>0) { ind +=ar[0].length - arnewl; if(lastIndex_new && ind<=lastIndex_new)return false;//чтобы проверенное вхождение не повторялось для следующих проверок. Первая проверка - окончательная, т.к. в ней более полно учитывается жадность квантификаторов. lastIndex_new=ind; }; if(pos_back)//позитивный поиск назад { prestr=str.substr(0, ind); if(pos_back.length==undefined)//одно регулярное выражение { //pos_back.lastIndex=0; if(!pos_back.test(prestr))return false; }else{ for(var i=0; i0, надо с предостережением: в нём не использовать ^|символ, т.к. если вначале строке сработает ^, lastIndex пройдёт дальше, и символ в позитве назад будет другим, т.е. может оказаться вхождением. Например, поиск незаэкранированной скобки: /(?:^|[^\\](?:\\\\)*)(\()/ при nvh=1, на стоке начинающеся с (( поведёт себя некорректно, т.к. вторую скобку не возьмет во вхождение @param {RegExp} zakr - рег. выр. по поиску закрывающей скобки (всё аналогично открывающей) @return {array} - массив скобок, описан в create_tree_by_str */ this.pos_zakr=function(str, nach, zakr) { nach.lastIndex=0; var arleft=[]; // var f=function(d, str, pcre, ar) { arleft.push([ar.lvh, ar[0], 'l']); return true; }; this.execAll({str:str, pcre:nach, str_betw:false, no_new_vh:0,feach:f, pos_back:/(?:^|[^\\](?:\\\\)*)$/}); zakr.lastIndex=0; var arright=[]; var f=function(d, str, pcre, ar) { arright.push([ar.lvh, ar[0], 'r']); return true; }; this.execAll({str:str, pcre:zakr, str_betw:false, no_new_vh:0,feach:f , pos_back:/(?:^|[^\\](?:\\\\)*)$/}); var ar=arleft.concat(arright);//всё в кучу //функция сортировки элементов массива var f=function sIncrease(i, ii) { // По возрастанию //первичное условие if (i[0] > ii[0])return 1;//надо поменять порядок if (i[0] < ii[0])return -1;//точно не менять //далее можно вторичные, но смысла нет. return 0;//совсем не определенность } ar.sort(f); return ar; }; /** служебный используемый метод, созданный ранее следующего create_tree_by_str2, и хуже него создает древесную структуру по строке и массиву скобок в строке. @param {string} *str исходная строка @param {array} *ar массив скобок в исходной строке, состоит из элементов - массивов со следующей струтурой: 0 - позиция скобки в строке 1 - строка содержащая текст скобки, 2 - 'l'|'r' левая или правая скобка этот массив можно получить функцией pos_zakr @param {boolean} emptystr - надо ли вставлять пустые строки в структуру? по умолчанию true - надо вставлять. минус - в экономичности - т.е. пустые строки как лишние элементы в массиве плюс - в быстром обращении(по позиции) к нужной внутренней скобке( по i*2+1), т.к. тогда четко установлено что строки идут поочередно с массивами, начинаются и заканчиваются непременно строкой @return {array} - иерархическая древесная структура, отражающая вложенность скобок и содержимое в них элементы этой структуры - структуры такого же типа (скобки и их содержимое), и строки между структурами, в конец элемента любой структуры такого типа помещается информационный объект, он содержит root - ссылка на структуру корень parent - ссылка на структуру - родителя, у корня нет родителя lsk - левая скобка - массив со структурой элементов в массиве аргумента *ar rsk правая скобка - той же структуры cur_depth - глубина этой скобки, глубина измеряется с 0, т.е дочерняя скобка root имеет глубину 0 max_depth - глубина самой глубокой вложенной скобки в эту скобку i_par - индекс(позиция) скобки в массиве этой структуры */ this.create_tree_by_str=function(str,ar,emptystr) { //debugger; if(emptystr==undefined)emptystr=true; ar.unshift([0,'','l']); ar.push([str.length,'','r']); //debugger; var tree; var stack=[]; var curpos=0, max_depth=0, cur_depth=-1; for(var i=0; i-1) { stack[cur_depth].push(otr);//строку в массив, если не пустая }; curpos=pos+sk[1].length;//символ следующий после скобки if(sk[2]=='l') { var ae=[[sk]];//новый массив в дереве -- if(cur_depth>-1) { stack[cur_depth].push(ae);//заносим в древесный массив }; stack.push(ae);//заносим в глубинный стек, глубина повышается max_depth=++cur_depth; stack[cur_depth][0][1]=cur_depth;//--- }else if(sk[2]=='r') { if(cur_depth==-1){break;};//закрывать нечего в строке //создаем информативный объект в конце списка. var azakr=stack[cur_depth]; var o={ root: stack[0], lsk:azakr[0][0],//-- rsk:sk, cur_depth:cur_depth, max_depth:stack[cur_depth][0][1]//-- }; if(cur_depth>0) { o.i_par=stack[cur_depth-1].length-2;//-2 т.к. удалим первый элемент. o.parent=stack[cur_depth-1]; o.parent[0][1]=Math.max(o.parent[0][1],o.max_depth);//-- }; azakr.shift();//удаляем первый элемент запоминающий инфомацию, для резюме azakr.push(o);//и добавляем вконец элемнт с резюмирующей информацией var last=stack.pop();//глубина понижается. --cur_depth; if(cur_depth==-1)tree=last; }; }; if(cur_depth>-1){//открывающих больше закрывающих. tree=stack[0]; tree.push({error: 'moreleft'}); }else if(i-1) { stack[cur_depth].push(otr);//строку в массив, если не пустая }; curpos=pos+sk[1].length;//символ следующий после скобки if(sk[2]=='l') { var ae=[];//новый массив в дереве --[sk] ae.lsk=sk; if(cur_depth>-1) { stack[cur_depth].push(ae);//заносим в древесный массив }; stack.push(ae);//заносим в глубинный стек, глубина повышается max_depth=++cur_depth; stack[cur_depth].max_depth=cur_depth;//--- }else if(sk[2]=='r') { if(cur_depth==-1){break;};//закрывать нечего в строке //создаем информативный объект в конце списка. var azakr=stack[cur_depth]; azakr.root=stack[0]; azakr.rsk=sk; azakr.cur_depth=cur_depth; if(cur_depth>0) { azakr.i_par=stack[cur_depth-1].length-1;//позиция в родительстком массиве azakr.parent=stack[cur_depth-1]; azakr.parent.max_depth=Math.max(azakr.parent.max_depth, azakr.max_depth);//-- }; var last=stack.pop();//глубина понижается. --cur_depth; if(cur_depth==-1)tree=last; }; }; if(cur_depth>-1){//открывающих больше закрывающих. tree=stack[0]; tree.push({error: 'moreleft'}); }else if(i0){ var treei_1=tree[i-1]; var treei_1=treei_1[treei_1.length-1]; var lsk=treei_1.lsk[0]; var rsk=treei_1.rsk; rsk=rsk[0]+rsk[1].length; arnewstr.push(new_s.substring(lsk,rsk)); }; var treei=tree[i];//строка в которой обработаем \dd var f=function(s,k1){ return '\\'+(arkarm[parseInt(k1)-1]+1); }; //debugger; treei=treei.replace(/(?:\\)(\d{1,2})/g,f); arnewstr.push(treei); }; //debugger; new_s=arnewstr.join(''); //debugger; var newpcre=this.setReadOnly(pcre,{source:new_s}); //new RegExp(new_s,'g'); //debugger; newpcre.opos={arpos: arpos, arkarm:arkarm, nvh_old:newpcre.nvh}; if(newpcre.nvh>0)newpcre.nvh=arkarm[newpcre.nvh-1]+1;//по новому считаем вхождение т.к. добавлены скобки. return newpcre; }; /** служебный метод. вызывается в функции execOne к результату exec, с применным позицоориентированным regexp. добавить позиции карманов по объекту - свойство opos модифицированного RegExp полученного функцией pos_karm. изменить содержимое карманов - убрать вспомогательные, как будто был применен не позициориентированный regexp. @param {array} arrezi - массив вхкарманов строк - результат exec @param {object} o_pos_karm - свойство opos см. функцию pos_karm @return {array} arrezi, но с удалёнными элементами: вспомогательные карманы, измененным nvh, и добавленным массивом позиций arpos вхкарманов в строке, lvh и rvh - не меняются */ this.add_pos_karm=function(arrezi,o_pos_karm) { //for(var i=1; i$&" из нё получаем массив удобный для функции обработки по этому шаблону callback_shabl @param {string} *shabl - строка шаблон @return {array} массив шаблон массив шаблон представляет собой элементы из целых чисел( диапазон -9 до 199), знака '$' и строк между ними - строк в строке shabl между элементами начинающихся с $ обозначения числам даётся в комментарии ниже в самой функции. например -9 - означает - колличество карманов: case 'c': arstr.push(-9);break;//всего колличество карманов(с исх. вхождением)* */ this.create_ar_replace_shabl=function(shabl) { var pshabl=/(?:\$((k?)(\d{1,2})|(?!k)\D))/g; var ar=this.execAll(shabl,pshabl,0,true); var arstr=[];//формируем массив удобный для функции for(var i=1; i>>1,//счетчик вхождений, его номер в этом порядке prev_betw:ar[i-1].str, //строка от пред. до этого вхождение next_betw:ar[i+1].str, //строка от этого до предыдущего before_vh:str.substr(0, v.lvh), //строка всё до вхожденя after_vh:str.substr(v.rvh) //строка всё после вхождения }; var s=f(o); if(s===null){ arr.push(v[v.nvh]);//поставим то что было break; }else{ arr.push(s);//вызываем функцию обработчик }; }; //debugger; arr.push(str.substr(v.rvh));//и остаток строки дописываем return arr.join('');//сливаем массив в строку }; };//конец класса /** Демонстрация применения библиотеки */ (function(){ oPcre=new cPcre(); var str='ac abc ad abc ad ac ac abc abc ac abbc abc '; var pcre=/(a(?:b)*c)+?(abc)/g;//второй карман - вхождение //удаляем пробелы которые служили для лучшей визуальности //(отделения групп символов на которые акцентрируется внимание). str=str.replace(/ /g,''); //lastIndex:3>2 поэтому первый ^ас abc исключается. //str_betw:true - нужны строки в массиве между вхождениями //надо найти не более 2 вхождений(третье и четвертое будет в этом примере), // поэтому последнее(пятое abc) ac abbc abc$ исключается //no_new_Fvh:2 в качестве вхождения будет второй карман, а до него - как позитивный поиск назад, // поэтому второе ad abc не проходит, а третье и четвертое abc аbc - пройдут даже если четвертое слитно с третьей. //получим массив из 5 элементов - 2 и 4 - объекты вхождения, 1,3,5 - объекты строки между вхождениями var ar=oPcre.execAll({str:str, pcre:pcre, lastIndex:3, str_betw:true, limitVh:2, no_new_vh:2}); //debugger;//можно раскоментировать какие нужно debugger и изучить массивы с помощью firedebug //тоже самое, но только сюда добавляются позиции - массив arpos в двух объектах вхождения в массиве var o={};//для взятия промежуточного результата var ar=oPcre.execAllPos({str:str, pcre:pcre, lastIndex:3, str_betw:true, limitVh:2, no_new_vh:2,oper:o}); var newpcre=o.newpcre;//взяли pcre нового позициориентированного формата //debugger; //результат тот же, только теперь уже pcre не пересчитывается, и результат поэтому выдается быстрее, // и еще no_new_vh:2 можно не писать т.к. эта информация уже в newpcre, однако нужно lastIndex:3, // т.к. проведенный поиск увеличил его до конца последнего найденного вхождения, // и нужно востановить для такого же результата. var ar=oPcre.execAllPos({str:str, pcre:newpcre, lastIndex:3, str_betw:true, limitVh:2}); //debugger; //тоже самое только добавлен neg_back:/abc$/ справедливо и для execAll // как следствие - вместо четвертого(в str) вхождения abc вторым(в массиве ar) будет выбрано пятое, // т.к. четвертое - не удовлет негативному поиску назад, т.к. в перед четвертым вхождении была неугодная abc. //еще пример, но позитивного поиска назад можно увидеть в методе pos_zakr этой библиотеки, в неё передаётся ///(?:^|[^\\](?:\\\\)*)$/ чтобы отсеивать экранированные слэшами вхождения (круглые и квадратные скобки) //кроме того можно написать [neg_back:/abc$/] или [pos_back:/(?:^|[^\\](?:\\\\)*)$/] результат тот-же, //т.е. в каждом из этих двух видов поиска назад можно задать больше чем одно регулярное выражение var ar=oPcre.execAllPos({str:str, pcre:newpcre, lastIndex:3, str_betw:true, limitVh:0, neg_back:/abc$/}); //debugger; //использование reg и feach для дополнительного ограничения, или прочей обработки сходу. справедливо и для execAll //как видно результат такой как и предыдущий, только сформирован исключением вхождением на 19 позиции, //как начало вхождения четвертого вхождения abc, именно на ней был неугодный /abc$/ при просмотре назад. //можно вместо return false поставить return null и тогда не будет выдано второе вхождение //(поиск прекратится с вхождения 19 позиции, а сам он не будет включен) var reg={vh:19}; var feach=function(o, str, pcre, ar){if(o.vh==ar.lvh){ return false;}; return true;}; var ar=oPcre.execAllPos({str:str, pcre:newpcre, lastIndex:3, str_betw:true, limitVh:0, reg:reg, feach:feach}); //debugger; //для замены можно использовать предыдущий результат (или от функции execAll, но обязательно с str_betw=true), //вхождения в данном случае будет обведены в круглые скобки var strrez=oPcre.replace({arrez:ar, str:str, regshabl:'($&)'}); //debugger; //здесь показано что replace может принять все значения которые принимает execAll или execAllPos, //и выполнить их задачу - получить массив вхождений перед образованием строки с перестановками //можно также было написать pcre:newpcre и убрать тогда no_new_vh:2 как это можно для execAll или execAllPos, //тогда можно было бы убрать и bpos:true т.к. newpcre - уже с позициями карманов. //и не стоит решать нужно ли их вычислять для повышения производительности или нет. //если добавить str_betw:true или str_betw:false, то всё равно массив будет включать строки между вхождениями, //поэтому смысла добавлять ни то ни другое - нет. //в объект o свойства oper добавляются еще свойства arrrez - массив вхождений, и arrepl - массив обработанный шаблон, //как промежуточные результаты, чтобы их можно было использовать в дальнейшем в функциях replace не вычисляя всё заново. //Здесь можно было бы опустить bpos - тогда не будет включены позиции карманов в массиве вхождений, //и их нельзя будет использовать в шаблоне замены regshabl как $ki где i - номер кармана в регулярном выражении, //нельзя также будет их получить в функциях frepl и feach. var o={}; var strrez=oPcre.replace({str:str, pcre:pcre, lastIndex:3, no_new_vh:2, limitVh:0, bpos:true, reg:reg, feach:feach, regshabl:'($&)', oper:o}); //debugger; //показано как можно использовать свойства arrez и arrepl - полученных на объекте oper - параметр-переменной. var strrez=oPcre.replace({arrez:o.arrez, str:str, regshabl:o.arrepl}); //debugger; //replace допускает помимо обычного и групповое задание аргументов. //показано как можно группировать аргументы, сдесь группа arg_arrez аргументов использующихся в качестве аргументов //промежуточной функции execAll или execAllPos. Группировка для перспективы если будет дальнейшее усложнения //структуры вызовов функций и передачи им аргументов, //например станет возможным в разных группах задавать одни и теже названия аргументов. //в данном случае можно было бы в replace назвать //feach и frepl одинаково - feach, а также reg и regshabl - reg, т.к. они находятся в разных группах - внешней и внутренней, //но было решено сделать replace совместимым с одноуровневым(без групп) приёмом всех аргументов, потому аргументы //названы по разному чтобы не конфликтовали. //При группировке также группируются и промежуточные значения выдаваемые в параметре переменой oper, //в данном случае объект oper будет включать объект arrez_oper, который был параметром переменной в функции // execAll или execAllPos. var strrez=oPcre.replace({arg_arrez:{str:str, pcre:pcre, lastIndex:3, no_new_vh:2, feach:feach, limitVh:2, reg:reg}, bpos:true, regshabl:'($&)', oper:o}); //debugger; //проверка шаблона со всеми возможными обозначениями. //еще показано что в случае bpos:false, frepl:oPcre.callback_shabl и наличии позиций карманов в regshabl:'$kчисло', //то для предотвращения конфликта bpos устанавливается внутри самой replace в true var strrez=oPcre.replace({arg_arrez:{str:'acabcbkacabcbk', pcre:(/a(([ab])(c)\2)/g), no_new_vh:1, limitVh:2}, bpos:false, regshabl:"[($0$1$2$3)($k0$k1$k2$k3)($$$c$&$j$q$i)($p$n$`$')]", oper:o}); //debugger; //вызываем собственные функцию frepl и объект regshabl вместо шаблона. //в отличае от шаблона возможны любые алгоритмические подстановки, например o.reg.a[o.no_vh%2] //и в отличии от нативного replace возможно использование дополнит. параметров, например o.arvcarm[o.nvh]. //функция frepl может выдавать null в этом случае операция замены сбрасывается. //еще показано как были заменены свойства frepl и regshabl на имена feach и reg соотвтетсвенно, какие есть во //внутренней группе arg_arrez, т.к. те имеют сходный с ними смысл, это преимущество использования //групп аргументов. var frepl=function(o){return '['+o.reg.a[o.no_vh%2]+o.arpos[1]+'('+o.arvcarm[o.nvh]+')]';}; //var frepl=function(o){return ((o.no_vh%2==0)?'['+o.reg.a[o.no_vh%2]+o.arpos[1]+'('+o.arvcarm[o.nvh]+')]':null);}; //выдача null var regshabl={a:['слово:','word:']}; var strrez=oPcre.replace({arg_arrez:{str:str, pcre:pcre, lastIndex:3, no_new_vh:2, feach:feach, limitVh:2, reg:reg}, bpos:true, reg:regshabl, feach:frepl, oper:o}); //debugger; //пример использования humanstr_to_source и setReadOnly var strpcre='\ ^\\s*( #начало строки\n\ (\\d+) #день\n\ \\s* \\x23 \\s* #разделитель\n\ (\\d+) \ \\s* \\x23 \\s* # разделитель\n\ (\\d+) #год\n\ )\\s*$ #конец строки\n\ '; var strpcre2=oPcre.humanstr_to_source(strpcre); var pcre=/a/mg; //RegExp(strpcre2,'gm'); var pcre=oPcre.setReadOnly(pcre, {source:strpcre2}); var ar=pcre.exec('Привет \n 12 #11 # 1931\nПока'); //debugger; //продемонстрирован метод str_to_regexp, он преобразует строку в регулярное выражение //причем допускается модификатор x, и кроме того как и в пхп устанавливается любой символ //кроме парных скобок имеющих разный вид(зеркально отображенный по горизонтали) //идущий в самом начале в качестве разделителя ресурса от модификаторов, в данном случае был поставлен | var pcre=oPcre.str_to_regexp('|\ ^\\s*( #начало строки\n\ (\\d+) #день\n\ \\s* \\x23 \\s* #разделитель\n\ (\\d+) \ \\s* \\x23 \\s* # разелитель \n\ (\\d+) #год\n\ )\\s*$ #конец строки\n\ |mxg'); var ar=pcre.exec('Привет \n 12 #11 # 1931\nПока'); //debugger; //Здесь продемонстрирована возможность писать строку вместо регулярного выражения в параметре pcre //аналогично можно писать в execAll и execAllPos var o={}; var strrez=oPcre.replace({ arg_arrez:{ str:'Привет \n 12 #11 # 1931\nПока', pcre:'|\ ^\\s*( #начало строки\n\ (\\d+) #день\n\ \\s* \\x23 \\s* #разделитель, этот комментарий обязателен\n\ (\\d+) \ \\s* \\x23 \\s* # разделитель\n\ (\\d+) #год\n\ )\\s*$ #конец строки\n\ |mxg' }, bpos:true, reg:"Год:$4 Месяц: $3 День: $2", oper:o}); //debugger; });//чтобы продемонстрировать применение библиотеки вставьте '()' перед ';//чтобы продемонстрировать'