我有一个例程,可以将文件转换为不同的格式并保存。原始数据文件是按编号命名的,但我的例程会根据原始文件中的内部名称给输出文件命名。
我尝试批量运行整个目录,它在遇到一个内部名称带有斜杠的文件时出错。糟糕!如果在这里出现这种情况,它很容易在其他文件上发生。是否有RTL(或WinAPI)例程可以对字符串进行清理,并删除无效符号,使其可安全用作文件名?
我有一个例程,可以将文件转换为不同的格式并保存。原始数据文件是按编号命名的,但我的例程会根据原始文件中的内部名称给输出文件命名。
我尝试批量运行整个目录,它在遇到一个内部名称带有斜杠的文件时出错。糟糕!如果在这里出现这种情况,它很容易在其他文件上发生。是否有RTL(或WinAPI)例程可以对字符串进行清理,并删除无效符号,使其可安全用作文件名?
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验证每个部分。对于无效字符或保留的文件名(如“COM”),MoveFile将失败并为有效文件名返回成功或ERROR_ALREADY_EXISTS。
PathCleanupSpec位于Win32API/JwaShlObj.pas下的Jedi Windows API中。
关于是否有任何API函数可以清理文件名(甚至检查其有效性)的问题 - 似乎没有。引用自PathSearchAndQualify()函数的评论:
没有出现任何Windows API可以验证用户输入的路径;每个应用程序都需要自行解决此问题。
所以,您只能查看来自文件名、路径和命名空间(Windows)中的文件名合法性规则:
使用当前代码页中几乎所有字符作为名称(包括Unicode字符和扩展字符集中的字符(128-255)),除了以下内容:
不要将以下保留设备名称用于文件名称:CON
,PRN
,AUX
,NUL
,COM1..COM9
,LPT1..LPT9
。
还应避免紧接着这些名称的扩展名;例如,不建议使用NUL.txt
。
如果您知道您的程序只会写入NTFS文件系统,那么您可能可以确信该文件系统没有其他不允许的字符,因此您只需要检查文件名是否过长(使用MAX_PATH
常量),在删除所有无效字符之后(或者替换为下划线等)。
程序还应确保文件名清理不会导致文件名冲突,并且它会自动覆盖以相同名称结束的其他文件。
{
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;
// 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.
对于其他想使用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;
gsub
来替换任何不是“单词字符”的内容。在大多数具有类Perl的正则表达式的语言中,这个字符类将是“\w
”,或者作为一个简单选项,否则为“[A-Za-z0-9]
”。使用这个函数。对我来说很好用。 目的是获取一个目录级别的名称
使用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;
在现代 Delphi 上尝试一下:
use System.IOUtils;
...
result := TPath.HasValidFileNameChars(FileName, False)
我也允许在文件名中使用德语umlauts或其他字符,如-,_等。
MoveFile()
函数的第一个参数中使用nil
是未记录的行为。此外,只有在MoveFile()
首次返回FALSE时才检查GetLastError()
,而此代码没有进行检查。 - Remy Lebeau