web-dev-qa-db-fra.com

Besoin d'effectuer une recherche générique (*,?, Etc.) sur une chaîne à l'aide de Regex

Je dois effectuer des recherches génériques (*, ?, etc.) sur une chaîne . Voici ce que j'ai fait:

string input = "Message";
string pattern = "d*";
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);

if (regex.IsMatch(input))
{
    MessageBox.Show("Found");
}
else
{
    MessageBox.Show("Not Found");
}

Avec le code ci-dessus, le bloc "Trouvé" frappe mais en réalité il ne devrait pas!

Si mon motif est "e *", seul "Found" devrait apparaître.

Ma compréhension ou exigence est d * recherche devrait trouver le texte contenant "d" suivi de tous les caractères.

Devrais-je changer de motif en "d. *" Et "e. *"? Existe-t-il un support dans .NET pour Wild Card qui le fait en interne tout en utilisant la classe Regex?

58
Scott

De http://www.codeproject.com/KB/recipes/wildcardtoregex.aspx :

public static string WildcardToRegex(string pattern)
{
    return "^" + Regex.Escape(pattern)
                      .Replace(@"\*", ".*")
                      .Replace(@"\?", ".")
               + "$";
}

Donc, quelque chose comme foo*.xls? sera transformé en ^foo.*\.xls.$.

114
Gabe

Vous pouvez faire un simple mach générique sans RegEx en utilisant une fonction Visual Basic appelée LikeString.

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;

if (Operators.LikeString("This is just a test", "*just*", CompareMethod.Text))
{
  Console.WriteLine("This matched!");
}

Si vous utilisez CompareMethod.Text, la comparaison ne tiendra pas compte de la casse. Pour une comparaison sensible à la casse, vous pouvez utiliser CompareMethod.Binary.

Plus d'infos ici: http://www.henrikbrinch.dk/Blog/2012/02/14/Wildcard-matching-in-C

MSDN: http://msdn.Microsoft.com/en-us/library/Microsoft.visualbasic.compilerservices.operators.likestring%28v=vs.100%29.ASPX

21
Adam Szabo

La formulation correcte de l'expression globale d* est ^d, ce qui signifie que tout ce qui commence par d doit correspondre.

    string input = "Message";
    string pattern = @"^d";
    Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);

(La citation @ n'est pas nécessaire dans ce cas, mais c'est une bonne pratique car de nombreuses expressions rationnelles utilisent des échappements de barre oblique inverse qui doivent être laissés seuls et indique également au lecteur que cette chaîne est spéciale).

10
Mark Lakata

Windows et * nux traitent les caractères génériques différemment. *, ? et . sont traités de manière très complexe par Windows, la présence ou la position d’une personne changerait la signification d’une autre. Tandis que * nux reste simple, tout ce qu’il fait n’est qu’une simple correspondance de modèle. En outre, Windows correspond à ? pour 0 ou 1 caractères, Linux lui correspond pour exactement 1 caractères.

Je n'ai pas trouvé de documents faisant autorité à ce sujet, voici juste ma conclusion basée sur des jours de tests sur Windows 8/XP (la ligne de commande, la commande dir étant spécifique, et la méthode Directory.GetFiles utilise également les mêmes règles) et Ubuntu Server 12.04 .1 (commande ls). J’ai fait fonctionner des dizaines de cas courants et peu communs, même s’il existe de nombreux cas non plus.

La réponse actuelle de Gabe fonctionne comme * nux. Si vous voulez aussi un style Windows et êtes prêt à accepter l’imperfection, alors la voici:

    /// <summary>
    /// <para>Tests if a file name matches the given wildcard pattern, uses the same rule as Shell commands.</para>
    /// </summary>
    /// <param name="fileName">The file name to test, without folder.</param>
    /// <param name="pattern">A wildcard pattern which can use char * to match any amount of characters; or char ? to match one character.</param>
    /// <param name="unixStyle">If true, use the *nix style wildcard rules; otherwise use windows style rules.</param>
    /// <returns>true if the file name matches the pattern, false otherwise.</returns>
    public static bool MatchesWildcard(this string fileName, string pattern, bool unixStyle)
    {
        if (fileName == null)
            throw new ArgumentNullException("fileName");

        if (pattern == null)
            throw new ArgumentNullException("pattern");

        if (unixStyle)
            return WildcardMatchesUnixStyle(pattern, fileName);

        return WildcardMatchesWindowsStyle(fileName, pattern);
    }

    private static bool WildcardMatchesWindowsStyle(string fileName, string pattern)
    {
        var dotdot = pattern.IndexOf("..", StringComparison.Ordinal);
        if (dotdot >= 0)
        {
            for (var i = dotdot; i < pattern.Length; i++)
                if (pattern[i] != '.')
                    return false;
        }

        var normalized = Regex.Replace(pattern, @"\.+$", "");
        var endsWithDot = normalized.Length != pattern.Length;

        var endWeight = 0;
        if (endsWithDot)
        {
            var lastNonWildcard = normalized.Length - 1;
            for (; lastNonWildcard >= 0; lastNonWildcard--)
            {
                var c = normalized[lastNonWildcard];
                if (c == '*')
                    endWeight += short.MaxValue;
                else if (c == '?')
                    endWeight += 1;
                else
                    break;
            }

            if (endWeight > 0)
                normalized = normalized.Substring(0, lastNonWildcard + 1);
        }

        var endsWithWildcardDot = endWeight > 0;
        var endsWithDotWildcardDot = endsWithWildcardDot && normalized.EndsWith(".");
        if (endsWithDotWildcardDot)
            normalized = normalized.Substring(0, normalized.Length - 1);

        normalized = Regex.Replace(normalized, @"(?!^)(\.\*)+$", @".*");

        var escaped = Regex.Escape(normalized);
        string head, tail;

        if (endsWithDotWildcardDot)
        {
            head = "^" + escaped;
            tail = @"(\.[^.]{0," + endWeight + "})?$";
        }
        else if (endsWithWildcardDot)
        {
            head = "^" + escaped;
            tail = "[^.]{0," + endWeight + "}$";
        }
        else
        {
            head = "^" + escaped;
            tail = "$";
        }

        if (head.EndsWith(@"\.\*") && head.Length > 5)
        {
            head = head.Substring(0, head.Length - 4);
            tail = @"(\..*)?" + tail;
        }

        var regex = head.Replace(@"\*", ".*").Replace(@"\?", "[^.]?") + tail;
        return Regex.IsMatch(fileName, regex, RegexOptions.IgnoreCase);
    }

    private static bool WildcardMatchesUnixStyle(string pattern, string text)
    {
        var regex = "^" + Regex.Escape(pattern)
                               .Replace("\\*", ".*")
                               .Replace("\\?", ".")
                    + "$";

        return Regex.IsMatch(text, regex);
    }

Il y a une chose amusante, même l'API Windows PathMatchSpec n'est pas d'accord avec FindFirstFile . Essayez juste a1*., FindFirstFile dit que cela correspond à a1, PathMatchSpec dit non.

6
deerchao

d* signifie qu'il doit correspondre à zéro ou plusieurs caractères "d". Donc, toute chaîne est une correspondance valide. Essayez d+ à la place!

Afin de prendre en charge les modèles de caractères génériques, je remplacerais les caractères génériques par les équivalents RegEx. De même que * devient .* et ? devient .?. Alors votre expression ci-dessus devient d.*

3
Anders Zommarin

Vous devez convertir votre expression générique en une expression régulière. Par exemple:

    private bool WildcardMatch(String s, String wildcard, bool case_sensitive)
    {
        // Replace the * with an .* and the ? with a dot. Put ^ at the
        // beginning and a $ at the end
        String pattern = "^" + Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";

        // Now, run the Regex as you already know
        Regex regex;
        if(case_sensitive)
            regex = new Regex(pattern);
        else
            regex = new Regex(pattern, RegexOptions.IgnoreCase);

        return(regex.IsMatch(s));
    } 
3
carlos357

Vous devez échapper des symboles Regex spéciaux dans le modèle générique d’entrée (par exemple, le modèle *.txt sera équivalent à ^.*\.txt$) Ainsi, les barres obliques, les accolades et de nombreux symboles spéciaux doivent être remplacés par @"\" + s, où s - symbole Regex spécial.

3
Camarada

Tout le code supérieur n'est pas correct jusqu'à la fin.

En effet, lorsque vous effectuez une recherche sur zz * toto * ou zz *, vous n'obtiendrez pas de résultats corrects.

Et si vous recherchez "abcd *" dans "abcd" dans TotalCommander, il trouvera un fichier abcd si tout le code supérieur est erroné.

Voici le bon code.

public string WildcardToRegex(string pattern)
{             
    string result= Regex.Escape(pattern).
        Replace(@"\*", ".+?").
        Replace(@"\?", "."); 

    if (result.EndsWith(".+?"))
    {
        result = result.Remove(result.Length - 3, 3);
        result += ".*";
    }

    return result;
}
0
user3816530

Vous voudrez peut-être utiliser WildcardPattern from System.Management.Automation Assembly. Voir ma réponse ici .

0
VirtualVDX

Je pense que @Dmitri a une bonne solution à Faire correspondre les chaînes avec un caractère génériquehttps://stackoverflow.com/a/30300521/1726296

Sur la base de sa solution, j'ai créé deux méthodes d'extension. (crédit lui va)

Peut être utile.

public static String WildCardToRegular(this String value)
{
        return "^" + Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*") + "$";
}

public static bool WildCardMatch(this String value,string pattern,bool ignoreCase = true)
{
        if (ignoreCase)
            return Regex.IsMatch(value, WildCardToRegular(pattern), RegexOptions.IgnoreCase);

        return Regex.IsMatch(value, WildCardToRegular(pattern));
}

Usage:

string pattern = "file.*";

var isMatched = "file.doc".WildCardMatch(pattern)

ou

string xlsxFile = "file.xlsx"
var isMatched = xlsxFile.WildCardMatch(pattern)
0
Tejasvi Hegde