O que acontece quando você tem que lidar com "OldMacDonaldAndMrO'TooleWentToMcDonalds"?
Grant Wagner
2
Seu uso será limitado. Estarei usando principalmente para analisar nomes de variáveis, como ThisIsMySpecialVariable,
Matias Nino
Isso funcionou para mim: Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim(). E se você quiser dividir em cada letra maiúscula, basta remover o sinal de mais.
Mladen B.
Respostas:
173
Eu fiz isso há um tempo. Corresponde a cada componente de um nome CamelCase.
Esta é a melhor solução até agora, mas você precisa usar \\ B para compilar. Caso contrário, o compilador tenta tratar o \ B como uma seqüência de escape.
Ferruccio
Ótima solução. Alguém consegue pensar em uma razão para que essa não seja a resposta aceita? É menos capaz ou tem menos desempenho?
Drew Noakes em
8
Este trata maiúsculas consecutivas como palavras separadas (por exemplo, ANZAC é 5 palavras), enquanto a resposta de MizardX o trata (corretamente IMHO) como uma palavra.
Ray
2
@Ray, eu diria que "ANZAC" deveria ser escrito como "Anzac" para ser considerada uma palavra caseira pascal, já que não é case inglesa.
Sam de
1
@Neaox, em inglês deveria ser, mas isso não é acronym-case ou normal-english-case; é delimitado por maiúsculas. Se o texto de origem deve ser capitalizado da mesma forma que em inglês normal, então as outras letras também não devem ser capitalizadas. Por exemplo, por que o "i" em "é" maiúsculo para caber no formato delimitado por maiúsculas, mas não o "NZAC" em "ANZAC"? Estritamente falando, se você interpretar "ANZAC" como delimitado por maiúsculas, terá 5 palavras, uma para cada letra.
Sam
19
Ótima resposta, MizardX! Eu ajustei um pouco para tratar os numerais como palavras separadas, de modo que "AddressLine1" se tornasse "Address Line 1" em vez de "Address Line1":
Excelente adição! Suspeito que não poucas pessoas ficarão surpresas com a maneira como as respostas aceitas tratam os números em strings. :)
Jordan Gray
Eu sei que já se passaram quase 8 anos desde que você postou isso, mas funcionou perfeitamente para mim também. :) Os números me surpreenderam no início.
Michael Armes
A única resposta que passa em meus 2 testes atípicos: "Take5" -> "Take 5", "PublisherID" -> "Publisher ID". Eu quero
votar a favor
18
Apenas para variar ... Aqui está um método de extensão que não usa regex.
publicstaticclassCamelSpaceExtensions{publicstaticstringSpaceCamelCase(thisString input){returnnewstring(Enumerable.Concat(
input.Take(1),// No space before initial capInsertSpacesBeforeCaps(input.Skip(1))).ToArray());}privatestaticIEnumerable<char>InsertSpacesBeforeCaps(IEnumerable<char> input){foreach(char c in input){if(char.IsUpper(c)){yieldreturn' ';}yieldreturn c;}}}
Para evitar o uso de Trim (), antes de foreach eu coloquei: int counter = -1. dentro, adicione contador ++. altere a seleção para: if (char.IsUpper (c) && counter> 0)
the Box Developer
Isso insere um espaço antes do primeiro caractere.
Zar Shardan
Tomei a liberdade de corrigir o problema apontado por @ZarShardan. Sinta-se à vontade para reverter ou editar para sua própria correção se não gostar da mudança.
jpmc26
Isso pode ser aprimorado para lidar com abreviações, por exemplo, adicionando um espaço antes da última maiúscula em uma série de letras maiúsculas, por exemplo, BOEForecast => BOE Forecast
Nepaluz
11
Deixando de lado o excelente comentário de Grant Wagner:
Dim s AsString=RegularExpressions.Regex.Replace("ThisIsMyCapsDelimitedString","([A-Z])"," $1")
Bom ponto ... Sinta-se à vontade para inserir o .substring (), .trimstart (), .trim (), .remove (), etc. de sua escolha. :)
Pseudo Masoquista
9
Eu precisava de uma solução que suportasse siglas e números. Esta solução baseada em Regex trata os seguintes padrões como "palavras" individuais:
Uma letra maiúscula seguida por letras minúsculas
Uma sequência de números consecutivos
Letras maiúsculas consecutivas (interpretadas como acrônimos) - uma nova palavra pode começar usando a última maiúscula, por exemplo, HTMLGuide => "Guia HTML", "TheATeam" => "The A Team"
usingSystem.Text.RegularExpressions;namespaceDemo{publicclassIntercappedStringHelper{privatestaticreadonlyRegexSeparatorRegex;staticIntercappedStringHelper(){conststring 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=newRegex(pattern, options);}publicstaticstringSeparateWords(stringvalue,string separator =" "){returnSeparatorRegex.Replace(value, separator +"$1");}}}
+ 1 para explicar a regex e torná-la legível. E aprendi algo novo. Há um modo de espaçamento livre e comentários no .NET Regex. Obrigado!
Felix Keil
4
Para obter mais variedade, usando objetos C # simples e antigos, o seguinte produz a mesma saída que a excelente expressão regular de @MizardX.
publicstringFromCamelCase(string camel){// omitted checking camel for nullStringBuilder sb =newStringBuilder();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++;}elseif(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();}
Eu sabia que haveria uma maneira fácil de RegEx ... Tenho que começar a usá-la mais.
Max Schmeling
1
Não é um guru de regex, mas o que acontece com "HeresAWTFString"?
Nick
1
Você ganha "Heres AWTF String", mas é exatamente o que Matias Nino pediu na pergunta.
Max Schmeling
Sim, ele precisa adicionar que "várias capitais adjacentes são deixadas sozinhas". O que é obviamente necessário em muitos casos, por exemplo, "PublisherID" aqui vai para "Publisher I D" que é terrível
PandaWood
2
Regex é cerca de 10-12 vezes mais lento do que um loop simples:
publicstaticstringCamelCaseToSpaceSeparated(thisstring str){if(string.IsNullOrEmpty(str)){return str;}var res =newStringBuilder();
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();}
Eu modifiquei você, mas as pessoas geralmente acham melhor uma smackdown se não começar com "ingênuo".
MusiGenesis
Eu não acho que foi um confronto direto. Nesse contexto, ingênuo geralmente significa óbvio ou simples (ou seja, não necessariamente a melhor solução). Não há intenção de insulto.
Ferruccio
0
Provavelmente há uma solução mais elegante, mas é isso que eu vim com o topo da minha cabeça:
string myString ="ThisIsMyCapsDelimitedString";for(int i =1; i < myString.Length; i++){if(myString[i].ToString().ToUpper()== myString[i].ToString()){
myString = myString.Insert(i," ");
i++;}}
privatestaticStringBuilder camelCaseToRegular(string i_String){StringBuilder output =newStringBuilder();int i =0;foreach(char character in i_String){if(character <='Z'&& character >='A'&& i >0){
output.Append(" ");}
output.Append(character);
i++;}return output;}
/// <summary>/// Get the words in a code <paramref name="identifier"/>./// </summary>/// <param name="identifier">The code <paramref name="identifier"/></param> to extract words from.publicstaticstring[]GetWords(thisstring identifier){Contract.Ensures(Contract.Result<string[]>()!=null,"returned array of string is not null but can be empty");if(identifier ==null){returnnewstring[0];}if(identifier.Length==0){returnnewstring[0];}constint MIN_WORD_LENGTH =2;// Ignore one letter or one digit wordsvar length = identifier.Length;var list =newList<string>(1+ length/2);// Set capacity, not possible more words since we discard one char wordsvar sb =newStringBuilder();CharKind cKindCurrent =GetCharKind(identifier[0]);// length is not zero hereCharKind 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 cKindCurrentswitch(cKindCurrent){caseCharKind.Digit:caseCharKind.LowerCaseLetter:
sb.Append(c);// Append digit or lowerCaseLetter to sbif(cKindNext ==CharKind.UpperCaseLetter){goto TURN_SB_INTO_WORD;// Finish word if next char is upper}goto CHAR_PROCESSED;caseCharKind.Other:goto TURN_SB_INTO_WORD;default:// charCurrent is never Start or EndDebug.Assert(cKindCurrent ==CharKind.UpperCaseLetter);break;}// Here cKindCurrent is UpperCaseLetter// Append UpperCaseLetter to sb anyway
sb.Append(c);switch(cKindNext){default:goto CHAR_PROCESSED;caseCharKind.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;caseCharKind.End:caseCharKind.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();}privatestaticCharKindGetCharKind(char c){if(char.IsDigit(c)){returnCharKind.Digit;}if(char.IsLetter(c)){if(char.IsUpper(c)){returnCharKind.UpperCaseLetter;}Debug.Assert(char.IsLower(c));returnCharKind.LowerCaseLetter;}returnCharKind.Other;}enumCharKind{End,// For end of stringDigit,UpperCaseLetter,LowerCaseLetter,Other}
Testes:
[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")]publicvoidTest_GetWords(string identifier,string expectedWordsStr){var expectedWords = expectedWordsStr.Split(' ');if(identifier ==null|| identifier.Length<=1){
expectedWords =newstring[0];}var words = identifier.GetWords();Assert.IsTrue(words.SequenceEqual(expectedWords));}
Uma solução simples, que deve ser de ordem (s) de magnitude mais rápida do que uma solução regex (com base nos testes que executei nas principais soluções neste segmento), especialmente conforme o tamanho da string de entrada aumenta:
string s1 ="ThisIsATestStringAbcDefGhiJklMnoPqrStuVwxYz";string s2;StringBuilder sb =newStringBuilder();foreach(char c in s1)
sb.Append(char.IsUpper(c)?" "+ c.ToString(): c.ToString());
s2 = sb.ToString();
Regex.Replace(s, "([A-Z0-9]+)", " $1").Trim()
. E se você quiser dividir em cada letra maiúscula, basta remover o sinal de mais.Respostas:
Eu fiz isso há um tempo. Corresponde a cada componente de um nome CamelCase.
Por exemplo:
Para converter isso para apenas inserir espaços entre as palavras:
Se você precisa lidar com dígitos:
fonte
fonte
Ótima resposta, MizardX! Eu ajustei um pouco para tratar os numerais como palavras separadas, de modo que "AddressLine1" se tornasse "Address Line 1" em vez de "Address Line1":
fonte
Apenas para variar ... Aqui está um método de extensão que não usa regex.
fonte
Deixando de lado o excelente comentário de Grant Wagner:
fonte
Eu precisava de uma solução que suportasse siglas e números. Esta solução baseada em Regex trata os seguintes padrões como "palavras" individuais:
Você poderia fazer isso como uma linha:
Uma abordagem mais legível pode ser melhor:
Aqui está um extrato dos testes (XUnit):
fonte
Para obter mais variedade, usando objetos C # simples e antigos, o seguinte produz a mesma saída que a excelente expressão regular de @MizardX.
fonte
Abaixo está um protótipo que converte o seguinte em caixa do título:
Obviamente, você só precisa do método "ToTitleCase".
A saída do console seria a seguinte:
Postagem do blog referenciada
fonte
fonte
Regex é cerca de 10-12 vezes mais lento do que um loop simples:
fonte
Solução regex ingênua. Não suporta O'Conner e adiciona um espaço no início da string também.
fonte
Provavelmente há uma solução mais elegante, mas é isso que eu vim com o topo da minha cabeça:
fonte
Tente usar
O resultado será adequado para mistura de alfabeto com números
fonte
Implementando o código psudo de: https://stackoverflow.com/a/5796394/4279201
fonte
Para fazer a correspondência entre a categoria Unicode sem maiúsculas e maiúsculas :
(?<=\P{Lu})(?=\p{Lu})
fonte
Implementação rápida e processual:
Testes:
fonte
Uma solução simples, que deve ser de ordem (s) de magnitude mais rápida do que uma solução regex (com base nos testes que executei nas principais soluções neste segmento), especialmente conforme o tamanho da string de entrada aumenta:
fonte