2009-06-06 12 views
23

Ho una routine che converte un file in un formato diverso e lo salva. I file di dati originali erano numerati, ma la mia routine dà all'output un nome di file basato su un nome interno trovato nell'originale.Come posso disinfettare una stringa da utilizzare come nome file?

Ho provato a eseguirlo in batch su un'intera directory, e ha funzionato bene fino a quando non ho colpito un file il cui nome interno aveva una barra in esso. Oops! E se lo fa qui, potrebbe facilmente farlo su altri file. Esiste una routine RTL (o WinAPI) da qualche parte che sanificherà una stringa e rimuoverà i simboli non validi, quindi è sicuro da usare come nome file?

risposta

20

È possibile utilizzare PathGetCharType function, PathCleanupSpec function o il seguente trucco:

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; 

Questo codice divide stringa in parti e utilizza MoveFile per verificare ogni parte. MoveFile fallirà per caratteri non validi o nomi di file riservati (come 'COM') e restituirà successo o ERROR_ALREADY_EXISTS per nome file valido.


PathCleanupSpec è nel Jedi Windows API sotto Win32API/JwaShlObj.pas

+0

Grazie! PathCleanupSpec sembra esattamente quello che sto cercando. –

+1

+1 per PathCleanupSpec, roba interessante –

1

ho fatto questo:

// 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, '_'); 
} 
+0

* punti al tag "Delphi" nella domanda * Sembra un buon algoritmo, però. Esiste un'API Win32 equivalente a quelle chiamate System.IO.Path? –

+0

Dopo aver esaminato la classe in Reflector, puoi facilmente vedere quali sono questi caratteri non validi. Portalo semplicemente a Delphi. – RichardOD

+0

... se avessi C# e Reflector, sì. Preferisco evitare i mal di testa che derivano dal codice gestito. –

5

Verificare se la stringa ha caratteri non validi; Soluzione da 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 *) 

E, per le stringhe di ritorno False, si potrebbe fare qualcosa di semplice come this per ogni carattere non valido:

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 
3

Beh, la cosa più semplice è quello di utilizzare una regex e il tuo preferito versione della lingua di gsub per sostituire tutto ciò che non è un "carattere di parola". Questa classe di caratteri sarebbe "\w" nella maggior parte delle lingue con espressioni regolari Perl, o "[A-Za-z0-9]" come opzione semplice altrimenti.

In particolare, a differenza di alcuni esempi in altre risposte, non si desidera cercare caratteri non validi da rimuovere, ma cercare caratteri validi da conservare. Se stai cercando caratteri non validi, sei sempre vulnerabile all'introduzione di nuovi personaggi, ma se stai cercando solo quelli validi, potresti essere leggermente meno inefficiente (in quanto hai sostituito un personaggio che non hai davvero bisogno di), ma almeno non ti sbagli mai.

Ora, se si desidera rendere la nuova versione tanto più vecchia possibile, si può considerare la sostituzione. Invece di cancellare, puoi sostituire un personaggio o personaggi che sai essere ok. Ma farlo è un problema abbastanza interessante che probabilmente è un buon argomento per un'altra domanda.

+1

No. Se si considera che le versioni recenti di Windows supportano nomi di file Unicode completi e qualcosa come Ä £ ̆Ώ ۑ≥ ♣. Txt è valido, si desidera sicuramente una lista nera per un'operazione come questa, non una lista bianca. –

+0

Non nel modo in cui ho interpretato la domanda.Non stai cercando di vedere se una stringa arbitraria è un nome file valido, stai cercando di garantire un nome file valido da una trasformazione di una stringa arbitraria. Questi sono (forse sottilmente) diversi. Ad esempio, se è possibile convertire qualsiasi stringa in un numero univoco a 8 cifre, che potrebbe non avere alcuna relazione ovvia con la stringa originale, ma garantisce comunque che è possibile salvare la cosa dannata su disco. –

+0

Sì. Sto cercando di garantire un nome file valido dalla trasformazione di una stringa arbitraria, preservando il maggior numero possibile di informazioni trasmesse dalla stringa originale. –

10

Per quanto riguarda la domanda se esiste una funzione API per disinfettare un file con un nome (o persino verificare la sua validità) - sembra che non ci sia nessuno. Citando il commento sul PathSearchAndQualify() function:

Non sembra essere alcuna API di Windows che convaliderà un percorso inserito dall'utente; questo è lasciato come un esercizio ad hoc per ogni applicazione.

Così si può consultare solo le regole per il nome del file di validità da File Names, Paths, and Namespaces (Windows):

  • utilizzare praticamente qualsiasi carattere nella tabella codici corrente per un nome, compresi i caratteri Unicode e caratteri nel set di caratteri estesi (128-255), con le seguenti eccezioni:

    • I seguenti caratteri riservati non sono ammessi:
      <>:? "/ \ | *
    • I caratteri le cui rappresentazioni dei numeri interi sono nell'intervallo compreso tra zero e 31 non sono consentiti.
    • Qualsiasi altro carattere che il file system di destinazione non consente.
  • Non utilizzare i seguenti nomi dei dispositivi riservati per il nome di un file: CON, PRN, AUX, NUL, COM1..COM9, LPT1..LPT9.
    Evitare anche questi nomi seguiti immediatamente da un'estensione; ad esempio, NUL.txt non è consigliato.

Se sapete che il vostro programma sarà sempre e solo scrivere al file system NTFS, probabilmente si può essere sicuri che non ci siano altri personaggi che il file system non consente, quindi si avrebbe solo per verificare che il file il nome non è troppo lungo (utilizzare la costante MAX_PATH) dopo che tutti i caratteri non validi sono stati rimossi (o sostituiti da caratteri di sottolineatura, ad esempio).

Un programma deve anche assicurarsi che il nome del file che disinfetta non abbia portato a conflitti di nome file e che sovrascriva silenziosamente altri file che hanno finito con lo stesso nome.

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

Per chiunque altro la lettura di questo e di voler utilizzare PathCleanupSpec, ho scritto questa routine di prova che sembra funzionare ... c'è una mancanza definita di esempi sulla 'rete. È necessario includere ShlObj.pas (non so se è stato aggiunto PathCleanupSpec ma ho provato questo in Delphi 2010) Sarà inoltre necessario verificare la presenza di XP sp2 o superiore

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; 
0

Prova questo su un Delphi moderna:

use System.IOUtils; 
... 
result := TPath.HasValidFileNameChars(FileName, False) 

Consente anche di avere dieresi o altri caratteri tedeschi come -, _, .. in un nome file.

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. 
Problemi correlati