Comment puis-je désinfecter une chaîne pour l'utiliser comme nom de fichier?
J'ai une routine qui convertit un fichier dans un autre format et l'enregistre. Les fichiers de données d'origine étaient numérotés, mais ma routine donne à la sortie un nom de fichier basé sur un Nom interne trouvé dans l'original.
J'ai essayé de l'exécuter par lots sur un répertoire entier, et cela a bien fonctionné jusqu'à ce que je frappe un fichier dont le Nom interne avait une barre oblique. Oups! Et si il le fait ici, il pourrait facilement le faire sur d'autres fichiers. Y a-t-il une routine RTL (ou WinAPI) quelque part qui va désinfecter un string et supprimer les symboles invalides de sorte qu'il est sûr d'utiliser comme un nom de fichier?
9 réponses
Vous pouvez utiliser PathGetCharType fonction, PathCleanupSpec fonction, ou l'astuce suivante:
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;
CE code divise la chaîne en parties et utilise MoveFile pour vérifier chaque partie. MoveFile échouera pour les caractères invalides ou les noms de Fichiers réservés (COMME 'COM') et retournera success ou ERROR_ALREADY_EXISTS pour le nom de fichier valide.
PathCleanupSpec se trouve dans Jedi Windows API sous Win32API / JwaShlObj.pas
En ce qui concerne la question de savoir s'il existe une fonction API pour désinfecter un fichier un nom (ou même vérifier sa validité) - il semble n'y en avoir aucun. Citant le commentaire sur le PathSearchAndQualify () fonction :
Il ne semble pas y avoir D'API Windows qui validera un chemin entré par l'utilisateur; ceci est laissé comme un exercice ad hoc pour chaque application.
Vous ne pouvez donc consulter les règles de validité des noms de fichiers qu'à partir de noms de fichiers, chemins et espaces de noms (Windows):
-
Utilisez presque tous les caractères de la page de code en cours pour un nom, y compris les caractères Unicode et les caractères du jeu de caractères étendu (128-255), à l'exception des éléments suivants:
- caractères réservés suivants ne sont pas autorisés:
: "/ \ | ? * - les caractères dont les représentations entières sont comprises entre zéro et 31 ne sont pas autorisés.
- Tout autre caractère qui le système de fichiers cible ne permet pas.
- caractères réservés suivants ne sont pas autorisés:
Ne l'utilisez pas les noms de périphérique réservé pour le nom d'un fichier:
CON
,PRN
,AUX
,NUL
,COM1..COM9
,LPT1..LPT9
.
Évitez également ces noms suivis immédiatement d'une extension; par exemple,NUL.txt
n'est pas recommandé.
Si vous savez que votre programme n'écrira que sur les systèmes de fichiers NTFS, vous pouvez probablement être sûr qu'il n'y a pas d'autres caractères que le système de fichiers n'autorise pas, donc vous le feriez il suffit de vérifier que le nom du fichier n'est pas trop long (utilisez la constante MAX_PATH
) après que tous les caractères non valides ont été supprimés (ou remplacés par des traits de soulignement, par exemple).
Un programme doit également s'assurer que l'assainissement du nom de fichier n'a pas entraîné de conflits de noms de fichiers et qu'il écrase silencieusement les autres fichiers qui ont fini par porter le même nom.
{
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;
Vérifiez si la chaîne a des caractères invalides; solution de ici:
//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 *)
Et, pour les chaînes renvoyant False, Vous pouvez faire quelque chose de simple comme this pour chaque caractère invalide:
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
Pour tous ceux qui lisent ceci et qui veulent utiliser PathCleanupSpec, j'ai écrit cette routine de test qui semble fonctionner... il y a un manque défini d'exemples sur le net. Vous devez inclure ShlObj.pas (pas sûr quand PathCleanupSpec a été ajouté mais j'ai testé cela dans Delphi 2010) Vous devrez également vérifier XP sp2 ou supérieur
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;
Eh bien, le plus simple est d'utiliser une expression régulière et la version de gsub
de votre langue préférée pour remplacer tout ce qui n'est pas un caractère "word"."Cette classe de caractères serait" \w
"dans la plupart des langues avec des expressions rationnelles de type Perl, ou" [A-Za-z0-9]
" comme une option simple sinon.
En particulier, contrairement à certains exemples d'autres réponses, vous ne voulez pas rechercher de caractères invalides à supprimer, mais rechercher des caractères valides à conserver. Si vous cherchez des caractères non valides, vous êtes toujours vulnérable à l'introduction de nouveaux personnages, mais si vous ne cherchez que des personnages valides, vous pourriez être légèrement moins inefficace (en ce sens que vous avez remplacé un personnage dont vous n'aviez pas vraiment besoin), mais au moins vous ne vous tromperez jamais.
Maintenant, si vous voulez que la nouvelle version ressemble autant que possible à l'ancienne, vous pourriez envisager un remplacement. Au lieu de supprimer, vous pouvez remplacer un caractère ou des caractères que vous savez être ok. Mais faire cela est un problème assez intéressant que c'est probablement un bon sujet pour une autre question.
J'ai fait ceci:
// 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, '_');
}
Essayez ceci sur un delphi moderne:
use System.IOUtils;
...
result := TPath.HasValidFileNameChars(FileName, False)
Je permet également d'avoir des umlauts allemands ou d'autres caractères comme -,_,.. dans un nom de fichier.
// 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.