<p>Here's a problem I ran into recently.  I have attributes strings of the form</p>

<pre><code>"x=1 and y=abc and z=c4g and ..."
</code></pre>

<p>Some attributes have numeric values, some have alpha values, some have mixed, some have dates, etc.</p>

<p>Every string is <em>supposed</em> to have "<code>x=someval and y=anotherval</code>" at the beginning, but some don't.  I have three things I need to do.  </p>

<ol>
<li>Validate the strings to be certain that they have <code>x</code> and <code>y</code>.  </li>
<li>Actually parse the values for <code>x</code> and <code>y</code>.</li>
<li>Get the rest of the string.</li>
</ol>

<p>Given the example at the top, this would result in the following variables:</p>

<pre><code>$x = 1;
$y = "abc";
$remainder = "z=c4g and ..."
</code></pre>

<p>My question is: Is there a (reasonably) simple way to parse these <em>and</em> validate with a single regular expression?  i.e.:</p>

<pre><code>if ($str =~ /someexpression/)
{
    $x = $1;
    $y = $2;
    $remainder = $3;
}
</code></pre>

<p>Note that the string may consist of <em>only</em> <code>x</code> and <code>y</code> attributes.  This is a valid string.</p>

<p>I'll post my solution as an answer, but it doesn't meet my single-regex preference.</p>

[{'answer_id': 10537, 'author': 'Derek Park', 'author_id': 872, 'author_profile': 'https://Stackoverflow.com/users/872', 'pm_score': 0, 'selected': False, 'text': "<p>Here's basically what I did to solve this:</p>\n\n<pre><code>($x_str, $y_str, $remainder) = split(/ and /, $str, 3);\n\nif ($x_str !~ /x=(.*)/)\n{\n    # error\n}\n\n$x = $1;\n\nif ($y_str !~ /y=(.*)/)\n{\n    # error\n}\n\n$y = $1;\n</code></pre>\n\n<p>I've omitted some additional validation and error handling.  This technique works, but it's not as concise or pretty as I would have liked.  I'm hoping someone will have a better suggestion for me.</p>\n"}, {'answer_id': 10573, 'author': 'Rudd Zwolinski', 'author_id': 219, 'author_profile': 'https://Stackoverflow.com/users/219', 'pm_score': 2, 'selected': True, 'text': '<p>I\'m not the best at regular expressions, but this seems pretty close to what you\'re looking for:</p>\n\n<pre><code>/x=(.+) and y=([^ ]+)( and (.*))?/\n</code></pre>\n\n<p>Except you use $1, $2, and $4.  In use:</p>\n\n<pre><code>my @strs = ("x=1 and y=abc and z=c4g and w=v4l",\n            "x=yes and y=no",\n            "z=nox and w=noy");\n\nforeach (@strs) {\n    if ($_ =~ /x=(.+) and y=([^ ]+)( and (.*))?/) {\n        $x = $1;\n        $y = $2;\n        $remainder = $4;\n        print "x: $x; y: $y; remainder: $remainder\\n";\n    } else {\n        print "Failed.\\n";\n    }\n}\n</code></pre>\n\n<p>Output:</p>\n\n<pre><code>x: 1; y: abc; remainder: z=c4g and w=v4l\nx: yes; y: no; remainder: \nFailed.\n</code></pre>\n\n<p>This of course leaves out plenty of error checking, and I don\'t know everything about your inputs, but this seems to work.</p>\n'}, {'answer_id': 13677, 'author': 'Cebjyre', 'author_id': 1612, 'author_profile': 'https://Stackoverflow.com/users/1612', 'pm_score': 1, 'selected': False, 'text': '<p>As a fairly simple modification to Rudd\'s version,</p>\n\n<pre><code>/^x=(.+) and y=([^ ]+)(?: and (.*))?/\n</code></pre>\n\n<p>will allow you to use $1, $2 and $3 (the ?: makes it a noncapturing group), and will ensure that the string starts with "x=" rather than allowing a "not_x=" to match</p>\n\n<p>If you have better knowledge of what the x and y values will be, this should be used to tighten the regex further:</p>\n\n<pre><code>my @strs = ("x=1 and y=abc and z=c4g and w=v4l",\n        "x=yes and y=no",\n        "z=nox and w=noy",\n        "not-x=nox and y=present",\n        "x=yes and w=\'there is no and y=something arg here\'");\n\nforeach (@strs) {\n    if ($_ =~ /^x=(.+) and y=([^ ]+)(?: and (.*))?/) {\n        $x = $1;\n        $y = $2;\n        $remainder = $3;\n        print "x: {$x}; y: {$y}; remainder: {$remainder}\\n";\n    } else {\n        print "$_ Failed.\\n";\n    }\n}\n</code></pre>\n\n<p>Output:</p>\n\n<pre><code>x: {1}; y: {abc}; remainder: {z=c4g and w=v4l}\nx: {yes}; y: {no}; remainder: {}\nz=nox and w=noy Failed.\nnot-x=nox and y=present Failed.\nx: {yes and w=\'there is no}; y: {something}; remainder: {}\n</code></pre>\n\n<p>Note that the missing part of the last test is due to the current version of the y test requiring no spaces, if the x test had the same restriction that string would have failed.</p>\n'}, {'answer_id': 21003, 'author': 'Brad Gilbert', 'author_id': 1337, 'author_profile': 'https://Stackoverflow.com/users/1337', 'pm_score': 2, 'selected': False, 'text': '<p>Assuming you also want to do something with the other name=value pairs this is how I would do it ( using Perl version 5.10 ):</p>\n\n<pre><code>use 5.10.0;\nuse strict;\nuse warnings;\n\nmy %hash;\nwhile(\n    $string =~ m{\n       (?: ^ | \\G )    # start of string or previous match\n       \\s*\n\n       (?&lt;key&gt;   \\w+ ) # word characters\n       =\n       (?&lt;value&gt; \\S+ ) # non spaces\n\n       \\s*             # get to the start of the next match\n       (?: and )?\n    }xgi\n){\n    $hash{$+{key}} = $+{value};\n}\n\n# to make sure that x &amp; y exist\ndie unless exists $hash{x} and exists $hash{y};\n</code></pre>\n\n<p>On older Perls ( at least Perl 5.6 );</p>\n\n<pre><code>use strict;\nuse warnings;\n\nmy %hash;\nwhile(\n    $string =~ m{\n       (?: ^ | \\G )   # start of string or previous match\n       \\s*\n\n       ( \\w+ ) = ( \\S+ )\n\n       \\s*            # get to the start of the next match\n       (?: and )?\n    }xgi\n){\n    $hash{$1} = $2;\n}\n\n# to make sure that x &amp; y exist\ndie unless exists $hash{x} and exists $hash{y};\n</code></pre>\n\n<p>These have the added benefit of continuing to work if you need to work with more data.</p>\n'}, {'answer_id': 63872, 'author': 'Frosty', 'author_id': 7476, 'author_profile': 'https://Stackoverflow.com/users/7476', 'pm_score': 1, 'selected': False, 'text': '<p>Rudd and Cebjyre have gotten you most of the way there but they both have certain problems:</p>\n\n<p>Rudd suggested:</p>\n\n<blockquote>\n  <p>/x=(.+) and y=([^ ]+)( and (.*))?/</p>\n</blockquote>\n\n<p>Cebjyre modified it to:</p>\n\n<blockquote>\n  <p>/^x=(.+) and y=([^ ]+)(?: and (.*))?/</p>\n</blockquote>\n\n<p>The second version is better because it will not confuse "not_x=foo" with "x=foo" but will accept things such as "x=foo z=bar y=baz" and set $1 = "foo z=bar" which is undesirable.</p>\n\n<p>This is probably what you are looking for:</p>\n\n<blockquote>\n  <p>/^x=(\\w+) and y=(\\w+)(?: and (.*))?/</p>\n</blockquote>\n\n<p>This disallows anything between the x= and y= options, places  and allows and optional " and..." which will be in $3</p>\n'}]
['https://Stackoverflow.com/questions/10533', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/872/']