views:

100

answers:

4

My regex skills are not very good and recently a new data element has thrown my parser into a loop

Take the following string

"+USER=Bob Smith-GROUP=Admin+FUNCTION=Read/FUNCTION=Write"

Previously I had the following for my regex : [+\\-/]

Which would turn the result into

USER=Bob Smith
GROUP=Admin
FUNCTION=Read
FUNCTION=Write
FUNCTION=Read

But now I have values with dashes in them which is causing bad output

New string looks like "+USER=Bob Smith-GROUP=Admin+FUNCTION=Read/FUNCTION=Write/FUNCTION=Read-Write"

Which gives me the following result , and breaks the key = value structure.

USER=Bob Smith
GROUP=Admin
FUNCTION=Read
FUNCTION=Write
FUNCTION=Read
Write

Can someone help me formulate a valid regex for handling this or point me to some key / value examples. Basically I need to be able to handle + - / signs in order to get combinations.

A: 

You didn't specify what regex engine you're using, but this works if you've got lookahead/lookbehind.

It works on the premise that the keys are all uppercase only, whilst the values aren't - not sure if that's a valid assumption, but if it's not then as noted things will get complicated and messy.

(?<=[+-\/])[A-Z]+=(?:(?![A-Z]+=)[^=])+(?=[+-\/]|$)


And here's my attempt to explain that (not sure how much this makes sense):

(?x)         # enable regex comment mode
(?<=[+-\/])  # start with one of the delimiters, but excluded from match
[A-Z]+       # match one or more uppercase (for the key)
=            # match the equal sign

(?:          # start non-capturing group

  (?!          # start negative lookahead, to prevent keys matching
    [A-Z]+=      # a key and equals (since in negative lookahead, this is what we exclude)
  )            # end the negative lookahead
  [^=]         # match a character that's not =

)+           # end non-capturing group, match one or more times, until...

(?=[+-\/]|$) # next char must be delimiter or end of line for match to succeed


For Java string->regex, backslashes need escaping (as would quotes, if there were any):

Pattern p = Pattern.compile("(?<=[+-\\/])[A-Z]+=(?:(?![A-Z]+=)[^=])+(?=[+-\\/]|$)");


And if capturing groups are needed, just add parens round the appropriate parts:

Pattern p = Pattern.compile("(?<=[+-\\/])([A-Z]+)=((?:(?![A-Z]+=)[^=])+(?=[+-\\/]|$))");


The matching part of this, to turn it into newline delimited text, is something like...

Matcher m = p.Matcher( InputText );
StringBuffer Result = new StringBuffer("");

while ( m.find() )
{
   Result.append( m.Group() + "\n" );
}
Peter Boughton
Sorry I am using the java pattern to execute the regex Pattern p = Pattern.compile("[+\\-/]"); The values can be upper or lower case, I have no problem flipping them to be one case though.
wojtek_z
Well if you can force the key and value to always be different case, that allows you to differentiate, which means it may be possible. To use the above expression in Java, just double escape all the `\`s.
Peter Boughton
hhmm unfortunately I am unable to get this to work, it seems to work in my regex tester but in java code the results are funky to say the least. Not sure if I have the correct escaping where needed
wojtek_z
Make sure you're matching now, not replacing (as before) - I've added a quick (untested) sample of how to do that.
Peter Boughton
Also, can you add your existing Java code to the question?
Peter Boughton
Thank you sir this has solved my issue.
wojtek_z
A: 

Based on your second example, this regex: (\w+)=([\w|-|\s]+) returns these results:

USER=Bob Smith
GROUP=Admin
FUNCTION=Read
FUNCTION=Write
FUNCTION=Read-Write

The parenthesis provide groupings for each element, so each match will contain two groups, the first will have the part before the = (so USER,GROUP,FUNCTION) and the second will have the value (Bob Smith, Admin, Read, Write, Read-Write)

You can also name the groups if that would make it easier:

(?<funcrion>\w+)=(?<value>[\w|-|\s]+)  

Or if you don't care about the groups, you can remove the parens altogether

\w+=[\w|-|\s]+
ckramer
The named group stuff there wont work, it's .NET only syntax.
Peter Boughton
Oh, and that regex is wrong - you can't use alternation inside a character class - you'd want either `((?:\w|-|\s)+)` or `([\w\-\s]+)` - except that incorrectly adds GROUP key to USER value.
Peter Boughton
This appears to give me the negatives of the values, how can i flip this to get the key=value combination rather then just the = or - signs ?
wojtek_z
You'll need to switch from using `replace` to `match`.
Peter Boughton
A: 

Another option, if you've got a limited set of keys, you could just match:

(?<=[+-\\/])(USER|GROUP|FUNCTION)=[^=]+(?=$|[+-\\/](?:USER|GROUP|FUNCTION))


Which in Java I'd probably implement like this:

String Key = "USER|GROUP|FUNCTION" ;
String Delim = "[+-\\/]";
Pattern p = Pattern.compile("(?<="+Delim+")("+Key+")=[^=]+(?=$|"+Delim+"(?:"+Key+"))");

This relies on, for example "Write" not being a valid key (and if you can force the case of keys to be either "write" or "WRITE" then that means it'll work).


The matching part of this, to turn it into newline delimited text, is something like...

Matcher m = p.Matcher( InputText );
StringBuffer Result = new StringBuffer("");

while ( m.find() )
{
   Result.append( m.Group() + "\n" );
}
Peter Boughton
A: 

If you're delimiting fields with characters that can appear in values, you're screwed.

Suppose you receive a string like:

one=a-two=b-three=c-d-four=e

Should that parse into this?

one=a
two=b
three=c-d
four=e

Or should it parse into this?

one=a
two=b
three=c
d-four=e

How do you know? What's your basis for deciding this?

Robert Rossney