На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Rouse_, jack128, Krid
  
    > Вставка переменных в строку , Подстановка конкретных значений в строку-шаблон на место переменных вида %name%
      Вставка переменных в строку

      Функция для подстановки произвольных значений в строку на место переменных вида %name% (ограничивающие символы могут быть любыми, одинаковыми или нет, но обязательно должны присутствовать оба). Пригодится для задания шаблонов названий, имён файлов (например, при генерации из тегов), HTML страниц (при создании отчётов). Нечто подобное можно сделать через Format('bla %0:s bla %1:s',[s1, s2]), но этот метод обладает низкой наглядностью для пользователя (все переменные будут обозначаться %1, %2, и т.д.). Представленная реализация даёт неограниченные возможности для манипуляций (условное преобразование итогового значения, подстановка в зависимости от предыдущих подстановок и т.п.) Для D2009+ используется reference to function, что даёт следующие плюшки: не нужно объявлять отдельную функцию-обработчик; доступны все переменные вызывающей функции.

      ExpandedWrap disabled
        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;


      Применение
      ExpandedWrap disabled
        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');


      Функция и построенные на ней другие функции верой и правдой служат уже с полгода. Наличие аналога в Генофонде мне неизвестно. Возможно, кому-нибудь пригодится.
        С функциями Replace* есть одна маленькая беда: если строка очень большая (>10Mb) с большим количеством замен, то операторы типа "Result := Result + ..." очень сильно тупят из-за частого выделения памяти под строку. Бывали случаи, что память становилась настолько фрагментированной, что системе не удавалось выделить память для выполнения этой операции.
        Выкручивались предварительным подсчётом замен, вычислением и одноразовой установкой размера результирующей строки и, с помощью MOVE, заполнение результата. :rolleyes:
        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
        0 пользователей:


        Рейтинг@Mail.ru
        [ Script execution time: 0,0365 ]   [ 16 queries used ]   [ Generated: 29.03.24, 02:32 GMT ]