30

我有一个将文件转换为不同格式并保存的例程。原始数据文件已编号,但我的例程根据原始数据文件中的内部名称为输出提供文件名。

我尝试在整个目录上批量运行它,它运行良好,直到我点击了一个内部名称中有斜杠的文件。哎呀!如果它在这里这样做,它可以很容易地在其他文件上这样做。某处是否有 RTL(或 WinAPI)例程可以清理字符串并删除无效符号,以便安全地用作文件名?

4

8 回答 8

24

您可以使用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在 Win32API/JwaShlObj.pas 下的Jedi Windows API

于 2009-06-07T08:28:01.850 回答
12

关于是否有任何 API 函数来清理文件名称(甚至检查其有效性)的问题 - 似乎没有。引用PathSearchAndQualify()函数的评论:

似乎没有任何 Windows API 可以验证用户输入的路径;这留作每个应用程序的临时练习。

所以只能从File Names, Paths, and Namespaces (Windows)中查阅文件名有效性的规则:

  • 几乎可以使用当前代码页中的任何字符作为名称,包括 Unicode 字符和扩展字符集 (128–255) 中的字符,但以下字符除外:

    • 不允许使用以下保留字符:
      < > : " / \ | ? *
    • 不允许使用整数表示在 0 到 31 范围内的字符。
    • 目标文件系统不允许的任何其他字符。
  • 不要使用以下保留设备名称作为文件名:CON, PRN, AUX, NUL, COM1..COM9, LPT1..LPT9.
    还要避免这些名称后跟扩展名;例如,NUL.txt不推荐。

如果您知道您的程序只会写入 NTFS 文件系统,您可能可以确定没有文件系统不允许的其他字符,因此您只需检查文件名是否太长(使用常量)在MAX_PATH所有无效字符已被删除(或替换为下划线,例如)之后。

程序还应该确保文件名清理不会导致文件名冲突,并且它会默默地覆盖以相同名称结尾的其他文件。

于 2009-06-07T05:19:04.277 回答
9
{
  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;
于 2009-06-14T15:33:15.497 回答
4

对于其他阅读本文并想要使用 PathCleanupSpec 的人,我编写了这个似乎有效的测试例程......'net 上肯定缺乏示例。您需要包含 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;
于 2012-06-13T10:01:07.130 回答
2

好吧,最简单的事情是使用正则表达式和您最喜欢的语言版本gsub来替换任何不是“单词字符”的东西。\w在大多数带有类似 Perl 的正则表达式的语言中,这个字符类将是“ ”,否则将“ [A-Za-z0-9]”作为一个简单的选项。

特别是,与其他答案中的一些示例相比,您不想查找要删除的无效字符,而是要查找要保留的有效字符。如果您正在寻找无效字符,您总是容易受到引入新字符的影响,但如果您只寻找有效字符,您的效率可能会稍微低一些(因为您替换了一个您并没有真正使用过的字符)需要),但至少你永远不会错。

现在,如果您想让新版本尽可能地像旧版本一样,您可能会考虑更换。您可以替换一个或多个您知道没问题的字符,而不是删除。但这样做是一个足够有趣的问题,它可能是另一个问题的好话题。

于 2009-06-06T23:52:08.577 回答
2
// 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
  begin
    if not TPath.IsValidFileNameChar(Result[i]) then
      Result[i] := aReplaceWith;
  end;
end.
于 2017-04-26T21:34:35.850 回答
0

在现代德尔福上试试这个:

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

我还允许在文件名中包含德语变音符号或其他字符,例如 -、_、..。

于 2016-11-18T11:42:31.587 回答
0

使用此功能。对我来说很好,目的是取回一级目录名称

使用 shelobj...

function  CleanDirName(DirFileName : String) : String;
var
  CheckStr : String;
  Path: array [0..MAX_PATH - 1] of WideChar;
  Filename: array[0..MAX_PATH - 1] of WideChar;
  ReturnValue: integer;

begin
  //--     The following are considered invalid characters in all names.
  //--     \ / : * ? " < > |

  CheckStr := Trim(DirFileName);
  CheckStr := StringReplace(CheckStr,'/','-',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'\','-',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'.','-',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,':',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'?',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'<',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'>',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'|',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'!',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'~',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'+',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'=',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,')',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'(',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'*',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'&',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'^',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'%',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'$',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'#',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'@',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'{',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'}',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,'"',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,';',' ',[rfReplaceAll, rfIgnoreCase]);
  CheckStr := StringReplace(CheckStr,',',' ',[rfReplaceAll, rfIgnoreCase]);

  // '' become - nil
  CheckStr := StringReplace(CheckStr,'''','',[rfReplaceAll, rfIgnoreCase]);

  StringToWideChar(CheckStr,FileName, MAX_PATH);
  StringToWideChar('C:\',Path, MAX_PATH);
  ReturnValue:= PathCleanupSpec(Path,Filename);

  Filename := StringReplace(Filename,'  ',' ',[rfReplaceAll, rfIgnoreCase]);
  Result := String(Filename);
end;
于 2021-08-08T09:36:42.650 回答