Question

J'ai besoin d'un moyen simple et robuste pour supprimer les caractères de chemin et de fichier illégaux d'une simple chaîne. J'ai utilisé le code ci-dessous, mais il ne semble rien faire. Qu'est-ce qui me manque?

using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}
Était-ce utile?

La solution

Essayez quelque chose comme ceci à la place;

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(), ""); 
}

Mais je suis d'accord avec les commentaires, j'essaierais probablement de traiter la source des chemins illégaux, plutôt que d'essayer de transformer un chemin illicite en un chemin légitime, mais probablement involontaire.

Éditer: Ou une solution potentiellement «meilleure», utilisant celle de Regex.

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");

Néanmoins, la question que l'on demande de savoir pourquoi vous le faites en premier lieu.

Autres conseils

public string GetSafeFilename(string filename)
{

    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));

}

Cette réponse était sur un autre fil de Ceres , je l'aime vraiment bien, c'est simple et net.

J'utilise Linq pour nettoyer les noms de fichiers. Vous pouvez facilement l'étendre pour rechercher également des chemins valides.

private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

Mettre à jour

Certains commentaires indiquent que cette méthode ne fonctionne pas pour eux. J'ai donc inclus un lien vers un extrait de code DotNetFiddle afin que vous puissiez valider la méthode.

https://dotnetfiddle.net/nw1SWY

Vous pouvez supprimer les caractères illégaux à l'aide de Linq comme ceci:

var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

MODIFIER
Voici à quoi cela ressemble avec la modification requise mentionnée dans les commentaires:

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());

Ce sont toutes d'excellentes solutions, mais elles reposent toutes sur Path.GetInvalidFileNameChars, qui peut ne pas être aussi fiable que vous le pensez. Notez la remarque suivante dans la documentation MSDN sur Path.GetInvalidPathChars :

  

Le tableau renvoyé par cette méthode ne contient pas la totalité des caractères non valides dans les noms de fichier et de répertoire. L'ensemble des caractères non valides peut varier en fonction du système de fichiers. Par exemple, sur les plates-formes de bureau Windows, les chemins d'accès non valides peuvent inclure les caractères ASCII / Unicode 1 à 31, ainsi que les guillemets (& Quot;), inférieurs à (& Lt;), supérieurs à ( gt;), pipe (|), retour arrière (\ b), null (\ 0) et tabulation (\ t).

Ce n'est pas mieux avec la méthode <=> . Il contient exactement la même remarque.

Pour les noms de fichier:

string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

Pour les chemins complets:

string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars()));

Notez que si vous souhaitez utiliser cette fonctionnalité en tant que fonctionnalité de sécurité, une approche plus robuste consisterait à développer tous les chemins, puis à vérifier que le chemin fourni par l'utilisateur est bien un enfant d'un répertoire auquel l'utilisateur devrait avoir accès.

Pour commencer, la la suppression supprime uniquement les caractères du début ou fin de la chaîne . Deuxièmement, vous devez évaluer si vous voulez vraiment supprimer les caractères offensants ou si vous échouez rapidement et que vous sachiez que leur nom de fichier est invalide. Mon choix est le dernier, mais ma réponse devrait au moins vous montrer comment faire les choses correctement ET dans le mauvais sens:

question sur StackOverflow montrant comment vérifie si une chaîne donnée est un nom de fichier valide . Notez que vous pouvez utiliser l'expression régulière de cette question pour supprimer les caractères remplacés par une expression régulière (si vous en avez vraiment besoin).

J'utilise des expressions régulières pour y parvenir. Tout d’abord, je construis dynamiquement l’expression régulière.

string regex = string.Format(
                   "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Ensuite, j'appelle simplement removeInvalidChars.Replace pour rechercher et remplacer. Cela peut évidemment être étendu pour couvrir également les caractères de chemin.

Le meilleur moyen de supprimer les caractères illégaux de la saisie utilisateur consiste à remplacer les caractères illégaux à l'aide de la classe Regex, à créer une méthode dans le code derrière ou également à valider côté client à l'aide du contrôle RegularExpression.

public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

OU

<asp:RegularExpressionValidator ID="regxFolderName" 
                                runat="server" 
                                ErrorMessage="Enter folder name with  a-z A-Z0-9_" 
                                ControlToValidate="txtFolderName" 
                                Display="Dynamic" 
                                ValidationExpression="^[a-zA-Z0-9_]*$" 
                                ForeColor="Red">

Je préfère absolument l’idée de Jeff Yates. Cela fonctionnera parfaitement si vous le modifiez légèrement:

string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

L’amélioration consiste simplement à échapper à la regex générée automatiquement.

Voici un extrait de code qui devrait aider pour .NET 3 et versions ultérieures.

using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path, "");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName, "");
    }
}

La plupart des solutions ci-dessus combinent des caractères illégaux pour le chemin et le nom de fichier, ce qui est faux (même si les deux appels renvoient actuellement le même jeu de caractères). Je diviserais d’abord le chemin + nom de fichier en chemin et nom de fichier, puis appliquerais le jeu approprié à l’un ou l’autre, puis fusionner les deux à nouveau.

wvd_vegt

Si vous supprimez ou remplacez par un seul caractère les caractères non valides, vous pouvez avoir des collisions:

<abc -> abc
>abc -> abc

Voici une méthode simple pour éviter ceci:

public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
    return s;
}

Le résultat:

 <abc -> [1]abc
 >abc -> [2]abc

Lance une exception.

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }

J'ai écrit ce monstre pour le plaisir, il vous permet d'aller et venir:

public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if(Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}

Je pense qu'il est beaucoup plus facile de valider à l'aide d'une expression régulière et de spécifier les caractères autorisés au lieu d'essayer de rechercher tous les caractères incorrects. Voir ces liens: http://www.c-sharpcorner.com/UploadFr/ RegExpPSD12062005021717AM / RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/ oreilly / windows / news / csharp_0101.html

En outre, effectuez une recherche sur " éditeur d’expressions régulières & "; ils aident beaucoup. Il y en a autour de qui même sortir le code en c # pour vous.

Cela semble être O (n) et ne dépense pas trop de mémoire en chaînes:

    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }

En parcourant les réponses ici, elles ** semblent toutes impliquer l’utilisation d’un tableau de caractères composé de caractères de nom de fichier non valides.

Certes, il s’agit peut-être d’une micro-optimisation - mais à l’intention de tous ceux qui voudraient vérifier un grand nombre de valeurs comme noms de fichiers valides, il convient de noter que la création d’un hachage de caractères invalides entraînera une amélioration des performances. .

J'ai déjà été très surpris (choqué) par la rapidité avec laquelle un hashset (ou un dictionnaire) surpasse les itérations d'une liste. Avec les chaînes, c'est un nombre ridiculement bas (environ 5 à 7 éléments de la mémoire). Avec la plupart des autres données simples (références d'objet, numéros, etc.), le croisement magique semble se situer autour de 20 éléments.

Il y a 40 caractères non valides dans la liste Path.InvalidFileNameChars " " ;. A fait une recherche aujourd'hui et il y a une bonne référence ici sur StackOverflow qui montre que le hashset prendra un peu plus de la moitié du temps d'un tableau / d'une liste pour 40 éléments: https://stackoverflow.com/a/10762995/949129

Voici la classe d'assistance que j'utilise pour la désinfection des chemins. J'oublie maintenant pourquoi j'avais l'option de remplacement sophistiquée, mais c'est là un bonus mignon.

Méthode de bonus supplémentaire " IsValidLocalPath " aussi:)

(** ceux qui n'utilisent pas d'expressions régulières)

public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <summary>Replaces characters in <c>text</c> that are not allowed in file names with the 
    /// specified replacement character.</summary>
    /// <param name="text">Text to make into a valid filename. The same string is returned if 
    /// it is valid already.</param>
    /// <param name="replacement">Replacement character, or NULL to remove bad characters.</param>
    /// <param name="fancyReplacements">TRUE to replace quotes and slashes with the non-ASCII characters ” and ⁄.</param>
    /// <returns>A string that can be used as a filename. If the output string would otherwise be empty, "_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    if (c == '"') repl = '”'; // U+201D right double quotation mark
                    else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
                    else if (c == '/') repl = '⁄'; // U+2044 fraction slash
                }
                if (repl != '\0')
                    sb.Append(repl);
            }
            else
                sb.Append(c);
        }

        if (sb.Length == 0)
            return "_";

        return changed ? sb.ToString() : text;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name="pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}
public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source, "");
            return result;
        }
    }

Vous pouvez utiliser la méthode clairement.

Le nom de fichier ne peut pas contenir de caractères de symboles Path.GetInvalidPathChars(), + et #, ni d'autres noms spécifiques. Nous avons combiné tous les chèques en une seule classe:

public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; 
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if(invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

La méthode GetValidFileName remplace toutes les données incorrectes par _.

Une ligne pour nettoyer la chaîne de tout caractère illégal pour le nommage de fichier Windows:

public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}

Cela vous plaira et évitera les collisions

 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }

Je pense que la question déjà pas complète a répondu ... Les réponses décrivent uniquement le nom de fichier OU le chemin propre ... pas les deux. Voici ma solution:

private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
    returnValue = returnValue.TrimEnd('\\');
    return returnValue;
}

J'ai créé une méthode d'extension qui combine plusieurs suggestions:

  1. Maintien de caractères non autorisés dans un ensemble de hachage
  2. Filtrage des caractères sous ascii 127. Etant donné que Path.GetInvalidFileNameChars n'inclut pas tous les caractères non valides possibles avec les codes ascii compris entre 0 et 255. Voir ici et MSDN
  3. Possibilité de définir le caractère de remplacement

Source:

public static class FileNameCorrector
{
    private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string ToValidFileName(this string name, char replacement = '\0')
    {
        var builder = new StringBuilder();
        foreach (var cur in name)
        {
            if (cur > 31 && cur < 128 && !invalid.Contains(cur))
            {
                builder.Append(cur);
            }
            else if (replacement != '\0')
            {
                builder.Append(replacement);
            }
        }

        return builder.ToString();
    }
}

Ou vous pouvez simplement faire

[YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top