Can someone provide a regular expression for parsing name/value pairs from a string? The pairs are separated by commas, and the value can optionally be enclosed in quotes.
This is how I would do it if you can use Perl 5.10
.
qr/ (?<key> (?: [^=,\\] | (?&escape) )++ # Prevent null keys ) \s*+ = \s*+ (?<value> (?"ed) | (?: [^=,\s\\] | (?&escape) )++ # Prevent null value ( use quotes for that ) ) (?(DEFINE) (?<escape>\\.) (?<quoted> " (?: (?&escaped) | [^"\\] )*+ " ) ) /x
The elements would be accessed through %+
.
perlretut was very helpful in creating this answer.
No escape:
/([^=,]*)=("[^"]*"|[^,"]*)/
Double quote escape for both key and value:
/((?:"[^"]*"|[^=,])*)=((?:"[^"]*"|[^=,])*)/
key=value,"key with "" in it"="value with "" in it",key=value" "with" "spaces
Backslash string escape:
/([^=,]*)=("(?:\\.|[^"\\]+)*"|[^,"]*)/
key=value,key="value",key="val\"ue"
Full backslash escape:
/((?:\\.|[^=,]+)*)=("(?:\\.|[^"\\]+)*"|(?:\\.|[^,"\\]+)*)/
key=value,key="value",key="val\"ue",ke\,y=val\,ue
Edit: Added escaping alternatives.
Edit2: Added another escaping alternative.
You would have to clean up the keys/values by removing any escape-characters and surrounding quotes.
Nice answer from MizardX. Minor niggles - it doesn't allow for spaces around names etc (which may not matter), and it collects the quotes as well as the quoted value (which also may not matter), and it doesn't have an escape mechanism for embedding double quote characters in the quoted value (which, once more, may not matter).
As written, the pattern works with most of the extended regular expression systems. Fixing the niggles would probably require descent into, say, Perl. This version uses doubled quotes to escape -- hence a="a""b" generates a field value 'a""b' (which ain't perfect, but could be fixed afterwards easily enough):
/\s*([^=,\s]+)\s*=\s*(?:"((?:[^"]|"")*)"|([^,"]*))\s*,?/
Further, you'd have to use $2 or $3 to collect the value, whereas with MizardX's answer, you simply use $2. So, it isn't as easy or nice, but it covers a few edge cases. If the simpler answer is adequate, use it.
Test script:
#!/bin/perl -w
use strict;
my $qr = qr/\s*([^=,\s]+)\s*=\s*(?:"((?:[^"]|"")*)"|([^,"]*))\s*,?/;
while (<>)
{
while (m/$qr/)
{
print "1= $1, 2 = $2, 3 = $3\n";
$_ =~ s/$qr//;
}
}
This witters about either $2 or $3 being undefined - accurately.