delphi - quede - como limpiar una cadena de plata
¿Cómo puedo desinfectar una cadena para usarla como un nombre de archivo? (9)
Bueno, lo más fácil es usar una expresión regular y la versión de gsub
de tu idioma favorito para reemplazar cualquier cosa que no sea un "personaje de palabra". Esta clase de caracteres sería " /w
" en la mayoría de los lenguajes con expresiones regulares similares a Perl, o " [A-Za-z0-9]
" como una opción simple en caso contrario.
Particularmente, en contraste con algunos de los ejemplos en otras respuestas, no desea buscar caracteres no válidos para eliminar, pero busque caracteres válidos para mantener. Si busca caracteres no válidos, siempre es vulnerable a la introducción de nuevos personajes, pero si busca solo los válidos, puede ser un poco menos ineficiente (en el sentido de que reemplazó a un personaje que realmente no necesita hacerlo), pero al menos nunca te equivocarás.
Ahora, si desea hacer que la nueva versión sea lo más antigua posible, podría considerar la posibilidad de reemplazarla. En lugar de eliminar, puedes sustituir un personaje o personajes que sabes que están bien. Pero hacer eso es un problema bastante interesante que probablemente sea un buen tema para otra pregunta.
Tengo una rutina que convierte un archivo a un formato diferente y lo guarda. Los archivos de datos originales estaban numerados, pero mi rutina da a la salida un nombre de archivo basado en un nombre interno que se encuentra en el original.
Traté de ejecutarlo en un directorio completo, y funcionó bien hasta que toqué un archivo cuyo nombre interno tenía una barra. Oops! Y si lo hace aquí, podría hacerlo fácilmente en otros archivos. ¿Hay alguna rutina RTL (o WinAPI) en alguna parte que desinfecte una cadena y elimine los símbolos no válidos para que sea seguro usarla como un nombre de archivo?
Compruebe si la cadena tiene caracteres no válidos; solución desde 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 *)
Y, para cadenas que devuelven False, puede hacer algo simple como this para cada carácter inválido:
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
Con respecto a la pregunta de si hay alguna función API para desinfectar un nombre de archivo (o incluso comprobar su validez), parece que no hay ninguno. Citando el comentario de la función PathSearchAndQualify () :
No parece haber ninguna API de Windows que valide una ruta ingresada por el usuario; esto se deja como un ejercicio ad hoc para cada aplicación.
Por lo tanto, solo puede consultar las reglas para la validez del nombre de archivo desde Nombres de archivos, Rutas y Espacios de nombres (Windows) :
Use casi cualquier carácter en la página de códigos actual para un nombre, incluidos los caracteres Unicode y los caracteres en el juego de caracteres extendido (128-255), a excepción de lo siguiente:
- Los siguientes caracteres reservados no están permitidos:
<>: "/ / |? * - Los caracteres cuyas representaciones enteras están en el rango de cero a 31 no están permitidos.
- Cualquier otro carácter que el sistema de archivos de destino no permita.
- Los siguientes caracteres reservados no están permitidos:
No utilice los siguientes nombres de dispositivo reservados para el nombre de un archivo:
CON
,PRN
,AUX
,NUL
,COM1..COM9
,LPT1..LPT9
.
También evite estos nombres seguidos inmediatamente por una extensión; por ejemplo,NUL.txt
no es recomendado.
Si sabe que su programa solo escribirá en sistemas de archivos NTFS, probablemente pueda estar seguro de que no hay otros caracteres que el sistema de archivos no permita, por lo que solo deberá verificar que el nombre del archivo no sea demasiado largo (use la constante MAX_PATH
) después de eliminar todos los caracteres inválidos (o reemplazarlos por guiones bajos, por ejemplo).
Un programa también debe asegurarse de que la desinfección del nombre de archivo no haya generado conflictos de nombre de archivo y sobrescribe silenciosamente otros archivos que terminaron con el mismo nombre.
Hice esto:
// 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, ''_'');
}
Para cualquiera que lea esto y quiera usar PathCleanupSpec, escribí esta rutina de prueba que parece funcionar ... hay una falta definida de ejemplos en la red. Necesita incluir ShlObj.pas (no estoy seguro de cuándo se agregó PathCleanupSpec pero lo probé en Delphi 2010) También deberá verificar si hay XP sp2 o superior
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;
Prueba esto en un delphi moderno:
use System.IOUtils;
...
result := TPath.HasValidFileNameChars(FileName, False)
También me permite tener diéresis alemanas u otros caracteres como -, _, .. en un nombre de archivo.
Puede usar la función PathGetCharType , la función PathCleanupSpec o el siguiente truco:
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;
Este código divide la cadena en partes y usa MoveFile para verificar cada parte. MoveFile fallará en cuanto a los caracteres no válidos o los nombres de archivo reservados (como ''COM'') y devolverá el resultado correcto o ERROR_ALREADY_EXISTS para el nombre de archivo válido.
PathCleanupSpec está en la API Jedi de Windows en Win32API / JwaShlObj.pas
// 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.
{
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;