Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.190.156.80] |
|
Сообщ.
#1
,
|
|
|
Вставка переменных в строку
Функция для подстановки произвольных значений в строку на место переменных вида %name% (ограничивающие символы могут быть любыми, одинаковыми или нет, но обязательно должны присутствовать оба). Пригодится для задания шаблонов названий, имён файлов (например, при генерации из тегов), HTML страниц (при создании отчётов). Нечто подобное можно сделать через Format('bla %0:s bla %1:s',[s1, s2]), но этот метод обладает низкой наглядностью для пользователя (все переменные будут обозначаться %1, %2, и т.д.). Представленная реализация даёт неограниченные возможности для манипуляций (условное преобразование итогового значения, подстановка в зависимости от предыдущих подстановок и т.п.) Для D2009+ используется reference to function, что даёт следующие плюшки: не нужно объявлять отдельную функцию-обработчик; доступны все переменные вызывающей функции. uses StrUtils; // для PosEx {$I 'Compilers.inc'} // для определения версии компилятора (расширенная aka 2009+ или обычная) // брать отсюда http://code.google.com/p/virtual-treeview/source/browse/trunk/Common/Compilers.inc?spec=svn235&r=235 // либо просто удалить дефайны, если используется только один компилятор // Прототип callback для ReplaceTokens. Vars - параметры, которые переданы в // ReplaceTokens из вызывающей функции. Для того, чтобы // компиляторы ниже RAD2009 могли юзать переменные из вызывающей функции // Вход: Token - полученный токен // Выход: Token - результат замены, который надо подставлять в исходную строку // Result = True, если замена произведена, и False, если нет. В этом случае // то, останется ли токен или будет удалён, определяется флагом EatUnmatched TReplTokenCallback = {$IFDEF COMPILER_12_UP}reference to{$ENDIF} function(var Token: string{$IFNDEF COMPILER_12_UP}; Vars: array of const{$ENDIF}): Boolean; // Находит все вхождения строк между символами TokenStart и TokenEnd в строке Patt // и заменяет их на результат выполнения функции Callback // Флаг EatUnmatched определяет, удалять ли вхождения, для которых Callback // возвращает False (если он = False, такие вхождения остаются как были) function ReplaceTokens(const Patt: string; TokenStart, TokenEnd: Char; EatUnmatched: Boolean; Callback: TReplTokenCallback{$IFNDEF COMPILER_12_UP}; Vars: array of const{$ENDIF}): string; var token: String; curr, txt_beg, pos_beg, pos_end: Integer; begin curr := 1; txt_beg := 1; Result := ''; // пока в шаблоне есть токены while curr <= Length(Patt) do begin // выделяем токен (символы, обрамлённые TokenStart и TokenEnd) pos_beg := PosEx(TokenStart, Patt, curr); if pos_beg = 0 then Break; pos_end := PosEx(TokenEnd, Patt, pos_beg+1); if pos_end = 0 then Break; token := Copy(patt, pos_beg+1, pos_end-pos_beg-1); // вызываем callback функцию и действуем по её результатам if not Callback(token{$IFNDEF COMPILER_12_UP}, Vars{$ENDIF}) then if EatUnmatched // съедаем или оставляем необработанный токен then Result := Result + Copy(Patt, txt_beg, pos_beg-txt_beg) else begin Inc(curr); Continue; end else Result := Result + Copy(Patt, txt_beg, pos_beg-txt_beg) + token; // двигаемся дальше curr := pos_end+1; txt_beg := pos_end+1; end; // добавляем оставшийся хвост текста после последнего токена Result := Result + Copy(Patt, txt_beg, Length(Patt)); end; Применение const TokenStart = '<'; TokenEnd = '>'; FnPatt = 'FILENAME'; // Заменяет все вхождения подстановки <filename> на имя файла, причём // отслеживает регистр подстановки: преобразовывает в нижний, верхний либо // оставляет как есть, если исходный шаблон имеет смешанный регистр {$IFDEF COMPILER_12_UP} // Для расширенного компилятора объявляем функцию внутри function ReplaceFn(const Patt, fn: string): string; begin Result := ReplaceTokens(Patt, TokenStart, TokenEnd, False, function(var Token: string): Boolean begin Result := True; // смотрим, в каком регистре токен, заменяем на // имя файла в соответствующем регистре if Token = UpperCase(FnPatt) then Token := UpperCase(fn) else if Token = LowerCase(FnPatt) then Token := LowerCase(fn) else if LowerCase(Token) = LowerCase(FnPatt) then Token := fn else Result := False; end); end; {$ELSE} // Для обычного - объявляем дополнительную function ReplFnCallbackFn(var Token: string; Vars: array of const): Boolean; var SrcFn: string; begin SrcFn := PChar(Vars[0].VPChar); Result := True; // смотрим, в каком регистре токен, заменяем на имя файла в соответствующем регистре if Token = UpperCase(FnPatt) then Token := UpperCase(SrcFn) else if Token = LowerCase(FnPatt) then Token := LowerCase(SrcFn) else if LowerCase(Token) = LowerCase(FnPatt) then Token := SrcFn else Result := False; end; function ReplaceFn(const Patt, Fn: string): string; begin Result := ReplaceTokens(Patt, TokenStart, TokenEnd, False, ReplFnCallbackFn, [Fn]); end; {$ENDIF} res := ReplaceFn('bla <filename> bla <FilEnaMe> bla <FILENAME>', 'c:\WINDOWS\temp'); Функция и построенные на ней другие функции верой и правдой служат уже с полгода. Наличие аналога в Генофонде мне неизвестно. Возможно, кому-нибудь пригодится. |
Сообщ.
#2
,
|
|
|
С функциями Replace* есть одна маленькая беда: если строка очень большая (>10Mb) с большим количеством замен, то операторы типа "Result := Result + ..." очень сильно тупят из-за частого выделения памяти под строку. Бывали случаи, что память становилась настолько фрагментированной, что системе не удавалось выделить память для выполнения этой операции.
Выкручивались предварительным подсчётом замен, вычислением и одноразовой установкой размера результирующей строки и, с помощью MOVE, заполнение результата. |