キャメルケースまたはTitleCaseを分割する正規表現(詳細)


81

キャメルケースまたはTitleCase式の一部を抽出するための素晴らしい正規表現を見つけました。

 (?<!^)(?=[A-Z])

期待どおりに機能します。

  • 値->値
  • キャメルバリュー->キャメル/バリュー
  • TitleValue->タイトル/値

たとえば、Javaの場合:

String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
//words equals words = new String[]{"lorem","Ipsum"}

私の問題は、それがいくつかの場合に機能しないことです:

  • ケース1:値-> V / A / L / U / E
  • ケース2:eclipseRCPExt-> eclipse / R / C / P / Ext

私の考えでは、結果は次のようになります。

  • ケース1:VALUE
  • ケース2:日食/ RCP /内線

言い換えると、n個の大文字が与えられます。

  • n文字の後に小文字が続く場合、グループは次のようになります:(n-1文字)/(n番目の文字+小文字)
  • n文字が最後にある場合、グループは次のようになります:(n文字)。

この正規表現を改善する方法について何かアイデアはありますか?


おそらく^、ネガティブルックビハインドの大文字の条件付き修飾子と別の条件付き大文字小文字が必要になるようです。確かなテストはしていませんが、問題を解決するための最善の策だと思います。
ナイトファイアキャット2011

誰かが調べているなら
ハマグリ2016年

回答:


112

次の正規表現は、上記のすべての例で機能します。

public static void main(String[] args)
{
    for (String w : "camelValue".split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) {
        System.out.println(w);
    }
}   

これは、文字列の先頭の一致を無視するだけでなく、大文字の前に別の大文字が付いている一致も無視するように、ネガティブルックビハインドを強制することによって機能します。これは「VALUE」のような場合を処理します。

正規表現の最初の部分は、「RPC」と「Ext」の間で分割できないため、「eclipseRCPExt」で失敗します。これが2番目の節の目的です(?<!^)(?=[A-Z][a-z]。この句では、文字列の先頭を除いて、すべての大文字の前で分割し、その後に小文字を続けることができます。


1
これはPHPでは機能しませんが、@ ridgerunnerでは機能します。PHPでは、「ルックビハインドアサーションはオフセット13の固定長ではありません」と表示されます。
igorsantos07 2014

15
@Igoru:正規表現のフレーバーはさまざまです。質問はPHPではなくJavaに関するものであり、答えもそうです。
NPE 2014

1
質問が「java」としてタグ付けされている間、質問はまだ一般的です-コードサンプル(それは決して一般的ではあり得ない)を除いて。したがって、この正規表現のより単純なバージョンがあり、それが言語間でも機能する場合は、誰かがそれを指摘する必要があると思いました:)
igorsantos07 2014

7
@Igoru:「一般的な正規表現」は架空の概念です。
Casimir et Hippolyte 2014

3
@ igorsantos07:いいえ、組み込みの正規表現の実装はプラットフォーム間で大きく異なります。Perlのようにしようとしているものもあれば、POSIXのようにしようとしているものもあり、その中間にあるものやまったく異なるものもあります。
クリストファーHammarström

78

これを必要以上に複雑にしているようです。以下のためにキャメルケース、分割位置は、単にどこでも大文字はすぐに小文字を次のされています。

(?<=[a-z])(?=[A-Z])

この正規表現がサンプルデータを分割する方法は次のとおりです。

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCPExt

希望する出力との唯一の違いは、でありeclipseRCPExt、ここで正しく分割されていると私は主張します。

補遺-改善されたバージョン

注:この回答は最近賛成を得て、もっと良い方法があることに気づきました...

上記の正規表現に2つ目の選択肢を追加することで、OPのすべてのテストケースが正しく分割されます。

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

改善された正規表現がサンプルデータを分割する方法は次のとおりです。

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCP / Ext

編集:20130824RCPExt -> RCP / Extケースを処理するための改善されたバージョンを追加しました。


ご意見ありがとうございます。この例では、パーツを定数名に変換するため、RCPとExtを分離する必要があります(スタイルガイドライン:「単語を区切るためにアンダースコアを使用するすべて大文字」)。この場合、ECLIPSE_RCPEXTよりもECLIPSE_RCP_EXTを使用します。
jmini 2011

4
助けてくれてありがとう; 私は、文字列に数字の世話をするためにオプションのカップルを追加するために、あなたの正規表現を変更した:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
thoroc

これがベストアンサーです!シンプルで明確。ただし、この回答とOPによる元の正規表現はJavascriptとGolangでは機能しません。
ベトナム


10

私はaixのソリューションを機能させることができませんでした(そしてそれはRegExrでも機能しません)、それで私は私がテストしたものを思いつきました、そしてあなたが探していることを正確に行うようです:

((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))

これを使用する例を次に示します。

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms.
;   (^[a-z]+)                       Match against any lower-case letters at the start of the string.
;   ([A-Z]{1}[a-z]+)                Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))", "$1 ")
newString := Trim(newString)

ここでは、各単語をスペースで区切っているので、文字列がどのように変換されるかの例を次に示します。

  • ThisIsATitleCASEString =>これはタイトルのCASE文字列です
  • andThisOneIsCamelCASE =>そしてこれはキャメルケースです

上記のこのソリューションは、元の投稿が要求することを実行しますが、数字を含むラクダとパスカルの文字列を見つけるために正規表現も必要だったので、数字を含めるためにこのバリエーションも考え出しました。

((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))

およびその使用例:

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms and including numbers.
;   (^[a-z]+)                               Match against any lower-case letters at the start of the command.
;   ([0-9]+)                                Match against one or more consecutive numbers (anywhere in the string, including at the start).
;   ([A-Z]{1}[a-z]+)                        Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)|([0-9])))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string or a number.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))", "$1 ")
newString := Trim(newString)

そして、数字を含む文字列がこの正規表現でどのように変換されるかの例をいくつか示します。

  • myVariable123 =>私の変数123
  • my2Variables => my2変数
  • The3rdVariableIsHere => 3つのrdVariableはここにあります
  • 12345NumsAtTheStartIncludedToo => 12345開始時のNumsも含まれています

1
不要なキャプチャグループが多すぎます。あなたはそれを次のように書くことができたでしょう:(^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))最初のものの(^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))ために、そして2番目のもののために。最も外側のも除去することができるが、全体のマッチを参照する構文は、言語間のポータブルでない($0及び$&2つの可能性あり)。
nhahtdh 2014

同じ簡略化された正規表現:([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
AlexSuhinin19年

3

単なる文字よりも多くの文字を処理するにはA-Z

s.split("(?<=\\p{Ll})(?=\\p{Lu})|(?<=\\p{L})(?=\\p{Lu}\\p{Ll})");

どちらか:

  • 小文字の後に分割し、その後に大文字を続けます。

parseXML-> parseXML

または

  • 任意の文字の後に分割し、その後に大文字と小文字を続けます。

XMLParser-> XMLParser


より読みやすい形式で:

public class SplitCamelCaseTest {

    static String BETWEEN_LOWER_AND_UPPER = "(?<=\\p{Ll})(?=\\p{Lu})";
    static String BEFORE_UPPER_AND_LOWER = "(?<=\\p{L})(?=\\p{Lu}\\p{Ll})";

    static Pattern SPLIT_CAMEL_CASE = Pattern.compile(
        BETWEEN_LOWER_AND_UPPER +"|"+ BEFORE_UPPER_AND_LOWER
    );

    public static String splitCamelCase(String s) {
        return SPLIT_CAMEL_CASE.splitAsStream(s)
                        .collect(joining(" "));
    }

    @Test
    public void testSplitCamelCase() {
        assertEquals("Camel Case", splitCamelCase("CamelCase"));
        assertEquals("lorem Ipsum", splitCamelCase("loremIpsum"));
        assertEquals("XML Parser", splitCamelCase("XMLParser"));
        assertEquals("eclipse RCP Ext", splitCamelCase("eclipseRCPExt"));
        assertEquals("VALUE", splitCamelCase("VALUE"));
    }    
}

3

簡単な

ここでの両方の上位の回答は、ポジティブルックビハインドを使用したコードを提供しますが、これはすべての正規表現フレーバーでサポートされているわけではありません。正規表現は、以下の両方をキャプチャしますPascalCaseし、camelCase複数の言語で使用することができます。

注:この質問はJavaに関するものだと思いますが、異なる言語でタグ付けされた他の質問でこの投稿について複数の言及があり、同じ質問に対するコメントもいくつかあります。

コード

ここで使用されているこの正規表現を参照してください

([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)

結果

サンプル入力

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

サンプル出力

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

説明

  • 1つ以上の大文字の英字に一致する [A-Z]+
  • または、ゼロまたは1つの大文字の英字に一致し[A-Z]?、その後に1つ以上の小文字の英字が続きます[a-z]+
  • 以下が大文字の英字[A-Z]または単語境界文字であることを確認してください\b


0

以下の式をJavaに使用できます。

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?=[A-Z][a-z])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)

3
こんにちはMaicon、StackOverflowへようこそ。ご回答ありがとうございます。これは質問に答えるかもしれませんが、それが問題をどのように解決するを学ぶために他の人に説明を提供しません。コードの説明を含めるように回答を編集できますか?ありがとうございました!
ティム・マローン2016

0

そこにないセパレーターを探す代わりに、名前のコンポーネントを見つけることも検討するかもしれません(それらは確かにそこにあります):

String test = "_eclipse福福RCPExt";

Pattern componentPattern = Pattern.compile("_? (\\p{Upper}?\\p{Lower}+ | (?:\\p{Upper}(?!\\p{Lower}))+ \\p{Digit}*)", Pattern.COMMENTS);

Matcher componentMatcher = componentPattern.matcher(test);
List<String> components = new LinkedList<>();
int endOfLastMatch = 0;
while (componentMatcher.find()) {
    // matches should be consecutive
    if (componentMatcher.start() != endOfLastMatch) {
        // do something horrible if you don't want garbage in between

        // we're lenient though, any Chinese characters are lucky and get through as group
        String startOrInBetween = test.substring(endOfLastMatch, componentMatcher.start());
        components.add(startOrInBetween);
    }
    components.add(componentMatcher.group(1));
    endOfLastMatch = componentMatcher.end();
}

if (endOfLastMatch != test.length()) {
    String end = test.substring(endOfLastMatch, componentMatcher.start());
    components.add(end);
}

System.out.println(components);

これはを出力します[eclipse, 福福, RCP, Ext]。もちろん、配列への変換は簡単です。


0

([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)上記のctwheelsで指定された正規表現文字列がMicrosoftの正規表現で機能することを確認できます。

また、数字を処理するctwheelsの正規表現に基づいて、次の代替案を提案したいと思います([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b)

これにより、次のような文字列を分割できます。

DrivingB2BTradeIn2019Onwards

2019年以降のB2B取引の推進


0

JavaScriptソリューション

/**
 * howToDoThis ===> ["", "how", "To", "Do", "This"]
 * @param word word to be split
 */
export const splitCamelCaseWords = (word: string) => {
    if (typeof word !== 'string') return [];
    return word.replace(/([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)/g, '!$&').split('!');
};

彼らはJavaScriptソリューションを求めていますが、なぜ同じソリューションを2回提供しているのですか?これらの質問が同一であると思われる場合は、投票して1つを重複として閉じてください。
トト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.