Using RegEx to match URL routes - regex

I'm building a PHP Framework for conclusion of my course, and I've stuck on a solution for match some custom routes and standard routes.
My framework's route are similar at routes of Zend Framework 1.
It's match standard routes for
/module/controller/action/param/value/param2/value2/paramn/valuen
The part of URI are optional, and the / route leads to application module, index controller and index action without params and values.
I'm stuck in some custom routes, that I define this way:
/blog/:postname/
/admin/logout/
/blog/posts/:year/:category/
/about/
That routes must match this examples URI requests.
/blog/my-first-post/
/blog/my-first-post/referenced/facebook/
/admin/logout/
/admin/logout/session-id/246753/action
/blog/posts/2013/turism/
/blog/posts/2013/turism/page/2/
But not had to match the standard routes. The custom routes must precede the standard routes.
Some examples of standard routes. Examples:
/
/application/
/application/index/
/application/index/index/
/blog/posts/view/id/3/
/admin/login/
/admin/login/logout (that one are the
/admin/blog/posts/edit/id/3/
/admin/blog/posts/edit/id/3/success/false/
The way I find to do this ellegantily is using RegEx for the matches, but I've trying to learn RegEx for more than one month and don't got it all.
PS: After match the current route, I must to bind the :variable with the related position in the REQUEST_URI.
Thank you for help.

While admittedly tempting, I wouldn't go with regex in this particular case. Even though I usually go that way. A simple loop and match would do, unless your course is setting some restrictions you have to follow.
I put together an example that should get the job done and runs in the console, just to show what i mean.
function get_route($uri){
$routes = [
'blog#show' => 'blog/:postname',
'admin#logout' => 'admin/logout',
'blog#category' => 'blog/posts/:year/:category',
'home#about' => 'about'
];
$params = [];
$uri = preg_replace('/#|\?.+/', '', $uri); // remove hash or query strings
$uri = preg_replace('/(^\/)?(\/$)?/', '', $uri); // trim slashes
$uri = explode('/', $uri);
$action = null;
foreach ($routes as $this_action => $this_route) { // loop through possible routes
$fractions = explode('/', $this_route);
if (sizeof($fractions) !== sizeof($uri)) continue; // did not match length of uri
for ($i=0; $i<sizeof($uri); $i++) { // compare each part of uri to each part of route
if (substr($fractions[$i], 0, 1) !== ':' && $fractions[$i] !== $uri[$i]) break; // not a match and not a param
if ($i === sizeof($uri)-1) { // made it to the last fraction!
$ii = 0;
foreach ($fractions as $fraction) {
if (substr($fraction, 0, 1) == ':') { // it's a param, map it!
$params[substr($fraction,1)] = $uri[$ii];
}
$ii++;
}
return ['action'=>$this_action, 'params'=>$params];
}
}
}
return false;
}

I could reach my needs with this code, a lot of tests has passed.
public function matchCustomRoute($uri)
{
if($uri == '')
{
return null;
}
$customRoutes = $this->getRoutes();
$explodeUri = explode('/', $uri);
$arrayUri = array();
foreach($explodeUri as $uriPart)
{
if($uriPart == '')
{
continue;
}
$arrayUri[] = $uriPart;
}
$countUri = count($arrayUri);
foreach($customRoutes as $key => $value)
{
$explodeRoute = explode('/',$value['route']);
$arrayRoute = array();
foreach($explodeRoute as $routePart)
{
if($routePart == '')
{
continue;
}
$arrayRoute[] = $routePart;
}
$countRoute = count($arrayRoute);
if($countRoute > $countUri)
{
continue;
}
$matches = 0;
for($i = 0 ; $i < $countRoute ; $i++)
{
$match = preg_match('/'.$arrayUri[$i].'/', '/'.$arrayRoute[$i].'/');
if($match == 0)
{
if(substr($arrayRoute[$i], 0, 1) == ':')
{
$value['params'][substr($arrayRoute[$i], 1)] = $arrayUri[$i];
}
else
{
continue;
}
}
$matches++;
}
if($matches == $countRoute)
{
return $value;
}
}
return null;
}
Thank you for help.

Related

Angular 8 custom currency mask

Right now I am making a currency mask directive that should be compatible with angular reactive forms. Here's my Stackblitz https://stackblitz.com/edit/angular-8-currency-directive-insert.
In the input element, I expect that when I enter 1, then 2, then 3, then 4, then 5 that I would see in the console {currency: "$1,234"} because the mask runs .substring(0,4) however I see {currency: "$1,2345"}.
I see the correct display value of $1,234 within the input element.
If I change .substring(0,4) to .substring(0,3) then the display value within the input element displays $1,23 when I expect it to display $1,234. The console outputs the correct value of {currency: "$1,234"}
Any suggestions that get to the root of the problem are very welcome! I have already done work arounds which involve things like splitting into an array, checking, popping off the end, and joining but those fixes are not ideal. Any suggestions are still welcome though!
Thank you for your support.
The code to focus on is found in currency.directive.ts provided below:
onInputChange(event, backspace) {
let newVal = event.replace(/\D/g, '');
if (newVal.length === 0) {
newVal = '';
} else if (newVal.length <= 3) {
newVal = newVal.replace(/^(\d{0,3})/, '$1');
// } else if (newVal.length <= 4) {
// newVal = newVal.replace(/^(\d{0,1})(\d{0,3})/, '$1,$2');
} else {
newVal = newVal.substring(0, 4);
newVal = newVal.replace(/^(\d{0,1})(\d{1,3})/, '$1,$2');
}
this.ngControl.valueAccessor.writeValue("$"+ newVal);
// console.log(this.toNumber(newVal))
}
Your question inspired me to create a CurrencyDirective that I would use. It does not approach this the way you have but I believe it could be used instead or hopefully to help others.
StackBlitz - Currency Format Directive
Reasons:
We should not be putting currency symbols in our value $1,234
We should format as the user types (painless UX)
We should be saving our currency values as raw numbers (strip
formatting)
We should not be regex'ing for 3 chars, 4 chars, 5 chars etc to conditionally add formatting (commas or dots)
Here's what I did instead.
I handle paste, input and drop events but the formatting is done within getCurrencyFormat():
getCurrencyFormat(val) {
// 1. test for non-number characters and replace/remove them
const filtered = parseInt(String(val).replace(this.currencyChars, ''));
// 2. format the number (add commas)
const usd = this.decimalPipe.transform(filtered, '1.0');
// 3. replace the input value with formatted numbers
this.renderer.setProperty(this.el.nativeElement, 'value', usd);
}
I believe that saving currency should be done in raw numbers. So on form submit I do this:
Number(this.form.get('currency').value.replace(/[^0-9]g/, ''));
Stackblitz https://stackblitz.com/edit/angular-8-currency-directive-insert-jdwx4b
currency custom input
import { Component, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-currency',
template: '<input [(ngModel)]="value" (keyup)="setValue(value)">',
styleUrls: ['./currency.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyComponent),
multi: true
}
]
})
export class CurrencyComponent implements ControlValueAccessor {
value;
constructor() {
}
setValue(event) {
let newVal = event.toString().replace(/\D/g, '');
if (newVal.length === 0) {
newVal = '';
} else if (newVal.length <= 3) {
newVal = newVal.replace(/^(\d{0,3})/, '$1');
} else {
newVal = newVal.substring(0, 4);
newVal = newVal.replace(/^(\d{0,1})(\d{1,3})/, '$1,$2');
}
newVal = '$' + newVal;
if (newVal) {
this.value = newVal;
setTimeout(() => {
// sometimes it needs a tick, specially first time
this.propagateChange(this.value);
});
}
}
writeValue(value: any) {
if (value !== undefined) {
this.setValue(value);
}
}
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() {
}
propagateChange = (_: any) => {
}
}
usage
<app-currency formControlName="currency"></app-currency>

Target child pages with regex

I need an regex which target all child pages of a certain group of parent pages, but NOT the parent pages them selfes.
To be more specific, I need an expression which targets:
/categoryA/XXX
/categoryB/YYY
/categoryC/ZZZ
But I do not want to include
/categoryA/
/categoryB/
/categoryC/
All help much appreciated!
Gustav
Try this one:
\/(\w+)\/([a-zA-Z]+)
I am assuming that the strings after the categories use letters only.
Input:
/categoryA/XXX
/categoryB/YYY
/categoryC/ZZZ
/categoryA/
/categoryB/
/categoryC/
Matches:
/categoryA/XXX
/categoryB/YYY
/categoryC/ZZZ
This one
([^\/]+$)
targets everything after the last slash
You could use this in an if() statement to filter out what you need, if I understand the question correctly.
Or this one:
\/category[A-Z]\/(.*)
In C#
childpage = Regex.Match(target, "/category[A-Z]/(.*)").Groups[1].Value;
In JavaScript
var myregexp = /\/category[A-Z]\/(.*)/;
var match = myregexp.exec(target);
if (match != null) {
childpage = match[1];
} else {
childpage = "";
}
In PHP
if (preg_match('%/category[A-Z]/(.*)%', $target, $groups)) {
$childpage = $groups[1];
} else {
$childpage = "";
}
In PowerShell
if ($target -match '/category[A-Z]/(.*)') {
$childpage = $matches[1]
} else {
$childpage = ''
}
In Python
match = re.search("/category[A-Z]/(.*)", target)
if match:
childpage = match.group(1)
else:
childpage = ""

Array comparison?

I am making a function that takes in an example and an ip address. For ex.
compare('192.168.*','192.168.0.42');
The asterix indicates that the following parts of ip can be anything. The function returns true or false based on if the example and ip is a match. I tried this kind of solution.
var compare = function(example, ip){
var ex = example.split(".");
var ip = ip.split(".");
var t = 0;
for(var i=0; i<4; i++){
if(ex[i] == ip[i] || ex[i] == "*" || typeof ex[i] === 'undefined' && ex[i-1] == "*"){
t++
if(t==4){
return true
}
}else{
return false;
}
}
}
What are the main advantages of using regular expression over this solution? What would be the best regular expression to do this?
How about checking if they are not equal then just return false?
var compare = function(example, ip){
// You should have some basic IP validations here for both example and ip.
var ex = example.split(".");
var ip = ip.split(".");
for(var i=0; i<ex.length; i++){
if(ex[i]=='*')
break;
if(ex[i]!=ip[i])
return false;
}
return true;
}
alert(compare('333.321.*','333.321.345.765'));
alert(compare('333.322.*','333.321.345.765'));
alert(compare('333.321.345.*','333.321.345.765'));
This goes way better with regular expressions. Try this:
function compare(example, ip) {
var regexp = new RegExp('^' + example.replace(/\./g, '\\.').replace(/\*/g, '.*'));
return regexp.test(ip);
}
compare('192.168.*', '192.168.0.42'); // => true
compare('192.167.*', '192.168.0.42'); // => false
What this does is, it translates your pattern to an regular expression. Regular expressions are extremely powerful in matching string. It also covers cases like this:
compare('192.168.*.42', '192.168.1.42'); // => true
compare('192.167.*.42', '192.168.1.43'); // => false

How to remove asterisk from this spin syntax code?

here is my code it is a text spinner (synonym)
public function fetchContent($keyword)
{
$customContent = $this->getOption('custom_content_text');
$this->_setHttpStatusCode(200);
if (!$customContent)
{
$this->_setContentStatus(self::CONTENT_STATUS_NO_RESULTS);
return false;
}
if (preg_match_all('/({\*)(.*?)(\*})/', $customContent, $result))
{
if (is_array($result[0]))
{
foreach ($result[0] as $index => $group_string)
{
//replace the first or next pattern match with a replaceable token
$customContent = preg_replace('/(\{\*)(.*?)(\*\})/', '{#'.$index.'#}', $customContent, 1);
$words = explode('|', $result[2][$index]);
//clean and trim all words
$finalPhrase = array();
foreach ($words as $word)
{
if (preg_match('/\S/', $word))
{
$word = preg_replace('/{%keyword%}/i', $keyword, $word);
$finalPhrase[] = trim($word);
}
}
$finalPhrase = $finalPhrase[rand(0, count($finalPhrase) - 1)];
//now inject it back to where the token was
$customContent = str_ireplace('{#' . $index . '#}', $finalPhrase, $customContent);
}
$this->_setContentStatus(self::CONTENT_STATUS_PASSED);
}
}
return $customContent;
}
}
there is regex that request bracket like this
{*spin1|spin2|spin3*}
here is the regex from the snippet above
if (preg_match_all('/({\*)(.*?)(\*})/', $customContent, $result))
$customContent = preg_replace('/(\{\*)(.*?)(\*\})/', '{#'.$index.'#}', $customContent, 1);
i would like to remove the * to format allow just {spin1|spin2|spin3} wich is more compatible with most spinner ,
i tried with some regex that i find online
i tried to remove the * from both regex without result
thanks you very much for your help
Remove \* instead of just * – Lucas Trzesniewski

Hide product description

How can I hide product description when the description is long in Opencart (product page) to reduce the load product page, but after clicking on the detail link then came out a full description.
In image you can see Example, Sorry for my bad english, Thanks!
Here is a link to example image example
Why not just truncate it? It will force it to be the right length for you every time!
Go to catalog/controller/product/category.php and when you see
foreach ($results as $result) {
if ($result['image']) {
$image = $this->model_tool_image->resize($result['image'], $this->config->get('config_image_product_width'), $this->config->get('config_image_product_height'));
} else {
$image = false;
}
Add this next:
function truncate($description, $tLimit="20", $break=" ", $pad="...")
{
if(strlen($string) <= $tlimit) return $string;
if(false !== ($breakpoint = strpos($string, $break, $tlimit))) {
if($breakpoint < strlen($string) - 1) {
$string = substr($string, 0, $breakpoint) . $pad;
}
}
return $description;
}
Feel free to change the variables:
$tLimit is how many letters you want to allow it.
$break is where you want it to cut off, right now it is set to cut off at the next space. You can have it interrupt words if you like by putting $break=""
$pad is what you want it to show after it cuts off the text.
If you really want no description to show at all Then I recommend still doing something similar to the original script.
function getDescriptionLength($description, $tLimit="20")
{
if(strlen($string) <= $tlimit) return $string;
else {
$description = NULL;
}
return $description;
}