2008-09-30 5 views
104

Comment puis-je aller de cette chaîne: "ThisIsMyCapsDelimitedString".NET - Comment pouvez-vous diviser une chaîne délimitée "majuscules" en un tableau?

... à cette chaîne: "This Is My Caps Délimité String"

lignes de code en moins grand nombre VB.net est préférable, mais C# est aussi Bienvenue.

À la votre!

+0

Que se passe-t-il lorsque vous devez traiter avec "OldMacDonaldAndMrO'TooleWentToMcDonalds"? –

+2

Cela va seulement voir une utilisation limitée. Je vais principalement l'utiliser pour analyser des noms de variables tels que ThisIsMySpecialVariable, –

Répondre

158

Je l'ai fait il y a un certain temps. Il correspond à chaque composant d'un nom CamelCase.

/([A-Z]+(?=$|[A-Z][a-z])|[A-Z]?[a-z]+)/g 

Par exemple:

"SimpleHTTPServer" => ["Simple", "HTTP", "Server"] 
"camelCase" => ["camel", "Case"] 

Pour convertir en seulement insérer des espaces entre les mots:

Regex.Replace(s, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ") 

Si vous devez manipuler des chiffres:

/([A-Z]+(?=$|[A-Z][a-z]|[0-9])|[A-Z]?[a-z]+|[0-9]+)/g 

Regex.Replace(s,"([a-z](?=[A-Z]|[0-9])|[A-Z](?=[A-Z][a-z]|[0-9])|[0-9](?=[^0-9]))","$1 ") 
+1

CamelCase! C'est comme ça qu'on l'appelait! J'aime ça! Merci beaucoup! –

+13

En fait, camelCase a une lettre minuscule. Ce dont vous parlez ici, c'est PascalCase. –

+0

Cela a été corrigé. –

0

Il y a probablement une solution plus élégante, mais ce que je viens avec le dessus de ma tête:

string myString = "ThisIsMyCapsDelimitedString"; 

for (int i = 1; i < myString.Length; i++) 
{ 
    if (myString[i].ToString().ToUpper() == myString[i].ToString()) 
    { 
      myString = myString.Insert(i, " "); 
      i++; 
    } 
} 
11

excellente commentaire de Grant Wagner côté:

Dim s As String = RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString", "([A-Z])", " $1") 
+1

Cela laisse le résultat avec un espace de début: "Ceci est M ... – Ferruccio

+0

Bon point ... S'il vous plaît n'hésitez pas à insérer le .substring(), .trimstart(), .trim(), .remove(), etc. de votre choix. :) –

2
string s = "ThisIsMyCapsDelimitedString"; 
string t = Regex.Replace(s, "([A-Z])", " $1").Substring(1); 
+0

Je savais qu'il y aurait un moyen facile de RegEx ... Je dois commencer à l'utiliser plus. –

+1

Pas un gourou regex mais que se passe-t-il avec "HeresAWTFString"? – Nick

+1

Vous obtenez "Heres A W T F String" mais c'est exactement ce que Matias Nino a demandé dans la question. –

35
Regex.Replace("ThisIsMyCapsDelimitedString", "(\\B[A-Z])", " $1") 
+0

C'est la meilleure solution jusqu'à présent, mais vous devez utiliser \\ B pour compiler. Sinon, le compilateur essaie de traiter le \ B comme une séquence d'échappement. – Ferruccio

+0

Belle solution. Quelqu'un peut-il penser à une raison que cela ne devrait pas être la réponse acceptée? Est-il moins performant ou moins performant? –

+0

Fonctionne très bien, juste laissé tomber dans! Devrait être accepté réponse. –

1

Solution regex naïve. Ne manipulera pas O'Conner et ajoutera un espace au début de la chaîne.

s = "ThisIsMyCapsDelimitedString" 
split = Regex.Replace(s, "[A-Z0-9]", " $&"); 
+0

Je vous ai modifié, mais les gens prennent généralement un smackdown mieux s'il ne commence pas par "naïf". – MusiGenesis

+0

Je ne pense pas que c'était un clou. Dans ce contexte, naïf signifie généralement évident ou simple (c'est-à-dire pas nécessairement la meilleure solution). Il n'y a aucune intention d'insulte. – Ferruccio

+0

Ouais je voulais dire simpliste – Geoff

14

Juste pour un peu de variété ... Voici une méthode d'extension qui n'utilise pas une regex.

public static class CamelSpaceExtensions 
{ 
    public static string SpaceCamelCase(this String input) 
    { 
     return new string(InsertSpacesBeforeCaps(input).ToArray()); 
    } 

    private static IEnumerable<char> InsertSpacesBeforeCaps(IEnumerable<char> input) 
    { 
     foreach (char c in input) 
     { 
      if (char.IsUpper(c)) 
      { 
       yield return ' '; 
      } 

      yield return c; 
     } 
    } 
} 
+0

Pour éviter d'utiliser Trim(), avant le foreach j'ai mis: int counter = -1. à l'intérieur, ajouter le compteur ++. remplacez la vérification par: if (char.IsUpper (c) && counter> 0) –

+0

Ceci insère un espace avant le 1er caractère. –

4

Pour plus de variété, en utilisant de vieux objets C# simples, ce qui suit produit la même sortie que l'excellente expression régulière de @ MizardX.

public string FromCamelCase(string camel) 
{ // omitted checking camel for null 
    StringBuilder sb = new StringBuilder(); 
    int upperCaseRun = 0; 
    foreach (char c in camel) 
    { // append a space only if we're not at the start 
     // and we're not already in an all caps string. 
     if (char.IsUpper(c)) 
     { 
      if (upperCaseRun == 0 && sb.Length != 0) 
      { 
       sb.Append(' '); 
      } 
      upperCaseRun++; 
     } 
     else if(char.IsLower(c)) 
     { 
      if (upperCaseRun > 1) //The first new word will also be capitalized. 
      { 
       sb.Insert(sb.Length - 1, ' '); 
      } 
      upperCaseRun = 0; 
     } 
     else 
     { 
      upperCaseRun = 0; 
     } 
     sb.Append(c); 
    } 

    return sb.ToString(); 
} 
+1

Wow, c'est moche. Maintenant, je me souviens pourquoi j'aime tellement regex! +1 pour l'effort, cependant. ;) –

+0

lol .. totalement moche. regex est le chemin à parcourir! –

+1

De loin la réponse avec les meilleures performances. – Diego

18

Bonne réponse, MizardX! J'ai modifié légèrement pour traiter des chiffres comme des mots séparés, de sorte que « AddressLine1 » deviendrait « Adresse 1 » au lieu de « Adresse Ligne1 »:

Regex.Replace(s, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", "$1 ") 
+2

Bon ajout! Je soupçonne que peu de gens seront surpris par le traitement des nombres acceptés dans les chaînes. :) –

+0

Je sais que ça fait presque 8 ans que tu l'as posté, mais ça a fonctionné parfaitement pour moi aussi. :) Les numéros m'ont fait trébucher au début. –

+0

La seule réponse qui passe mes 2 tests aberrants: "Take5" -> "Take 5", "PublisherID" -> "Publisher ID". Je veux mettre en avant ce deux fois – PandaWood

0

Essayez d'utiliser

"([A-Z]*[^A-Z]*)" 

le résultat ajustement pour l'alphabet mélange avec des chiffres

Regex.Replace("AbcDefGH123Weh", "([A-Z]*[^A-Z]*)", "$1 "); 
Abc Def GH123 Weh 

Regex.Replace("camelCase", "([A-Z]*[^A-Z]*)", "$1 "); 
camel Case 
8

J'avais besoin d'une solution qui prend en charge les acronymes et les numéros.Cette solution regex traite les modèles suivants comme « mots » individuels:

  • Une lettre majuscule suivie par des lettres minuscules
  • Une séquence de nombres consécutifs
  • lettres majuscules consécutives (interprétées comme acronymes) - une nouvelle mot peut commencer à utiliser la dernière capitale, par exemple HTMLGuide => "Guide HTML", "TheATeam" => "The A Team"

Vous pourrait le faire comme un paquebot:

Regex.Replace(value, @"(?<!^)((?<!\d)\d|(?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z]))", " $1") 

Une approche plus lisible pourrait être mieux :

using System.Text.RegularExpressions; 

namespace Demo 
{ 
    public class IntercappedStringHelper 
    { 
     private static readonly Regex SeparatorRegex; 

     static IntercappedStringHelper() 
     { 
      const string pattern = @" 
       (?<!^) # Not start 
       (
        # Digit, not preceded by another digit 
        (?<!\d)\d 
        | 
        # Upper-case letter, followed by lower-case letter if 
        # preceded by another upper-case letter, e.g. 'G' in HTMLGuide 
        (?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z]) 
       )"; 

      var options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled; 

      SeparatorRegex = new Regex(pattern, options); 
     } 

     public static string SeparateWords(string value, string separator = " ") 
     { 
      return SeparatorRegex.Replace(value, separator + "$1"); 
     } 
    } 
} 

Voici un extrait des tests (XUnit):

[Theory] 
[InlineData("PurchaseOrders", "Purchase-Orders")] 
[InlineData("purchaseOrders", "purchase-Orders")] 
[InlineData("2Unlimited", "2-Unlimited")] 
[InlineData("The2Unlimited", "The-2-Unlimited")] 
[InlineData("Unlimited2", "Unlimited-2")] 
[InlineData("222Unlimited", "222-Unlimited")] 
[InlineData("The222Unlimited", "The-222-Unlimited")] 
[InlineData("Unlimited222", "Unlimited-222")] 
[InlineData("ATeam", "A-Team")] 
[InlineData("TheATeam", "The-A-Team")] 
[InlineData("TeamA", "Team-A")] 
[InlineData("HTMLGuide", "HTML-Guide")] 
[InlineData("TheHTMLGuide", "The-HTML-Guide")] 
[InlineData("TheGuideToHTML", "The-Guide-To-HTML")] 
[InlineData("HTMLGuide5", "HTML-Guide-5")] 
[InlineData("TheHTML5Guide", "The-HTML-5-Guide")] 
[InlineData("TheGuideToHTML5", "The-Guide-To-HTML-5")] 
[InlineData("TheUKAllStars", "The-UK-All-Stars")] 
[InlineData("AllStarsUK", "All-Stars-UK")] 
[InlineData("UKAllStars", "UK-All-Stars")] 
+1

+ 1 pour expliquer l'expression rationnelle et le rendre lisible. Et j'ai appris quelque chose de nouveau. Il existe un mode d'espacement libre et des commentaires dans .NET Regex. Je vous remercie! –

2

Ci-dessous est un prototype qui convertit les éléments suivants à titre de cas:

  • snake_case
  • camelCase
  • PascalCase
  • phrase cas
  • Titre Case (maintenir la mise en forme actuelle)

Évidemment, vous n'auriez besoin que de la méthode "ToTitleCase".

using System; 
using System.Collections.Generic; 
using System.Globalization; 
using System.Text.RegularExpressions; 

public class Program 
{ 
    public static void Main() 
    { 
     var examples = new List<string> { 
      "THEQuickBrownFox", 
      "theQUICKBrownFox", 
      "TheQuickBrownFOX", 
      "TheQuickBrownFox", 
      "the_quick_brown_fox", 
      "theFOX", 
      "FOX", 
      "QUICK" 
     }; 

     foreach (var example in examples) 
     { 
      Console.WriteLine(ToTitleCase(example)); 
     } 
    } 

    private static string ToTitleCase(string example) 
    { 
     var fromSnakeCase = example.Replace("_", " "); 
     var lowerToUpper = Regex.Replace(fromSnakeCase, @"(\p{Ll})(\p{Lu})", "$1 $2"); 
     var sentenceCase = Regex.Replace(lowerToUpper, @"(\p{Lu}+)(\p{Lu}\p{Ll})", "$1 $2"); 
     return new CultureInfo("en-US", false).TextInfo.ToTitleCase(sentenceCase); 
    } 
} 

La console hors serait comme suit:

THE Quick Brown Fox 
The QUICK Brown Fox 
The Quick Brown FOX 
The Quick Brown Fox 
The Quick Brown Fox 
The FOX 
FOX 
QUICK 

Blog Post Referenced

0

mise en œuvre du code de psudo: https://stackoverflow.com/a/5796394/4279201

private static StringBuilder camelCaseToRegular(string i_String) 
    { 
     StringBuilder output = new StringBuilder(); 
     int i = 0; 
     foreach (char character in i_String) 
     { 
      if (character <= 'Z' && character >= 'A' && i > 0) 
      { 
       output.Append(" "); 
      } 
      output.Append(character); 
      i++; 
     } 
     return output; 
    } 
1

Regex est d'environ 10 à 12 fois plus lent qu'une simple boucle:

public static string CamelCaseToSpaceSeparated(this string str) 
    { 
     if (string.IsNullOrEmpty(str)) 
     { 
      return str; 
     } 

     var res = new StringBuilder(); 

     res.Append(str[0]); 
     for (var i = 1; i < str.Length; i++) 
     { 
      if (char.IsUpper(str[i])) 
      { 
       res.Append(' '); 
      } 
      res.Append(str[i]); 

     } 
     return res.ToString(); 
    } 
0

impl procédure et rapide:

/// <summary> 
    /// Get the words in a code <paramref name="identifier"/>. 
    /// </summary> 
    /// <param name="identifier">The code <paramref name="identifier"/></param> to extract words from. 
    public static string[] GetWords(this string identifier) { 
    Contract.Ensures(Contract.Result<string[]>() != null, "returned array of string is not null but can be empty"); 
    if (identifier == null) { return new string[0]; } 
    if (identifier.Length == 0) { return new string[0]; } 

    const int MIN_WORD_LENGTH = 2; // Ignore one letter or one digit words 

    var length = identifier.Length; 
    var list = new List<string>(1 + length/2); // Set capacity, not possible more words since we discard one char words 
    var sb = new StringBuilder(); 
    CharKind cKindCurrent = GetCharKind(identifier[0]); // length is not zero here 
    CharKind cKindNext = length == 1 ? CharKind.End : GetCharKind(identifier[1]); 

    for (var i = 0; i < length; i++) { 
     var c = identifier[i]; 
     CharKind cKindNextNext = (i >= length - 2) ? CharKind.End : GetCharKind(identifier[i + 2]); 

     // Process cKindCurrent 
     switch (cKindCurrent) { 
      case CharKind.Digit: 
      case CharKind.LowerCaseLetter: 
       sb.Append(c); // Append digit or lowerCaseLetter to sb 
       if (cKindNext == CharKind.UpperCaseLetter) { 
       goto TURN_SB_INTO_WORD; // Finish word if next char is upper 
       } 
       goto CHAR_PROCESSED; 
      case CharKind.Other: 
       goto TURN_SB_INTO_WORD; 
      default: // charCurrent is never Start or End 
       Debug.Assert(cKindCurrent == CharKind.UpperCaseLetter); 
       break; 
     } 

     // Here cKindCurrent is UpperCaseLetter 
     // Append UpperCaseLetter to sb anyway 
     sb.Append(c); 

     switch (cKindNext) { 
      default: 
       goto CHAR_PROCESSED; 

      case CharKind.UpperCaseLetter: 
       // "SimpleHTTPServer" when we are at 'P' we need to see that NextNext is 'e' to get the word! 
       if (cKindNextNext == CharKind.LowerCaseLetter) { 
       goto TURN_SB_INTO_WORD; 
       } 
       goto CHAR_PROCESSED; 

      case CharKind.End: 
      case CharKind.Other: 
       break; // goto TURN_SB_INTO_WORD; 
     } 

     //------------------------------------------------ 

    TURN_SB_INTO_WORD: 
     string word = sb.ToString(); 
     sb.Length = 0; 
     if (word.Length >= MIN_WORD_LENGTH) { 
      list.Add(word); 
     } 

    CHAR_PROCESSED: 
     // Shift left for next iteration! 
     cKindCurrent = cKindNext; 
     cKindNext = cKindNextNext; 
    } 

    string lastWord = sb.ToString(); 
    if (lastWord.Length >= MIN_WORD_LENGTH) { 
     list.Add(lastWord); 
    } 
    return list.ToArray(); 
    } 
    private static CharKind GetCharKind(char c) { 
    if (char.IsDigit(c)) { return CharKind.Digit; } 
    if (char.IsLetter(c)) { 
     if (char.IsUpper(c)) { return CharKind.UpperCaseLetter; } 
     Debug.Assert(char.IsLower(c)); 
     return CharKind.LowerCaseLetter; 
    } 
    return CharKind.Other; 
    } 
    enum CharKind { 
    End, // For end of string 
    Digit, 
    UpperCaseLetter, 
    LowerCaseLetter, 
    Other 
    } 

Tests:

[TestCase((string)null, "")] 
    [TestCase("", "")] 

    // Ignore one letter or one digit words 
    [TestCase("A", "")] 
    [TestCase("4", "")] 
    [TestCase("_", "")] 
    [TestCase("Word_m_Field", "Word Field")] 
    [TestCase("Word_4_Field", "Word Field")] 

    [TestCase("a4", "a4")] 
    [TestCase("ABC", "ABC")] 
    [TestCase("abc", "abc")] 
    [TestCase("AbCd", "Ab Cd")] 
    [TestCase("AbcCde", "Abc Cde")] 
    [TestCase("ABCCde", "ABC Cde")] 

    [TestCase("Abc42Cde", "Abc42 Cde")] 
    [TestCase("Abc42cde", "Abc42cde")] 
    [TestCase("ABC42Cde", "ABC42 Cde")] 
    [TestCase("42ABC", "42 ABC")] 
    [TestCase("42abc", "42abc")] 

    [TestCase("abc_cde", "abc cde")] 
    [TestCase("Abc_Cde", "Abc Cde")] 
    [TestCase("_Abc__Cde_", "Abc Cde")] 
    [TestCase("ABC_CDE_FGH", "ABC CDE FGH")] 
    [TestCase("ABC CDE FGH", "ABC CDE FGH")] // Should not happend (white char) anything that is not a letter/digit/'_' is considered as a separator 
    [TestCase("ABC,CDE;FGH", "ABC CDE FGH")] // Should not happend (,;) anything that is not a letter/digit/'_' is considered as a separator 
    [TestCase("abc<cde", "abc cde")] 
    [TestCase("abc<>cde", "abc cde")] 
    [TestCase("abc<D>cde", "abc cde")] // Ignore one letter or one digit words 
    [TestCase("abc<Da>cde", "abc Da cde")] 
    [TestCase("abc<cde>", "abc cde")] 

    [TestCase("SimpleHTTPServer", "Simple HTTP Server")] 
    [TestCase("SimpleHTTPS2erver", "Simple HTTPS2erver")] 
    [TestCase("camelCase", "camel Case")] 
    [TestCase("m_Field", "Field")] 
    [TestCase("mm_Field", "mm Field")] 
    public void Test_GetWords(string identifier, string expectedWordsStr) { 
    var expectedWords = expectedWordsStr.Split(' '); 
    if (identifier == null || identifier.Length <= 1) { 
     expectedWords = new string[0]; 
    } 

    var words = identifier.GetWords(); 
    Assert.IsTrue(words.SequenceEqual(expectedWords)); 
    } 
Questions connexes