Match string that doesn't have number after letter - php

I've got a scenario as follows. Our systems needs to pull filters from a string passed in as a query parameter, but also throw a 404 error if the string isn't correctly formatted. So let's take the following three strings as an exmple:
pf0pt1000r
pfasdfadf
pf2000pt2100
By the application requirements, only #3 is supposed to match as a "valid" string. So my current regex to match that is /([a-z]+)(\d+)/. But this also matches #1, if not entirely, but it still matches.
My problem thus is twofold - I need 2 patterns, 1 that will match only the 3rd string in this list, and another that will match the "not-acceptable" strings 1 and 2. I believe there must be some way to "negate" a pattern (so then I'd technically only need one pattern, I'm assuming), but I'm not sure how exactly to do that.
Thanks for any help!
EDIT
For clarity's sake, let me explain. The "filter parameters" present here take the following structure - 1 or 2 letters, followed by a number of, well, numbers. That structure can repeat itself however many times. So for example, valid filter strings could be
pf100pt2000
pf100pt2000r2wp0to1
etc.
Invalid strings could be
pf10000pt2000r
pf3000pt2123wpno
... anything not following the structure above.

After clarifying the question:
^([a-zA-Z]{1,2}\d+)*$
Explanation:
[a-zA-Z] - a lower or upper case letter
{1,2} - one or two of those
\d+ - one or more digits
()* - the whole thing repeated any number of times
^$ - match the entire string from start(^) to end($)

You can use this regex for valid input:
^([a-zA-Z]+\d+)+$
RegEx Demo 1
To find invalid inputs use:
^(?!([a-zA-Z]+\d+)+$).+$
RegEx Demo 2

/^(?:(?:[a-z]+)(?:\d+))*$/
You were hella close, man. Just need to repeat that pattern over and over again till the end.
Change the * to a + to reject the empty string.
Oh, you had more specific requirements, try this:
/^(?:[a-z]{1,2}\d+)*$/
Broken down:
^ - Matches the start of the string an anchor
(?: - start a non-capturing group
[a-z] - A to Z. This you already had.
{1,2} - Repeat 1 or 2 times
\d+ - a digit or more You had this, too.
)* - Repeat that group ad nauseum
$ - Match the end of the string

If you only want digits at the end of the string, then
/\d$/
would do. \d = digit, $ = end of string.

Related

Regex to select a certain word followed by a integer or decimal?

I need a regular expression to detect the phrase Figure 1.5: in a given string. Also, I intend on using this expression in a PHP preg_replace() function.
Here are some more examples:
...are given. Figure 2.1: shows that...
...are given. Figure 3: shows that...
...are given. Figure 1.16: shows that...
...are given. Figure 0.4 shows that...
...are given. figure 5.1: shows that...
With my limited Regex knowledge, I was able to create this:
/\wFigure \d*\.?\d*/g
But that doesn't even begin to handle all of the permutations that could occur.
I would appreciate any suggestions that you might have.
There are several points here:
You are using \w at the start, perhaps, as a word boundary. In fact, \w matches a letter, digit or _ and actually requires this char to be at the exact location. However, there is no word char before Figure, so you need to either remove \w or replace with \b.
preg_replace replaces all non-overlapping occurrences by default, you do not need the g modifier
\d*\.?\d* is fine here, but since you want to match any digits followed with zero or more occurrences of . and digits you can use a more specific pattern like \d+(?:\.\d+)*.
You can use
preg_replace('/Figure \d+(?:\.\d+)*/', '', $string)
See the regex demo.
Details:
Figure - a string
- a space (replace with \s+ to match any one or more whitespaces, and consider adding u flag after last / if you need to find all Unicode whitespaces)
\d+ - one or more digits
(?:\.\d+)* - zero or more occurrences of . and one or more digits.

Regex to get the first number after a certain string followed by any data until the number

I have a piece of data, retrieved from the database and containing information I need. Text is entered in a free form so it's written in many different ways. The only thing I know for sure is that I'm looking for the first number after a given string, but after that certain string (before the number) can be any text as well.
I tried this (where mytoken is the string I know for sure its there) but this doesn't work.
/(mytoken|MYTOKEN)(.*)\d{1}/
/(mytoken|MYTOKEN)[a-zA-Z]+\d{1}/
/(mytoken|MYTOKEN)(.*)[0-9]/
/(mytoken|MYTOKEN)[a-zA-Z]+[0-9]/
Even mytoken can be written in capitals, lowercase or a mix of capitals and lowercase character. Can the expression be case insensitive?
You do not need any lazy matching since you want to match any number of non-digit symbols up to the first digit. It is better done with a \D*:
/(mytoken)(\D*)(\d+)/i
See the regex demo
The pattern details:
(mytoken) - Group 1 matching mytoken (case insensitively, as there is a /i modifier)
(\D*) - Group 2 matching zero or more characters other than a digit
(\d+) - Group 3 matching 1 or more digits.
Note that \D also matches newlines, . needs a DOTALL modifier to match across newlines.
You need to use a lazy quantifier. You can do that by putting a question mark after the star quantifier in the regex: .*?. Otherwise, the numbers will be matched by the dot operator until the last number, which will be matched by \d.
Regex: /(mytoken|MYTOKEN)(.*?)\d/
Regex demo
You can use the opposite:
/(mytoken|MYTOKEN)(\D+)(\d)/
This says: mytoken, followed by anything not a number, followed by a number. The (lazy) dot-star-soup is not always your best bet. The desired number will be in $3 in this example.

Merge two regex and match strings but not with period (or dot or full stop)

I'm using PHP. I need to validate the following kind of strings:
B223213FCF#COM
B90TY13AAA#DE
232342342X#CO.UK
Which patterns is:
"B" followed by 2 digits and 7 alphanumeric values
9 digits and as tenth character either a "X" or another digit
After the # the following domain extensions are accepted:
com.au, ca, cn, com, de, es, fr, in, it, co.jp, com.mx, nl, co.uk
I managed to work out the following two regex which somehow work:
/B[0-9]{2}[0-9A-Z]{7}|[0-9]{9}(X|0-9])
#(com\.au|ca|cn|com|de|es|fr|in|it|co\.jp|com\.mx|nl|co\.uk)/i
The issues I'm facing are:
I don't understand how to merge the two regex in a single one
The second regex needs to be fixed to not match dots as in this case:
B223213FCF#COM.
because the above string (with the dot at the end) still validates. The domain extension must be considered as exact word I suppose.
You can merge the two regexps as
^(?:B\d{2}\w{7}|\d{9}X)#(?:co\.(?:uk|jp)|com(?:\.(?:au|mx))?|c[an]|de|es|fr|i[nt]|nl)$
See the regex demo
I introduced anchors ^ and $ to make sure the pattern is applied to the entire string, added two alternatives before # ((?:B\d{2}\w{7} and \d{9}X)), and shrunk the TLDs to make the regex more efficient (since that way fewer backtracking steps are necessary).
The regex matches:
^ - start of string
(?:B\d{2}\w{7}|\d{9}X) - two alternatives:
B\d{2}\w{7} - B followed with 2 digits followed with 2 word characters
| - or...
\d{9}X - nine digits followed with X
# - a literal # sign
(?:co\.(?:uk|jp)|com(?:\.(?:au|mx))?|c[an]|de|es|fr|i[nt]|nl) - the list of TLD alternatives
$ - end of string
NOTE
If the first part up to # must be case sensitive and the second one should not, omit the general /i modifier at the end of the regex declaration and use the (?i:...) syntax to force the second part to be case insensitive:
'~^(?:B\d{2}\w{7}|\d{9}X)#(?i:co\.(?:uk|jp)|com(?:\.(?:au|mx))?|c[an]|de|es|fr|i[nt]|nl)$~'
See another regex demo

Regex OR matching stuff that I dont want

I am using PHP.
I have a strings like:
example.123.somethingelse
example.1234.somethingelse
example.2015.123.somethingelse
example.2015.1234.somethingelse
and I came up with this regex
/example\.(2015\.|)([0-9]{3,4})\./
What I want to get is "123" or "1234" and it works for these strings. But when the string is
example.2015.A01.somethingelse
the result is "2015".
The way that I see it, after "2015." I have "A" and this should not be matched by the regex, but it is ( and I suppose there is a solid reason for it that I dont understand atm).
How can I fix it ( make the regex match nothing since the last string does not follow the same structure as the others) ?
Your regex is this:
/example\.(2015\.|)([0-9]{3,4})\./
That says
First match "example" followed by a period
Then match either "2015" followed by a period OR nothing at all.
Then match 3 or 4 digits in a row followed by a period
When you have the string example.2015.A01.somethingelse it matches the "example.2015." but then, as you said, the "A" messes it up so it backtracks and matches just "example." (remember the "OR" allowed for nothing to be matched). So it matches "example." followed by NOTHING followed by 3 or 4 numeric digits -- since "2015" is 4 numeric digits it comfortably matches "example.2015".
It's hard to tell from your description, but I think you've just got a mis-placed vertical bar:
/example\.(2015\.)|([0-9]{3,4})\./
That should match EITHER "example.2015." OR numbers like 123 -- but "2015" is still 4 numeric digits in a row, so it will still match. I don't have a clear enough idea of the pattern to figure out how that could be avoided.
Maybe use \d+ and take the first result in the array.
In your regex, you use the following:
(2015\.|)
This allows the regex to match either 2015. or the empty string (zero characters).
When the regex example\.(2015\.|)([0-9]{3,4})\. is applied to the following example:
example.2015.A01.somethingelse
it will to match the literal characters example, and then the empty string with (2015\.|) and then uses ([0-9]{3,4})\. to match the string 2015, which is 4 numerical characters. Thus your expression matches the following:
example.2015.
Looks like you need a possessive quantifier:
/example\.(2015\.)?+([0-9]{3,4})\./
The 2015. is still optional, but once the regex has matched it, it won't give it up, even if that causes the match to fail. I'm assuming the substring you're trying to capture with ([0-9]{3,4}) can never have the value 2015. That is, you won't need to match something like this:
example.2015.somethingelse
If that's not the case, it's going to be much more complicated.
here is one more pattern
example\.(?:2015\.)?\K(\d+)
Demo
or to your specific amount of digits
example\.(?:2015\.)?\K(\d{3,4})

Matching ugly extra abbreviations and numbers in titles with PHP regex

I have to create regex to match ugly abbreviations and numbers. These can be one of following "formats":
1) [any alphabet char length of 1 char][0-9]
2) [double][whitespace][2-3 length of any alphabet char]
I tried to match double:
preg_match("/^-?(?:\d+|\d*\.\d+)$/", $source, $matches);
But I coldn't get it to select following example: 1.1 AA My test title. What is wrong with my regex and how can I add those others to my regex too?
In your regex you say "start of string, followed by maybe a - followed by at least one digit or followed by 0 or more digits, followed by a dot and followed by at least one digit and followed by the end of string.
So you regex could match for example.. 4.5, -.1 etc. This is exactly what you tell it to do.
You test input string does not match since there are other characters present after the number 1.1 and even if it somehow magically matched your "double" matching regex is wrong.
For a double without scientific notation you usually use this regex :
[-+]?\b[0-9]+(\.[0-9]+)?\b
Now that we have this out of our way we need a whitespace \s and
[2-3 length of alphabet]
Now I have no idea what [2-3 length of alphabet] means but by combining the above you get a regex like this :
[-+]?\b[0-9]+(\.[0-9]+)?\b\s[2-3 length of alphabet]
You can also place anchors ^$ if you want the string to match entirely :
^[-+]?\b[0-9]+(\.[0-9]+)?\b\s[2-3 length of alphabet]$
Feel free to ask if you are stuck! :)
I see multiple issues with your regex:
You try to match the whole string (as a number) by the anchors: ^ at the beginning and $ at the end. If you don't want that, remove those.
The number group is non-catching. It will be checked for matches, but those won't be added to $matches. That's because of the ?: internal options you set in (?:...). Remove ?: to make that group catching.
You place the shorter digit-pattern before the longer one. If you swap the order, the regex engine will look for it first and on success prefer it over the shorter one.
Maybe this already solves your issue:
preg_match("/-?(\d*\.\d+|\d+)/", $source, $matches);
Demo

Categories