web-dev-qa-db-fra.com

C # Sanitize File Name

J'ai récemment déplacé un groupe de fichiers MP3 de divers endroits dans un référentiel. J'avais construit les nouveaux noms de fichiers en utilisant les tags ID3 (merci, TagLib-Sharp!), Et j'ai remarqué que je recevais un System.NotSupportedException

"Le format du chemin donné n'est pas supporté."

Cela a été généré par File.Copy() ou Directory.CreateDirectory().

Il ne fallut pas longtemps pour comprendre que mes noms de fichiers devaient être désinfectés. Alors j'ai fait la chose évidente:

public static string SanitizePath_(string path, char replaceChar)
{
    string dir = Path.GetDirectoryName(path);
    foreach (char c in Path.GetInvalidPathChars())
        dir = dir.Replace(c, replaceChar);

    string name = Path.GetFileName(path);
    foreach (char c in Path.GetInvalidFileNameChars())
        name = name.Replace(c, replaceChar);

    return dir + name;
}

À ma grande surprise, j'ai continué à avoir des exceptions. Il s'est avéré que ':' n'est pas dans l'ensemble de Path.GetInvalidPathChars(), car il est valide dans une racine de chemin. Je suppose que cela a du sens - mais cela doit être un problème assez commun. Quelqu'un at-il un code abrégé qui assainit un chemin? Le plus approfondi que j'ai élaboré avec cela, mais il semble que c'est probablement exagéré.

    // replaces invalid characters with replaceChar
    public static string SanitizePath(string path, char replaceChar)
    {
        // construct a list of characters that can't show up in filenames.
        // need to do this because ":" is not in InvalidPathChars
        if (_BadChars == null)
        {
            _BadChars = new List<char>(Path.GetInvalidFileNameChars());
            _BadChars.AddRange(Path.GetInvalidPathChars());
            _BadChars = Utility.GetUnique<char>(_BadChars);
        }

        // remove root
        string root = Path.GetPathRoot(path);
        path = path.Remove(0, root.Length);

        // split on the directory separator character. Need to do this
        // because the separator is not valid in a filename.
        List<string> parts = new List<string>(path.Split(new char[]{Path.DirectorySeparatorChar}));

        // check each part to make sure it is valid.
        for (int i = 0; i < parts.Count; i++)
        {
            string part = parts[i];
            foreach (char c in _BadChars)
            {
                part = part.Replace(c, replaceChar);
            }
            parts[i] = part;
        }

        return root + Utility.Join(parts, Path.DirectorySeparatorChar.ToString());
    }

Toute amélioration visant à rendre cette fonction plus rapide et moins baroque serait très appréciée.

140
Jason Sundram

Pour nettoyer un nom de fichier, vous pouvez le faire

private static string MakeValidFileName( string name )
{
   string invalidChars = System.Text.RegularExpressions.Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
   string invalidRegStr = string.Format( @"([{0}]*\.+$)|([{0}]+)", invalidChars );

   return System.Text.RegularExpressions.Regex.Replace( name, invalidRegStr, "_" );
}
276
Andre

Une solution plus courte:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
93
DenNukem

Sur la base de l'excellente réponse d'Andre mais en tenant compte du commentaire de Spud sur les mots réservés, j'ai réalisé cette version:

/// <summary>
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path)
/// </summary>
/// <remarks>
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name
/// </remarks>
public static string CoerceValidFileName(string filename)
{
    var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
    var invalidReStr = string.Format(@"[{0}]+", invalidChars);

    var reservedWords = new []
    {
        "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4",
        "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
        "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
    };

    var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_");
    foreach (var reservedWord in reservedWords)
    {
        var reservedWordPattern = string.Format("^{0}\\.", reservedWord);
        sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase);
    }

    return sanitisedNamePart;
}

Et ce sont mes tests unitaires

[Test]
public void CoerceValidFileName_SimpleValid()
{
    var filename = @"thisIsValid.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual(filename, result);
}

[Test]
public void CoerceValidFileName_SimpleInvalid()
{
    var filename = @"thisIsNotValid\3\\_3.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid_3__3.txt", result);
}

[Test]
public void CoerceValidFileName_InvalidExtension()
{
    var filename = @"thisIsNotValid.t\xt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid.t_xt", result);
}

[Test]
public void CoerceValidFileName_KeywordInvalid()
{
    var filename = "aUx.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("_reservedWord_.txt", result);
}

[Test]
public void CoerceValidFileName_KeywordValid()
{
    var filename = "auxillary.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("auxillary.txt", result);
}
65
fiat
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
28
data

J'utilise la méthode System.IO.Path.GetInvalidFileNameChars() pour vérifier les caractères non valides et je n'ai aucun problème.

J'utilise le code suivant:

foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
    filename = filename.Replace(invalidchar, '_');
}
4
André Leal

Je voulais conserver les caractères d’une certaine manière, pas simplement remplacer le caractère par un trait de soulignement. 

Une façon que je pensais était de remplacer les caractères par des caractères similaires qui sont (dans ma situation) peu susceptibles d’être utilisés comme des caractères normaux. J'ai donc pris la liste des caractères non valides et trouvé des sosies. 

Ce qui suit sont des fonctions pour encoder et décoder avec les «look-a-likes».

Ce code n'inclut pas une liste complète de tous les caractères System.IO.Path.GetInvalidFileNameChars (). C'est donc à vous de prolonger ou d'utiliser le remplacement du soulignement pour tous les caractères restants.

private static Dictionary<string, string> EncodeMapping()
{
    //-- Following characters are invalid for windows file and folder names.
    //-- \/:*?"<>|
    Dictionary<string, string> dic = new Dictionary<string, string>();
    dic.Add(@"\", "Ì"); // U+OOCC
    dic.Add("/", "Í"); // U+OOCD
    dic.Add(":", "¦"); // U+00A6
    dic.Add("*", "¤"); // U+00A4
    dic.Add("?", "¿"); // U+00BF
    dic.Add(@"""", "ˮ"); // U+02EE
    dic.Add("<", "«"); // U+00AB
    dic.Add(">", "»"); // U+00BB
    dic.Add("|", "│"); // U+2502
    return dic;
}

public static string Escape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Key, replace.Value);
    }

    //-- handle dot at the end
    if (name.EndsWith(".")) name = name.CropRight(1) + "°";

    return name;
}

public static string UnEscape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Value, replace.Key);
    }

    //-- handle dot at the end
    if (name.EndsWith("°")) name = name.CropRight(1) + ".";

    return name;
}

Vous pouvez sélectionner vos propres look-a-likes. J'ai utilisé l'application Carte de caractères dans Windows pour sélectionner le mien %windir%\system32\charmap.exe

Comme je fais des ajustements par découverte, je vais mettre à jour ce code.

3
Valamas

Je pense que le problème est que vous appelez d'abord Path.GetDirectoryName sur la mauvaise chaîne. Si cela contient des caractères autres que des noms de fichiers, .Net ne peut pas dire quelles parties de la chaîne sont des répertoires et des projections. Vous devez faire des comparaisons de chaînes.

En supposant que seul le nom de fichier soit mauvais, et non le chemin d'accès complet, essayez ceci:

public static string SanitizePath(string path, char replaceChar)
{
    int filenamePos = path.LastIndexOf(Path.DirectorySeparatorChar) + 1;
    var sb = new System.Text.StringBuilder();
    sb.Append(path.Substring(0, filenamePos));
    for (int i = filenamePos; i < path.Length; i++)
    {
        char filenameChar = path[i];
        foreach (char c in Path.GetInvalidFileNameChars())
            if (filenameChar.Equals(c))
            {
                filenameChar = replaceChar;
                break;
            }

        sb.Append(filenameChar);
    }

    return sb.ToString();
}
2
Dour High Arch

J'ai eu du succès avec cela dans le passé.

Sympa, court et statique :-)

    public static string returnSafeString(string s)
    {
        foreach (char character in Path.GetInvalidFileNameChars())
        {
            s = s.Replace(character.ToString(),string.Empty);
        }

        foreach (char character in Path.GetInvalidPathChars())
        {
            s = s.Replace(character.ToString(), string.Empty);
        }

        return (s);
    }
2
Helix 88

Voici une méthode d'extension de chargement paresseux efficace basée sur le code d'André:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LT
{
    public static class Utility
    {
        static string invalidRegStr;

        public static string MakeValidFileName(this string name)
        {
            if (invalidRegStr == null)
            {
                var invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
                invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
            }

            return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, "_");
        }
    }
}
1
Bryan Legend

il y a beaucoup de solutions de travail ici. par souci d'exhaustivité, voici une approche qui n'utilise pas regex, mais utilise LINQ:

var invalids = Path.GetInvalidFileNameChars();
filename = invalids.Aggregate(filename, (current, c) => current.Replace(c, '_'));

De plus, c'est une solution très courte;)

1
kappadoky

Votre code serait plus propre si vous ajoutiez le répertoire et le nom du fichier ensemble et que vous le nettoyiez plutôt que de le nettoyer séparément. Pour ce qui est de désinfecter le:, prenez simplement le deuxième caractère de la chaîne. S'il est égal à "replacechar", remplacez-le par un deux-points. Comme cette application est destinée à votre usage personnel, une telle solution devrait être parfaitement suffisante.

0
Brian