Как я могу очистить строку для использования в качестве имени файла? - PullRequest
26 голосов
/ 07 июня 2009

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

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

Ответы [ 9 ]

23 голосов
/ 07 июня 2009

Вы можете использовать Функция 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') и вернет успех или ERROR_ALREADY_EXISTS для действительного имени файла.


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

12 голосов
/ 07 июня 2009

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

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

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

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

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

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

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

7 голосов
/ 14 июня 2009
{
  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;
5 голосов
/ 13 июня 2012

Для всех, кто читает это и хочет использовать 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 $80000000)=$80000000 then
    DebugString:= DebugString+'Fatal result. The cleaned path is not a valid file name'+#13+#10;
  if (ReturnValue and $00000001)=$00000001 then
    DebugString:= DebugString+'Replaced one or more invalid characters'+#13+#10;
  if (ReturnValue and $00000002)=$00000002 then
    DebugString:= DebugString+'Removed one or more invalid characters'+#13+#10;
  if (ReturnValue and $00000004)=$00000004 then
    DebugString:= DebugString+'The returned path is truncated'+#13+#10;
  if (ReturnValue and $00000008)=$00000008 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;
5 голосов
/ 07 июня 2009

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

//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, вы можете сделать что-то простое, например this для каждого недопустимого символа:

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
3 голосов
/ 07 июня 2009

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

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

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

1 голос
/ 07 июня 2009

Я сделал это:

// 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, '_');
}
0 голосов
/ 27 апреля 2017
// 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.
0 голосов
/ 18 ноября 2016

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

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

Я также разрешаю иметь в имени файла немецкие умлауты или другие символы, такие как -, _, ..

...