8

question related to this

I have a string

a\;b\\;c;d

which in Java looks like

String s = "a\\;b\\\\;c;d"

I need to split it by semicolon with following rules:

  1. If semicolon is preceded by backslash, it should not be treated as separator (between a and b).

  2. If backslash itself is escaped and therefore does not escape itself semicolon, that semicolon should be separator (between b and c).

So semicolon should be treated as separator if there is either zero or even number of backslashes before it.

For example above, I want to get following strings (double backslashes for java compiler):

a\;b\\
c
d
Community
  • 1
  • 1
lstipakov
  • 3,078
  • 4
  • 29
  • 46

5 Answers5

9

You can use the regex

(?:\\.|[^;\\]++)*

to match all text between unescaped semicolons:

List<String> matchList = new ArrayList<String>();
try {
    Pattern regex = Pattern.compile("(?:\\\\.|[^;\\\\]++)*");
    Matcher regexMatcher = regex.matcher(subjectString);
    while (regexMatcher.find()) {
        matchList.add(regexMatcher.group());
    } 

Explanation:

(?:        # Match either...
 \\.       # any escaped character
|          # or...
 [^;\\]++  # any character(s) except semicolon or backslash; possessive match
)*         # Repeat any number of times.

The possessive match (++) is important to avoid catastrophic backtracking because of the nested quantifiers.

Tim Pietzcker
  • 313,408
  • 56
  • 485
  • 544
  • It also returns empty strings, so I got `[a\;b\\, , c, , d, ]`. Is it possible somehow prevent it, except checking returned value of group()? – lstipakov Oct 26 '11 at 11:53
  • Yes, with a + instead of *, you get rid of the empty strings – Maurice Perry Oct 26 '11 at 11:56
  • Strange, it doesn't do this in my tests (in RegexBuddy, though). Well, if you don't want empty matches, change the `*` to `+`, but then you'll also not get "real" empty matches like in `a;;b`. – Tim Pietzcker Oct 26 '11 at 11:58
  • yep, real empty matches are fine. – lstipakov Oct 26 '11 at 12:16
  • FYI edge case: when last field ends with a escape char '\', or the input is just a sole escape char, the last escape char is lost, that is "a\" => ["a", "", ""]. The following seems to fix that edge case `"(?:\\\\(.|$)|[^;\\\\]++)*"` but not sure if creates another. My expression (so far) to also solve false empty fields but retain real empty fields is `"(?<=(?:^|;))(?:\\\\(?:.|$)|[^;\\\\]++)*"`. Thanks for neat idea. – AnyDev Aug 21 '20 at 04:43
0

This is the real answer i think. In my case i am trying to split using | and escape character is &.

    final String regx = "(?<!((?:[^&]|^)(&&){0,10000}&))\\|";
    String[] res = "&|aa|aa|&|&&&|&&|s||||e|".split(regx);
    System.out.println(Arrays.toString(res));

In this code i am using Lookbehind to escape & character. note that the look behind must have maximum length.

(?<!((?:[^&]|^)(&&){0,10000}&))\\|

this means any | except those that are following ((?:[^&]|^)(&&){0,10000}&)) and this part means any odd number of &s. the part (?:[^&]|^) is important to make sure that you are counting all of the &s behind the | to the beginning or some other characters.

Rasoul
  • 43
  • 1
  • 5
0
String[] splitArray = subjectString.split("(?<!(?<!\\\\)\\\\);");

This should work.

Explanation :

// (?<!(?<!\\)\\);
// 
// Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!(?<!\\)\\)»
//    Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!\\)»
//       Match the character “\” literally «\\»
//    Match the character “\” literally «\\»
// Match the character “;” literally «;»

So you just match the semicolons not preceded by exactly one \.

EDIT :

String[] splitArray = subjectString.split("(?<!(?<!\\\\(\\\\\\\\){0,2000000})\\\\);");

This will take care of any odd number of . It will of course fail if you have more than 4000000 number of \. Explanation of edited answer :

// (?<!(?<!\\(\\\\){0,2000000})\\);
// 
// Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!(?<!\\(\\\\){0,2000000})\\)»
//    Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!\\(\\\\){0,2000000})»
//       Match the character “\” literally «\\»
//       Match the regular expression below and capture its match into backreference number 1 «(\\\\){0,2000000}»
//          Between zero and 2000000 times, as many times as possible, giving back as needed (greedy) «{0,2000000}»
//          Note: You repeated the capturing group itself.  The group will capture only the last iteration.  Put a capturing group around the repeated group to capture all iterations. «{0,2000000}»
//          Match the character “\” literally «\\»
//          Match the character “\” literally «\\»
//    Match the character “\” literally «\\»
// Match the character “;” literally «;»
FailedDev
  • 26,037
  • 9
  • 50
  • 71
  • This fails for `a\\\;b;c` and other cases with more than two backslashes. – Tim Pietzcker Oct 26 '11 at 11:38
  • Can someone explain the downvoting? Unless I am missing something obvious? – FailedDev Oct 26 '11 at 11:53
  • I don't know, it wasn't me. Perhaps nested backreferences are a bit too complicated? – Tim Pietzcker Oct 26 '11 at 12:00
  • It wasn't me either, but don't look to me for an upvote. ;) That `{0,many}` hack is untrustworthy because Java's variable-width lookbehind support is notoriously buggy. But I wouldn't use this approach even in .NET, which imposes no restrictions at all on lookbehinds. A positive-matching approach like Tim's is more readable, more reliable, and much more portable (the possessive quantifiers are not essential). – Alan Moore Oct 26 '11 at 12:04
  • @AlanMoore Agreed. This does not mean the solution is wrong though. – FailedDev Oct 26 '11 at 12:07
0

I do not trust to detect those cases with any kind of regular expression. I usually do a simple loop for such things, I'll sketch it using C since it's ages ago I last touched Java ;-)

int i, len, state;
char c;

for (len=myString.size(), state=0, i=0; i < len; i++) {
    c=myString[i];
    if (state == 0) {
       if (c == '\\') {
            state++;
       } else if (c == ';') {
           printf("; at offset %d", i);
       }
    } else {
        state--;
    }
}

The advantages are:

  1. you can execute semantic actions on each step.
  2. it's quite easy to port it to another language.
  3. you don't need to include the complete regex library just for this simple task, which adds to portability.
  4. it should be a lot faster than the regular expression matcher.
hochl
  • 11,784
  • 10
  • 53
  • 83
0

This approach assumes that your string will not have char '\0' in your string. If you do, you can use some other char.

public static String[] split(String s) {
    String[] result = s.replaceAll("([^\\\\])\\\\;", "$1\0").split(";");
    for (int i = 0; i < result.length; i++) {
        result[i] = result[i].replaceAll("\0", "\\\\;");
    }
    return result;
}
krico
  • 5,653
  • 2
  • 24
  • 28