我有一个例程,可以将文件转换为不同的格式并保存。原始数据文件已编号,但我的例程根据原始文件中的内部名称为输出提供文件名。
我尝试在整个目录上批量运行它,它运行正常,直到我点击一个内部名称中有斜杠的文件。哎呀!如果它在这里,它可以很容易地在其他文件上。是否有某个RTL(或WinAPI)例程可以清理字符串并删除无效符号,因此可以安全地用作文件名?
答案 0 :(得分:23)
您可以使用PathGetCharType function,PathCleanupSpec function或以下技巧:
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答案 1 :(得分:11)
关于是否有任何API函数来清理文件名称(甚至检查其有效性)的问题 - 似乎没有。引用PathSearchAndQualify() function上的评论:
似乎没有任何Windows API会验证用户输入的路径;这是每个应用程序的临时练习。
因此,您只能查询File Names, Paths, and Namespaces (Windows)的文件名有效性规则:
使用当前代码页中的几乎任何字符作为名称,包括扩展字符集(128-255)中的Unicode字符和字符,但以下情况除外:
请勿将以下保留的设备名称用于文件名称:CON
,PRN
,AUX
,NUL
,COM1..COM9
,LPT1..LPT9
。
同时避免使用这些名称后立即进行扩展;例如,不建议使用NUL.txt
。
如果您知道您的程序只会写入NTFS文件系统,您可能可以确定文件系统不允许其他字符,因此您只需要检查文件名是否也不是在删除所有无效字符(例如,用下划线替换)之后,使用long(MAX_PATH
常量)。
程序还应确保文件名清理不会导致文件名冲突,并且它会以静默方式覆盖最终使用相同名称的其他文件。
答案 2 :(得分:7)
{
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;
答案 3 :(得分:5)
检查字符串是否包含无效字符;来自here的解决方案:
//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
答案 4 :(得分:5)
对于读这篇文章并想要使用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 :(得分:3)
嗯,最简单的方法是使用正则表达式和您最喜欢的语言gsub
版本来替换任何不是“单词字符”的内容。在大多数使用类似Perl的正则表达式的语言中,此字符类将为“\w
”,否则为“[A-Za-z0-9]
”作为简单选项。
特别是,与其他答案中的一些示例相比,您不希望查找要删除的无效字符,而是查找要保留的有效字符。如果您正在寻找无效字符,那么您总是容易受到新字符的引入,但如果您只查找有效字符,那么效率可能会稍低一些(因为您更换了一个字符而不是需要),但至少你永远不会错。
现在,如果您想让新版本尽可能地与旧版本一样,您可以考虑更换。您可以替换一个或多个字符,而不是删除。但这样做是一个非常有趣的问题,它可能是另一个问题的好主题。
答案 6 :(得分:1)
我这样做了:
// 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, '_');
}
答案 7 :(得分:0)
在现代的delphi上试试这个:
use System.IOUtils;
...
result := TPath.HasValidFileNameChars(FileName, False)
我还允许在文件名中使用德语变音符号或其他字符,如_,_,..
答案 8 :(得分:0)
// 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.
答案 9 :(得分: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;