Как очистить строку для использования в качестве имени файла?

У меня есть программа, которая конвертирует файл в другой формат и сохраняет его. Исходные файлы данных были пронумерованы, но моя процедура дает выходное имя файла на основе внутреннего имени, найденного в оригинале.

Я попытался запустить его в пакетном режиме по всему каталогу, и он работал нормально, пока я не попал в один файл, внутреннее имя которого имело косую черту. Упс! И если он делает это здесь, он может легко сделать это на других файлах. Есть ли процедура RTL (или WinAPI), которая будет дезинфицировать строку и удалить недопустимые символы, чтобы безопасно использовать ее в качестве имени файла?

9 ответов


можно использовать функция PathGetCharType, функция PathCleanupSpec или следующий трюк:

  function IsValidFilePath(const FileName: String): Boolean;
  var
    S: String;
    I: Integer;
  begin
    Result := False;
    S := FileName;
    repeat
      I := LastDelimiter('\/', S);
      MoveFile(nil, PChar(S));
      if (GetLastError = ERROR_ALREADY_EXISTS) or
         (
           (GetFileAttributes(PChar(Copy(S, I + 1, MaxInt))) = INVALID_FILE_ATTRIBUTES)
           and
           (GetLastError=ERROR_INVALID_NAME)
         ) then
        Exit;
      if I>0 then
        S := Copy(S,1,I-1);
    until I = 0;
    Result := True;
  end;

этот код делит строку на части и использует MoveFile для проверки каждой части. MoveFile завершится ошибкой для недопустимых символов или зарезервированных имен файлов (например, "COM") и вернет success или ERROR_ALREADY_EXISTS для допустимого имени файла.


PathCleanupSpec находится в Jedi Windows API под Win32API / JwaShlObj.pas


Что касается вопроса, есть ли какая - либо функция API для очистки файла имя (или даже проверить его действительность) - кажется, нет. Цитата из комментария к PathSearchAndQualify () функции:

кажется, что нет никакого API Windows, который будет проверять путь, введенный пользователем; это остается как специальное упражнение для каждого приложения.

Так вы можете только ознакомиться с правилами для действительность имени файла от имена файлов, пути и пространства имен (Windows):

  • используйте почти любой символ на текущей кодовой странице для имени, включая символы Юникода и символы в расширенном наборе символов (128-255), за исключением следующего:

    • следующие зарезервированные символы не допускаются:
      : "/ \ | ? *
    • символы, целочисленные представления которых находятся в диапазоне от нуля до 31 не допускается.
    • любой другой символ, который не разрешен целевой файловой системой.
  • не использовать следующие зарезервированные имена устройств для имени файла: CON, PRN, AUX, NUL, COM1..COM9, LPT1..LPT9.
    Также избегайте этих имен, за которыми немедленно следует расширение; например, NUL.txt не рекомендуется.

если вы знаете, что ваша программа будет писать только в файловые системы NTFS вероятно, вы можете быть уверены, что нет других символов, которые не разрешены файловой системой, поэтому вам нужно только проверить, что имя файла не слишком длинное (используйте MAX_PATH constant) после того, как все недопустимые символы были удалены (или заменены подчеркиваниями, например).

программа также должна убедиться, что дезинфекция имени файла не привела к конфликтам имен файлов, и она молча перезаписывает другие файлы, которые оказались с тем же именем.


{
  CleanFileName
  ---------------------------------------------------------------------------

  Given an input string strip any chars that would result
  in an invalid file name.  This should just be passed the
  filename not the entire path because the slashes will be
  stripped.  The function ensures that the resulting string
  does not hae multiple spaces together and does not start
  or end with a space.  If the entire string is removed the
  result would not be a valid file name so an error is raised.

}

function CleanFileName(const InputString: string): string;
var
  i: integer;
  ResultWithSpaces: string;
begin

  ResultWithSpaces := InputString;

  for i := 1 to Length(ResultWithSpaces) do
  begin
    // These chars are invalid in file names.
    case ResultWithSpaces[i] of 
      '/', '\', ':', '*', '?', '"', '<', '>', '|', ' ', #$D, #$A, #9:
        // Use a * to indicate a duplicate space so we can remove
        // them at the end.
        {$WARNINGS OFF} // W1047 Unsafe code 'String index to var param'
        if (i > 1) and
          ((ResultWithSpaces[i - 1] = ' ') or (ResultWithSpaces[i - 1] = '*')) then
          ResultWithSpaces[i] := '*'
        else
          ResultWithSpaces[i] := ' ';

        {$WARNINGS ON}
    end;
  end;

  // A * indicates duplicate spaces.  Remove them.
  result := ReplaceStr(ResultWithSpaces, '*', '');

  // Also trim any leading or trailing spaces
  result := Trim(Result);

  if result = '' then
  begin
    raise(Exception.Create('Resulting FileName was empty Input string was: '
      + InputString));
  end;
end;

проверьте, имеет ли строка недопустимые символы; решение из здесь:

//test if a "fileName" is a valid Windows file name
//Delphi >= 2005 version

function IsValidFileName(const fileName : string) : boolean;
const 
  InvalidCharacters : set of char = ['\', '/', ':', '*', '?', '"', '<', '>', '|'];
var
  c : char;
begin
  result := fileName <> '';

  if result then
  begin
    for c in fileName do
    begin
      result := NOT (c in InvalidCharacters) ;
      if NOT result then break;
    end;
  end;
end; (* IsValidFileName *)

и для строк, возвращающих False, вы можете сделать что-то простое, например этой для каждого недопустимого символа:

var
  before, after : string;

begin
  before := 'i am a rogue file/name';

  after  := StringReplace(before, '/', '',
                      [rfReplaceAll, rfIgnoreCase]);
  ShowMessage('Before = '+before);
  ShowMessage('After  = '+after);
end;

// Before = i am a rogue file/name
// After  = i am a rogue filename

для всех, кто читает это и хочет использовать PathCleanupSpec, я написал эту тестовую процедуру, которая, похоже, работает... в Сети явно не хватает примеров. Вам нужно включить ShlObj.pas (не уверен, когда был добавлен PathCleanupSpec, но я тестировал это в Delphi 2010) Вам также нужно будет проверить XP sp2 или выше

procedure TMainForm.btnTestClick(Sender: TObject);
var
  Path: array [0..MAX_PATH - 1] of WideChar;
  Filename: array[0..MAX_PATH - 1] of WideChar;
  ReturnValue: integer;
  DebugString: string;

begin
  StringToWideChar('a*dodgy%\filename.$&^abc',FileName, MAX_PATH);
  StringToWideChar('C:\',Path, MAX_PATH);
  ReturnValue:= PathCleanupSpec(Path,Filename);
  DebugString:= ('Cleaned up filename:'+Filename+#13+#10);
  if (ReturnValue and 000000)=000000 then
    DebugString:= DebugString+'Fatal result. The cleaned path is not a valid file name'+#13+#10;
  if (ReturnValue and 000001)=000001 then
    DebugString:= DebugString+'Replaced one or more invalid characters'+#13+#10;
  if (ReturnValue and 000002)=000002 then
    DebugString:= DebugString+'Removed one or more invalid characters'+#13+#10;
  if (ReturnValue and 000004)=000004 then
    DebugString:= DebugString+'The returned path is truncated'+#13+#10;
  if (ReturnValue and 000008)=000008 then
    DebugString:= DebugString+'The input path specified at pszDir is too long to allow the formation of a valid file name from pszSpec'+#13;
  ShowMessage(DebugString);
end;

ну, простая вещь-использовать регулярное выражение и версию вашего любимого языка gsub заменить все, что не является "слово характер."Этот класс символов будет "\w "в большинстве языков с Perl-подобными regexes или"[A-Za-z0-9]" как простой вариант, в противном случае.

в частности, в отличие от некоторых примеров в других ответах, вы не хотите искать недопустимые символы для удаления, но ищите допустимые символы для сохранения. Если вы ищете инвалида персонажи, вы всегда уязвимы для введения новых персонажей, но если вы ищете только действительные, вы можете быть немного менее неэффективными (в том, что вы заменили символ, который вам действительно не нужен), Но, по крайней мере, вы никогда не ошибетесь.

Теперь, если вы хотите сделать новую версию как можно больше похожей на старую, вы можете рассмотреть возможность замены. Вместо удаления, вы можете заменить символ или символы, которые вы знаете, чтобы быть в порядке. Но делать это интересно достаточно проблема, что это, вероятно, хорошая тема для другого вопроса.


Я сделал так:

// Initialized elsewhere...
string folder;
string name;
var prepl = System.IO.Path.GetInvalidPathChars();
var frepl = System.IO.Path.GetInvalidFileNameChars();
foreach (var c in prepl)
{
    folder = folder.Replace(c,'_');
    name = name.Replace(c, '_');
}
foreach (var c in frepl)
{
    folder = folder.Replace(c, '_');
    name = name.Replace(c, '_');
}

попробуйте это на современном delphi:

 use System.IOUtils;
 ...
 result := TPath.HasValidFileNameChars(FileName, False)

I позволяет также иметь немецкие умлауты или другие символы, такие как -,_,.. в имени.


// for all platforms (Windows\Unix), uses IOUtils.
function ReplaceInvalidFileNameChars(const aFileName: string; const aReplaceWith: Char = '_'): string;
var
  i: integer;
begin
  Result := aFileName;
  for i := Low(Result) to High(Result) do
    if not TPath.IsValidFileNameChar(Result[i]) then
      Result[i] := aReplaceWith;
  end;
end.