Как очистить строку для использования в качестве имени файла?
У меня есть программа, которая конвертирует файл в другой формат и сохраняет его. Исходные файлы данных были пронумерованы, но моя процедура дает выходное имя файла на основе внутреннего имени, найденного в оригинале.
Я попытался запустить его в пакетном режиме по всему каталогу, и он работал нормально, пока я не попал в один файл, внутреннее имя которого имело косую черту. Упс! И если он делает это здесь, он может легко сделать это на других файлах. Есть ли процедура 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.