I got f.e. a string
foo-bar/baz 123
and a pattern
#foo-(bar/baz)#
This pattern would give me the captured bar/baz,
But i would like to replace (sanitize) the / with a - to receive bar-baz.
Reason: i got a method that gets a string as parameter, and the regex pattern via config.
The tool around this is dynamically and will look up the returned match as an id.
But the target|entity to find is using - instead of `/ยด in the id.
So i now could hard code some exception like ~"if is this adapter then replace this and that" -
but i wonder if i could do that we regex.
Test code:
// Somewhere in a loop ...
$string = "foo-bar/baz 123"; // Would get dynamically as parameter.
$pattern = "#foo-(bar/baz)#"; // Would get dynamically from config.
// ...
if (preg_match($pattern, $string, $matches) === 1) {
// return $matches[1]; Would return captured id.
echo var_export($matches, true) . PHP_EOL;
}
Returns
array (
0 => 'foo-bar/baz',
1 => 'bar/baz',
)
Expected|Need
array (
0 => 'foo-bar/baz',
1 => 'bar-baz', // <-- "-" instead of "/"
)
So im searching a way to "match-replace".
Related
I have some PHP code that accepts an uploaded file from an HTML form then reads through it using regex to look for specific lines (in the case below, those with "Track Number" followed by an integer).
The file is an XML file that looks like this normally...
<key>Disc Number</key><integer>2</integer>
<key>Disc Count</key><integer>2</integer>
<key>Track Number</key><integer>1</integer>
But when PHP reads it in it gets rid of the XML tags for some reason, leaving me with just...
Disc Number2
Disc Count2
Track Number1
The file has to be XML, and I don't want to use SimpleXML cause that's a whole other headache. The regex matches the integers like I want it to (I can print them out "0","1","2"...) but of course they're returned as strings in $matches, and it seems I'm unable to make use of these strings. I need to check if the integer is between 0 and 9 but I um unable to do this no matter what I try.
Using intval() or (int) to first convert the matches to integers always returns 0 even though the given string contains only integers. And using in_array to compare the integer to an array of 0-9 as strings always returns false as well for some reason. Here's the trouble code...
$myFile = file($myFileTmp, FILE_IGNORE_NEW_LINES);
$numLines = count($myFile) - 1;
$matches = array();
$nums = array('0','1','2','3','4','5','6','7','8','9');
for ($i=0; $i < $numLines; $i++) {
$line = trim($myFile[$i]);
$numberMatch = preg_match('/Track Number(.*)/', $line, $matches); // if I try matching integers specifically it doesn't return a match at all, only if I do it like this - it gives me the track number I want but I can't do anything with it
if ($numberMatch == 1 and ctype_space($matches[1]) == False) {
$number = trim($matches[1]); // string containing an integer only
echo(intval($number)); // conversion doesn't work - returns 0 regardless
if (in_array($number,$nums)===True) { // searching in array doesn't work - returns FALSE regardless
$number = "0" . $number;
}
}
}
I've tried type checking, double quotes, single quotes, trimming whitespace, UTF8 encoding, === operator, regex matching numbers specifically with (\d+) (which doesn't return a match at all)...what else could it possibly be? When I try these things with regular strings it works fine, but the regex is messing everything up here. I'm about to give up on this app entirely, please save me.
Why is SimpleXML not an option? Consider the following code:
$str = "<container><key>Disc Number</key><integer>2</integer>
<key>Disc Count</key><integer>2</integer>
<key>Track Number</key><integer>1</integer></container>";
$xml = simplexml_load_string($str);
foreach ($xml->key as $k) {
// do sth. here with it
}
You should read RegEx match open tags except XHTML self-contained tags -- while doesn't exactly match your use case it has good reasons why one should use something besides straight up regexp matching for your use case.
Assuming that files only contain a single Track Number you can simplify what you're doing a lot. See the following:
test.xml
<key>Disc Number</key><integer>2</integer>
<key>Disc Count</key><integer>2</integer>
<key>Track Number</key><integer>1</integer>
test.php
<?php
$contents = file_get_contents('test.xml');
$result = preg_match_all("/<key>Track Number<\/key><integer>(\d)<\/integer>/", $contents, $matches);
if ($result > 0) {
print_r($matches);
$trackNumber = (int) $matches[1][0];
print gettype($trackNumber) . " - " . $trackNumber;
}
Result
$ php -f test.php
Array
(
[0] => Array
(
[0] => <key>Track Number</key><integer>1</integer>
)
[1] => Array
(
[0] => 1
)
)
integer - 1%
As you can see, there is no need to iterate through the files line by line when using preg_match_all. The matching here is very specific so you don't have to do extra checks for whitespace or validate that it's a number. Which you're doing against a string value currently.
I need a code to get this strings from inside a href tag
Example of weburl:
/video/funny-videos-with-dogs-21608674/
The strings what i need is:
Url Title = funny-videos-with-dogs
Url ID = 21608674
How i can get this 2 strings from the url via preg_match?
Update:
What i try so far is:
preg_match('/\/video\/(.*?)-/is', $vUrl, $vUrl_Title);
but is show me just "funny" ... i need something to can show "funny-videos-with-dogs"
This should be an easy one:
<?php
if (preg_match("#/video/([a-z\-]+)-([0-9]+)/#", "/video/funny-videos-with-dogs-21608674/", $matches)) {
print_r($matches);
$urlTitle = $matches[1];
$urlID = $matches[2];
}
else {
print_r("Not found!");
}
And this yells
Array
(
[0] => /video/funny-videos-with-dogs-21608674/
[1] => funny-videos-with-dogs
[2] => 21608674
)
Taking into account that non other chars should be matched. I'm pretty sure you can simplify the regexp, I don't have a deep regexp knowledge but this should work
I have a reqular expression for getting sub-string from a string, if a particular string doesn't exist then try to get another sub-string.
Reqular expression i am trying is this:
\#\s*\((.*?)\)|\((.*?)\)
But this does not work, and always get the second option sub-string instead of the first option.
Example string is
some text (2nd Sub-string) # (First sub-string)
And it give me this result:
Array
(
[0] => (2nd Sub-string)
[1] =>
[2] => 2nd Sub-string
)
Why don't you simply get both strings (for which your regexp works correctly) and check their existence programmatically? Something like:
$num = preg_match_all(
"/\#\s*\((.*?)\)|\((.*?)\)/",
"some text (2nd Sub-string) # (First sub-string)",
$matches, PREG_SET_ORDER
);
var_dump($num, $matches);
if($num < 2)
{
// no second match, read first
}
if(!array_key_exists(2, $matches[1]))
{
// another way to put it
}
HTH.
I'm trying to make a main menu bar link dynamic, based on the visitor's current page.
I started with
$path = $_SERVER['REQUEST_URI'];
Which, of course, returns things like
/subfolder/page.html
/subfolder1/subfolder2/page.html
/page.html
I need to grab whatever is after the first '/'. I've tried messing around with explode, but I stumble with what to do with the resulting array. I'm also going cross-eyed trying to write a regex - seems a more elegant solution.
Then I need to build my switch. Something along the lines of:
switch ($path)
{
case '/subfolder0':
$link = $root_url.'/subfolder0/anotherfolder/page.html';
break;
case '/subfolder1':
$link = $root_url.'/subfolder1/page.html';
break;
default:
$link = $root_url.'/subfolder2/page.html';
}
Finally, should I be using if...elseif for this in lieu of switch?
Thanks for your time, all!
To grab everything after the first /:
strstr($_SERVER['REQUEST_URI'], '/');
Or, with regex:
preg_match('#(/.*)#', $_SERVER['REQUEST_URI'], $matches); // $matches[1] will be the path
As far as the switch, I'd say if/elseif/else is the least-elegant in your case, switch isn't bad, but personally I'd go with an associative array:
$mapping = array('/subfolder0' => $root_url.'/subfolder0/anotherfolder/page.html', 'etc' => 'etc');
$link = $mapping($path);
This lets you keep the mapping in another file for organization, and makes it a little bit easier to maintain by separating configuration from implementation.
Using explode is not at all a bad idea if you are interested in all the parts of the URI, you should take a look at the documentation for explode
Its usage would be like so:
$exploded = explode('/','/path/to/page.html');
echo $exploded[0]; // Will print out '' (empty string)
echo $exploded[1]; // Will print out 'path'
echo $exploded[2]; // Will print out 'to'
echo $exploded[3]; // Will print out 'page.html'
However as far as I understand, you are looking to replace the link by whatever is after the first character (which is always '/'), you could use substr like so:
// Get whatever is after the first character and put it into $path
$path = substr($_SERVER['REQUEST_URI'], 1);
In your case, it is not needed because you are able to predict there is a backslash at the beginning of the string.
I would also suggest using an associative array to replace the URL.
I would implement the entire thing like so (removing the first backslash as you require):
// Define the URLs for replacement
$urls = array(
'subfolder0' => '/subfolder0/anotherfolder/page.html',
'subfolder1' => '/subfolder1/page.html'
);
// Get the request URI, trimming its first character (always '/')
$path = substr($_SERVER['REQUEST_URI'], 1);
// Set the link according to $urls associative array, or set
// the default URL if not found
$link = $urls[$path] or '/subfolder2/page.html';
Or with explode, taking only the first part of the URI:
// Get the parts of the request
$requestParts = explode('/', $_SERVER['REQUEST_URI']);
// Set the link according to $urls associative array, or set
// the default URL if not found
$link = $urls[$requestParts[1]] or '/subfolder2/page.html';
After analyzing the OP's question, I think he/she meant to phrase it as "Everything after the first '/', but before the second '/'. Here is what I got:
You could try this regex:
<?php
/*
* Regex: /((\w+?|\w+\.\w+?)(?!^\/))(?=\/.*$|$)/
*/
$paths = array(
'/subfolder9/',
'/subfolder/page.html',
'/subfolder1/subfolder2/page.html',
'/page.html'
);
foreach ($paths as $path) {
preg_match("/((\w+?|\w+\.\w+?)(?!^\/))(?=\/.*$|$)/", $path, $matches);
debug($matches);
}
// $matches[1] will contain the first group ( ) matched in the expression.
// or "subfolder<#>" or "<page>.<ext>"
// The loop results is as follows:
Array
(
[0] => subfolder9
[1] => subfolder9
[2] => subfolder9
)
Array
(
[0] => subfolder
[1] => subfolder
[2] => subfolder
)
Array
(
[0] => subfolder1
[1] => subfolder1
[2] => subfolder1
)
Array
(
[0] => page.html
[1] => page.html
[2] => page.html
)
?>
Note: This only works with regex flavors that support look-arounds (zero-width positive & negative look ahead are the ones used the example.)
This a great cheat sheet for regular expressions and I don't code without it.
Regular-Expressions.info - (click to view)
You can just use dirname function to get what you want:
$path = dirname('/subfolder/page.html'); // returns '/subfolder'
$path = dirname('/subfolder1/subfolder2/page.html'); // returns '/subfolder1'
$path = dirname('page.html'); // returns '.'
EDIT: Regex based solution:
$path = preg_replace('#^(/[^/]*).*$#', '$1', '/subfolder/page.html' )
I am trying to match a semi dynamically generated string. So I can see if its the correct format, then extract the information from it that I need. My Problem is I no matter how hard I try to grasp regex can't fathom it for the life of me. Even with the help of so called generators.
What I have is a couple different strings like the following. [#img:1234567890] and [#user:1234567890] and [#file:file_name-with.ext]. Strings like this pass through are intent on passing through a filter so they can be replaced with links, and or more readable names. But again try as I might I can't come up with a regex for any given one of them.
I am looking for the format: [#word:] of which I will strip the [, ], #, and word from the string so I can then turn around an query my DB accordingly for whatever it is and work with it accordingly. Just the regex bit is holding me back.
Not sure what you mean by generators. I always use online matchers to see that my test cases work. #Virendra almost had it except forgot to escape the [] charaters.
/\[#(\w+):(.*)\]/
You need to start and end with a regex delimeter, in this case the '/' character.
Then we escape the '[]' which is use by regex to match ranges of characters hence the '['.
Next we match a literal '#' symbol.
Now we want to save this next match so we can use it later so we surround it with ().
\w matches a word. Basically any characters that aren't spaces, punctuation, or line characters.
Again match a literal :.
Maybe useful to have the second part in a match group as well so (.*) will match any character any number of times, and save it for you.
Then we escape the closing ] as we did earlier.
Since it sounds like you want to use the matches later in a query we can use preg_match to save the matches to an array.
$pattern = '/\[#(\w+):(.*)\]/';
$subject = '[#user:1234567890]';
preg_match($pattern, $subject, $matches);
print_r($matches);
Would output
array(
[0] => '[#user:1234567890]', // Full match
[1] => 'user', // First match
[2] => '1234567890' // Second match
)
An especially helpful tool I've found is txt2re
Here's what I would do.
<pre>
<?php
$subj = 'An image:[#img:1234567890], a user:[#user:1234567890] and a file:[#file:file_name-with.ext]';
preg_match_all('~(?<match>\[#(?<type>[^:]+):(?<value>[^\]]+)\])~',$subj,$matches,PREG_SET_ORDER);
foreach ($matches as &$arr) unset($arr[0],$arr[1],$arr[2],$arr[3]);
print_r($matches);
?>
</pre>
This will output
Array
(
[0] => Array
(
[match] => [#img:1234567890]
[type] => img
[value] => 1234567890
)
[1] => Array
(
[match] => [#user:1234567890]
[type] => user
[value] => 1234567890
)
[2] => Array
(
[match] => [#file:file_name-with.ext]
[type] => file
[value] => file_name-with.ext
)
)
And here's a pseudo version of how I would use the preg_replace_callback() function:
function replace_shortcut($matches) {
global $users;
switch (strtolower($matches['type'])) {
case 'img' : return '<img src="images/img_'.$matches['value'].'jpg" />';
case 'file' : return ''.$matches['value'].'';
// add id of each user in array
case 'user' : $users[] = (int) $matches['value']; return '%s';
default : return $matches['match'];
}
}
$users = array();
$replaceArr = array();
$subj = 'An image:[#img:1234567890], a user:[#user:1234567890] and a file:[#file:file_name-with.ext]';
// escape percentage signs to avoid complications in the vsprintf function call later
$subj = strtr($subj,array('%'=>'%%'));
$subj = preg_replace_callback('~(?<match>\[#(?<type>[^:]+):(?<value>[^\]]+)\])~',replace_shortcut,$subj);
if (!empty($users)) {
// connect to DB and check users
$query = " SELECT `id`,`nick`,`date_deleted` IS NOT NULL AS 'deleted'
FROM `users` WHERE `id` IN ('".implode("','",$users)."')";
// query
// ...
// and catch results
while ($row = $con->fetch_array()) {
// position of this id in users array:
$idx = array_search($row['id'],$users);
$nick = htmlspecialchars($row['nick']);
$replaceArr[$idx] = $row['deleted'] ?
"<span class=\"user_deleted\">{$nick}</span>" :
"{$nick}";
// delete this key so that we can check id's not found later...
unset($users[$idx]);
}
// in here:
foreach ($users as $key => $value) {
$replaceArr[$key] = '<span class="user_unknown">User'.$value.'</span>';
}
// replace each user reference marked with %s in $subj
$subj = vsprintf($subj,$replaceArr);
} else {
// remove extra percentage signs we added for vsprintf function
$subj = preg_replace('~%{2}~','%',$subj);
}
unset($query,$row,$nick,$idx,$key,$value,$users,$replaceArr);
echo $subj;
You can try something like this:
/\[#(\w+):([^]]*)\]/
\[ escapes the [ character (otherwise interpreted as a character set); \w means any "word" character, and [^]]* means any non-] character (to avoid matching past the end of the tag, as .* might). The parens group the various matched parts so that you can use $1 and $2 in preg_replace to generate the replacement text:
echo preg_replace('/\[#(\w+):([^]]*)\]/', '$1 $2', '[#link:abcdef]');
prints link abcdef