Бесконечный цикл в Паскале, почему?

Я написал функцию Pascal для замены целой строки или только ее части в файле для использования в Inno Setup:

function ReplaceInFile(const FilePath, OldLinePart, Replacement: String;
                       DoReplaceWholeLine, IsCaseSensitive: Boolean): Boolean;
var
  FileLines:          TArrayOfString;
  Index:              Integer;
  FoundAtPos:         Integer;
  LeftOfOldLinePart:  String;
  RightOfOldLinePart: String;
  IsReplaced:         Boolean;
begin
  Result := False;
  if FileExists(FilePath) then begin
    LoadStringsFromFile(FilePath, FileLines);
    for Index := 0 to GetArrayLength(FileLines) - 1 do
    begin
      repeat
        FoundAtPos := 0;
        if IsCaseSensitive then
          FoundAtPos := Pos(OldLinePart, FileLines[Index])
        else
          FoundAtPos := Pos(Uppercase(OldLinePart), Uppercase(FileLines[Index]));
        if FoundAtPos > 0 then begin
          if DoReplaceWholeLine then begin
            FileLines[Index] := Replacement;
            IsReplaced := True;
          end
          else begin
            LeftOfOldLinePart := Copy(FileLines[Index], 1, FoundAtPos - 1);
            RightOfOldLinePart := Copy(FileLines[Index], FoundAtPos 
              + Length(OldLinePart), Length(FileLines[Index]) 
              - Length(LeftOfOldLinePart + OldLinePart));
            FileLines[Index] := LeftOfOldLinePart + Replacement + RightOfOldLinePart;
            IsReplaced := True;
          end;
        end;
      until FoundAtPos = 0;
    end;
    if IsReplaced then
      if SaveStringsToFile(FilePath, FileLines, False) then
        Result := True;
  end;
end;

Раньше он работал нормально, но заменял только первое вхождение OldLinePart в каждой строке файла, указанного FilePath, на замену. Именно тогда я добавил цикл повторения. Логика заключается в том, что Pos() возвращает 0, когда больше не найдено вхождений. Затем он должен перейти на следующую строку. Однако правда в том, что цикл продолжается бесконечно, и я понятия не имею, почему. Я попытался добавить оператор Break в предложение else if FoundAtPos > 0, но безуспешно...

Изменить: Очевидно, уже поздно. Я заменял OldLinePart очень длинной строкой... содержащей одно и то же слово.

Вопрос решился следующим образом.

function ReplaceInFile(const FilePath, OldLinePart, Replacement: String; DoReplaceWholeLine, IsCaseSensitive: Boolean): Boolean;
var
  FileLines: TArrayOfString;
  Index: Integer;
  SearchLinePart: String;
  FoundAtPosition: Integer;
  SearchOffset: Integer;
  LeftOfOldLinePart: String;
  RightOfOldLinePart: String;
  IsReplaced: Boolean;
begin
  Result := False;
  if FileExists(FilePath) then
  begin
    LoadStringsFromFile(FilePath, FileLines);
    for Index := 0 to GetArrayLength(FileLines) - 1 do
    begin
      SearchOffset := 0;
      SearchLinePart := FileLines[Index];
      repeat
        FoundAtPosition := 0;
        if IsCaseSensitive then
          FoundAtPosition := SearchOffset + Pos(OldLinePart, SearchLinePart)
        else
          FoundAtPosition := SearchOffset + Pos(Uppercase(OldLinePart), Uppercase(SearchLinePart));
        if FoundAtPosition > SearchOffset then
        begin
          if DoReplaceWholeLine then
          begin
            FileLines[Index] := Replacement;
            IsReplaced := True;
            Break;
          end
          else
          begin
            LeftOfOldLinePart := '';
            RightOfOldLinePart := '';
            LeftOfOldLinePart := Copy(FileLines[Index], 1, FoundAtPosition - 1);
            RightOfOldLinePart := Copy(FileLines[Index], FoundAtPosition + Length(OldLinePart), Length(FileLines[Index]) - Length(LeftOfOldLinePart + OldLinePart));
            FileLines[Index] := LeftOfOldLinePart + Replacement + RightOfOldLinePart;
            IsReplaced := True;
            SearchOffset := Length(LeftOfOldLinePart + Replacement);
            SearchLinePart := RightOfOldLinePart;
          end;
        end;
      until FoundAtPosition <= SearchOffset;
    end;
    if IsReplaced then
      if SaveStringsToFile(FilePath, FileLines, False) then
        Result := True;
  end;
end;

person thoiz_vd    schedule 08.05.2011    source источник
comment
Вот возможность: вы используете тестовый пример, в котором значение всегда находится. Таким образом (возможно) ваша функция Pos() возвращает значение, и это изменяет переменную FoundAtPosition. Таким образом, петля никогда не заканчивается. Опять же, это только в том случае, если вы тестируете значения, которые всегда находятся.   -  person itsols    schedule 08.05.2011
comment
@itsols: Что ж, то, что я хочу заменить в файле, оказывается и в имени файла. Но было бы глупо, если бы Pos() разобрал это. Несколько других условий поиска, которые я сейчас попробовал, похоже, работают...   -  person thoiz_vd    schedule 08.05.2011


Ответы (2)


Я заменил ваш цикл одним вызовом StringReplace. Это предотвращает (бесконечный) цикл и решает проблему.
Обратите внимание, что у вас также была проблема с IsReplaced, если, например. FileLines[4] был заменен, но не FilesLine[last], тогда ваш код не будет вызывать SaveStringsToFile.
Это также несколько упрощает код.

uses SysUtils;

function ReplaceInFile(const FilePath, OldLinePart, Replacement: String;
                       DoReplaceWholeLine, IsCaseSensitive: Boolean): Boolean;
var
  FileLines:          TArrayOfString;
  Index:              Integer;
  TempStr:            String;
  IsReplaced:         Boolean;
  Flags:              TReplaceFlags;
  IsReplacedAnywhere: Boolean;
begin
  Result := False;
  if FileExists(FilePath) then begin
    LoadStringsFromFile(FilePath, FileLines);
    IsReplacedAnywhere:= false;
    for Index := 0 to GetArrayLength(FileLines) - 1 do begin
      if DoReplaceWholeLine then begin
        IsReplaced := 
          IsCaseSensitive and (Pos(OldLinePart, FileLines[Index]) > 0) or 
          not(IsCaseSensitive) and 
           (Pos(Uppercase(OldLinePart), Uppercase(FileLines[Index])) > 0);
        if IsReplaced then FileLines[Index] := Replacement;
      end
      else begin
        Flags:= [rfReplaceAll];
        if not(IsCaseSensitive) then Flags:= Flags + [rfIgnoreCase];
        TempStr:= StringReplace(FileLines[Index], OldLinePart, Replacement
                                ,Flags);
        IsReplaced := (TempStr <> FileLines[Index]);
        if IsReplaced then FileLines[Index]:= TempStr;
      end; {else}
      IsReplacedAnywhere:= IsReplacedAnywhere or IsReplaced;
    end; {for Index}
    Result:= IsReplacedAnywhere 
             and SaveStringsToFile(FilePath, FileLines, False);
  end; {if}
end;

Дайте мне знать, если это работает для вас.

person Johan    schedule 08.05.2011
comment
Нет возможности использовать всю мощь Delphi, так как Inno Setup поддерживает только некоторое подмножество Pascal. В Inno есть функция замены строки, но я не знаю, как сделать ее нечувствительной к регистру. Кстати, то, что вы говорите об IsReplaced, не так. Как только для IsReplaced установлено значение True, будет вызвана функция сохранения, поскольку для IsReplaced никогда не устанавливается значение False. - person thoiz_vd; 08.05.2011
comment
@thoiz_vd, after:= stringreplace(Upper(before), Upper(StrFrom), StrTo); - person Johan; 08.05.2011
comment
@thoiz_vd, насчет IsReplaced, понятно. Но в моем коде вам нужно оставить IsReplacedAnywhere, потому что IsReplaced выполняет двойную функцию, чтобы увидеть, необходима ли замена. - person Johan; 08.05.2011
comment
Я не мог использовать StringChangeEx() в Inno Setup, потому что мне нужно было бы передать ему всю мою строку с заглавной буквы, что привело бы к тому, что выходная строка также была бы заглавной. Я только что изменил свою собственную функцию, чтобы искать только те части, которые еще не искались. - person thoiz_vd; 08.05.2011

Теперь это нужно сделать с помощью StringChangeEx. Хорошо, что он поддерживает Unicode. Некрасиво то, как он ломает старые скрипты, удаляя старые функции. StringReplace больше не компилируется; даже если это функция Delphi для этого. StringChange уже устарел.

person CodeLurker    schedule 08.09.2013
comment
Скрипты Inno Setup основаны на Pascal Script и содержат только те функции, которые нужны авторам. Они могли назвать эти функции совершенно по-другому или присвоить им другое значение из Delphi. Delphi и Pascal — это нечто отличное от пользовательского языка сценариев, основанного на Pascal Script. - person TLama; 09.09.2013
comment
Я понимаю, что Inno Pascal — это не Delphi, и я этого не утверждал. Мой комментарий был ответом на то, как это сделать, и жалобой на устаревание старого кода без веских причин. Я упомянул Delphi только потому, что это функция Delphi, и она может быть более широко известна; а также устаревание старого кода. Вы не продемонстрировали, что мой ответ неверен. - person CodeLurker; 17.08.2016