I'd like to use tag-style annotations in html text to replace sections of text/html depending on variable names using PHP. The replacement itself works perfectly if not using nested tags.
But if there are nested tags, only the outer one gets replaced.
My regex is this one:
\[\#if(not)?:([a-zA-Z0-9]+)(?:=(.*?))?\].*?\[\#endif:\2\]
You can see this regex in action with example content to parse here:
https://regex101.com/r/rE3fL1/2
I've read about (?R) but can't get it to work.
I tried replacing the .*? in the middle with (.*?|(?R)) but that doesn't even change anything.
How do I change the regex to also capture nested Tags?
Code: ($this->output accesses the Text)
public function output($dbAccess = true) {
// only translate when dbaccess is granted
if ($dbAccess)
$this->localize();
// insert values into template
foreach ( $this->values as $key => $value ) {
$tagToReplace = "[#$key]";
$this->output = str_replace ( $tagToReplace, $value, $this->output );
}
// gather conditional content sections from output
$condis = array();
$conmatches = array ();
preg_match_all ( '/\[\#if(not)?:([a-zA-Z0-9]+)(?:=(.*?))?\].*?\[\#endif:\2\]/s', $this->output, $conmatches );
if (count($conmatches) > 0) {
$c = $conmatches[0];
// if (count($c) > 0)
// echo "found " . count($c[0]) . " conditional tpl statement matches!";
for ($i=0; $i<count($c); $i++) {
$text = $c[$i];
$not = $conmatches[1][$i];
$name = $conmatches[2][$i];
$value = $conmatches[3][$i];
$condis[] = new ConditionalContent($text, $not, $name, $value);
}
// substitute conditional content sections
foreach ($condis as $cc) {
// convenience and readability vars!
$varname = $cc->name();
$vals = &$this->values;
$value = $cc->value();
// if condition is bound to value of variable and not just existence
if ($value != "") {
// del if name == exists(value)
if ($cc->not() && isset($vals[$varname])) {
if ($vals[$varname] == $value) {
$this->delContent($cc->content());
}
}
// del if not exists(value) or value != name
else {
if (!isset($vals[$varname]) || $vals[$varname] != $value) {
$this->delContent($cc->content());
}
}
}
else {
if ( isset($vals[$varname]) && $cc->not() ||
!isset($vals[$varname]) && !$cc->not()) {
$this->delContent($cc->content());
}
}
}
// delete all left over if(not) and endif statements
$this->output = preg_replace('/\[#(?:if(?:not){0,1}|endif):[a-zA-Z0-9]+(=.*?)?\]/', '', $this->output);
}
//else { echo "found no conditional tpl statements"; }
return $this->output;
}
Related
I want to make a method that returns keys and values. But only if the keys include the following string "_1" and "__last".
If only one matches then exit the function, only if the two string are included in the key, return the key with the value for a weather.
$infoList = array("_key_1"=>array("time"=>9, "day"=>"Tuesday", "weather"=>"sunny",
"humidity"=>"80%"),
"_key_2"=>array("time"=>5, "day"=>"Tuesday", "weather"=>"cloudy"),
"_key__last"=>array("time"=>3, "day"=>"Sunday", "weather"=>"rainy"))
public function getData() {
$list = array();
foreach($infoList as $key){
if(preg_match('/(_key)_(_1)/', $key) && preg_match('/(_key)_(__last)/', $key) == TRUE){
$list[$key] = $list[$key]["weather"]
}
}
return $list
}
You are making your life so much more difficult that it need be, use str_contains() its easier than building complex REGEX's and getting very confused by the look of it :)
I also fixed a number of other mistakes, such as the foreach that was not going to work, so check all the code.
It is also better to pass data to a function/method otherwise you get into scoping issues!
$infoList = array("_key_1"=>array("time"=>9, "day"=>"Tuesday", "weather"=>"sunny", "humidity"=>"80%"),
"_key_2"=>array("time"=>5, "day"=>"Tuesday", "weather"=>"cloudy"),
"_key__last"=>array("time"=>3, "day"=>"Sunday", "weather"=>"rainy"));
function getData(Array $infoList) {
$list = [];
$found = 0;
foreach($infoList as $key => $val) {
if( str_contains($key, '_1') || str_contains($key, '__last') ) {
$list[$key] = $val["weather"];
$found++;
}
}
if ( $found >= 2 ) {
return $list;
} else {
return false;
}
}
$res = getData($infoList);
if ( $res !== false ){
print_r($res);
} else {
echo 'Not Found';
}
RESULTS
Array
(
[_key_1] => sunny
[_key__last] => rainy
)
If you want to stick with RegEx, you can use positive lookaheads, the same way you check for passwords characters :
<?php
$pattern = '/^(?=.*_1)(?=.*_last).*$/';
$shouldMatch = [
'_1_last',
'foo_1bar_lasthello',
'_last_1',
'foo_lastbar_1hello'
];
echo 'next ones should match : ' . PHP_EOL;
foreach ($shouldMatch as $item)
{
if (preg_match($pattern, $item))
echo $item . PHP_EOL;
}
$shouldNOTMatch = [
'_2_first',
'bar_lasthello',
'foo_las_1hello'
];
echo 'next ones should NOT match : ' . PHP_EOL;
foreach ($shouldNOTMatch as $item)
{
// v------------ check
if (!preg_match($pattern, $item))
echo $item . PHP_EOL;
}
Output :
next ones should match :
_1_last
foo_1bar_lasthello
_last_1
foo_lastbar_1hello
next ones should NOT match :
_2_first
bar_lasthello
foo_las_1hello
We have got web app which does replacing some text with another using str_replace().
Find strings and replace strings are stored in template file.
We what to replace str_replace() function to preg_replace() to have possibility to use regex in find strings (to set them in the same template file).
In original scripts we have such parts of php code.
In one file:
class SiteConfig {
// Strings to search for in HTML before processing begins (used with $replace_string)
public $find_string = array();
// Strings to replace those found in $find_string before HTML processing begins
public $replace_string = array();
// a lot of code goes here
public function append(SiteConfig $newconfig) {
foreach (array('find_string', 'replace_string') as $var) {
// append array elements for this config variable from $newconfig to this config
//$this->$var = $this->$var + $newconfig->$var;
$this->$var = array_merge($this->$var, $newconfig->$var);
}
}
// a lot of code goes here
public static function build_from_array(array $lines) {
$config = new SiteConfig();
foreach ($lines as $line) {
$line = trim($line);
// skip comments, empty lines
if ($line == '' || $line[0] == '#') continue;
// get command
$command = explode(':', $line, 2);
// if there's no colon ':', skip this line
if (count($command) != 2) continue;
$val = trim($command[1]);
$command = trim($command[0]);
//if ($command == '' || $val == '') continue;
// $val can be empty, e.g. replace_string:
if ($command == '') continue;
// strip_attr is now an alias for strip.
// In FTR 3.8 we can strip attributes from elements, not only the elements themselves
// e.g. strip: //img/#srcset (removes srcset attribute from all img elements)
// but for backward compatibility (to avoid errors with new config files + old version of FTR)
// we've introduced strip_attr and we'll recommend using that in our public site config rep.
// strip_attr: //img/#srcset
if ($command == 'strip_attr') $command = 'strip';
// check for commands where we accept multiple statements
if (in_array($command, array('title', 'body', 'author', 'date', 'strip', 'strip_id_or_class', 'strip_image_src', 'single_page_link', 'single_page_link_in_feed', 'next_page_link', 'native_ad_clue', 'http_header', 'test_url', 'find_string', 'replace_string'))) {
array_push($config->$command, $val);
// check for single statement commands that evaluate to true or false
} elseif (in_array($command, array('tidy', 'prune', 'autodetect_on_failure', 'insert_detected_image'))) {
$config->$command = ($val == 'yes');
// check for single statement commands stored as strings
} elseif (in_array($command, array('parser'))) {
$config->$command = $val;
// special treatment for test_contains
} elseif (in_array($command, array('test_contains'))) {
$config->add_test_contains($val);
// special treatment for if_page_contains
} elseif (in_array($command, array('if_page_contains'))) {
$config->add_if_page_contains_condition($val);
// check for replace_string(find): replace
} elseif ((substr($command, -1) == ')') && preg_match('!^([a-z0-9_]+)\((.*?)\)$!i', $command, $match)) {
if (in_array($match[1], array('replace_string'))) {
array_push($config->find_string, $match[2]);
array_push($config->replace_string, $val);
} elseif (in_array($match[1], array('http_header'))) {
$_header = strtolower(trim($match[2]));
$config->http_header[$_header] = $val;
}
}
}
return $config;
}
}
In another file:
public function process($html, $url, $smart_tidy=true) {
// a lot of code goes before
// do string replacements
if (!empty($this->config->find_string)) {
if (count($this->config->find_string) == count($this->config->replace_string)) {
$html = str_replace($this->config->find_string, $this->config->replace_string, $html, $_count);
$this->debug("Strings replaced: $_count (find_string and/or replace_string)");
} else {
$this->debug('Skipped string replacement - incorrect number of find-replace strings in site config');
}
unset($_count);
}
// a lot of code goes after
}
I tried to replace str_replace() with preg_replace(), but while testing it shows an error:
Warning: preg_replace(): No ending matching delimiter '>' found in this line:
$html = preg_replace($this->config->find_string, $this->config->replace_string, $html, $_count);
Where is the error and how to replace str_replace() function to preg_replace() correctly?
I'm very very beginning in php, so any help is badly needed.
Big thanks in advance!
Rewrite your process function like this:
public function process($html, $url, $smart_tidy=true) {
// a lot of code goes before
// do string replacements
if (!empty($this->config->find_string)) {
if (count($this->config->find_string) == count($this->config->replace_string)) {
$new_config_find_string = array_map(function($new_pattern)
{
return '/'.preg_quote($new_pattern).'/';
},$this->config->find_string);
$html = preg_replace($new_config_find_string, $this->config->replace_string, $html, $_count);
$this->debug("Strings replaced: $_count (find_string and/or replace_string)");
} else {
$this->debug('Skipped string replacement - incorrect number of find-replace strings in site config');
}
unset($_count);
}
// a lot of code goes after
}
//let my controller be C and method be:
function X($array){}
//And my url to call it is:
localhost/mysite/C/X/array
Well i tried it but it returns 400-Bad Request response.
Does anyone know how to do it? Quick response will help me a lot//Thanx
localhost/mysite/C/X/?array=1&&?array=2....
$array = $this->input->get('array');
or
localhost/mysite/C/X/1,2,3,4,5
$array = explode(',' $this->uri->segment(n));
// in app/config/config.php
$config['permitted_uri_chars'] = 'a-z 0-9~%.:,_-';
My variant for array in url (/tours/novogodniye-turi/visa=yes;duration=small;transport=4,7,6,2/)
if ( ! function_exists('filter_parse_segment'))
{
function filter_parse_segment($segment, $merge_to_get = FALSE)
{
if(empty($segment))
{
return FALSE;
}
$parameters = explode(";", (string)$segment);
if(empty($parameters))
{
return FALSE;
}
$parameters_array = array();
foreach($parameters as $parameter)
{
if(empty($parameter))
{
continue;
}
$parameter = explode("=", $parameter);
if( ! isset($parameter[0], $parameter[1]) or empty($parameter[0]))
{
continue;
}
if(strpos($parameter[1], ','))
{
$parameter[1] = explode(",", $parameter[1]);
}
$parameters_array[$parameter[0]] = $parameter[1];
}
if($merge_to_get === TRUE)
{
$_GET = array_merge($_GET, $parameters_array);
}
return $parameters_array;
}
}
// --------------------------------------------------------------------
if ( ! function_exists('filter_collect_segment'))
{
function filter_collect_segment($array, $suffix = '', $remove = array())
{
if(empty($array) || ! is_array($array))
{
return '';
}
$segment_str = '';
foreach ($array as $key => $value)
{
if(empty($key) || in_array($key, (array)$remove))
{
continue;
}
if( ! $segment_str == '')
{
$segment_str = $segment_str.';';
}
if( ! is_array($value))
{
$segment_str = $segment_str.$key.'='.$value;
continue;
}
if(empty($value))
{
continue;
}
$parsed_value = '';
foreach ($value as $item)
{
if( ! $parsed_value == '')
{
$parsed_value = $parsed_value.',';
}
if(is_array($item) || empty($item))
{
continue;
}
$parsed_value = $parsed_value.$item;
}
$segment_str = $segment_str.$key.'='.$parsed_value;
}
if($segment_str != '')
{
$segment_str = $segment_str.$suffix;
}
return $segment_str;
}
}
// --------------------------------------------------------------------
if ( ! function_exists('filter_key'))
{
function filter_key($filter_array, $key, $value = NULL)
{
if( ! isset($filter_array[$key]))
{
return;
}
if($value == NULL)
{
return $filter_array[$key];
}
if( ! is_array($filter_array[$key]) && $filter_array[$key] == (string)$value)
{
return $value;
}
if(is_array($filter_array[$key]) && in_array($value, $filter_array[$key]))
{
return $value;
}
return FALSE;
}
}
If you want the solution in the pretty nice URL then you have to loop the array first and then concatenate the elements with some - dash or + plus signs like this;
$array = array(1,2,3,4);
$string = "";
foreach($array as $value){
$string .= $value."-";
}
$string = rtrim($string, "-");
redirect(base_url()."get_products?param=".$string);
And on the next page just get the param and use explode() function with - dash sign to create the array again.
try your url like this
if value you have to pass is
[1,2,3,2]
then
localhost/mysite/index.php/C/X/1,2,3,2
In a simple way you can make the array a string with some special character in between the values of the array.
Then in the landing page you can split the string with the special character and get the array again.
If the values are [1,2,3,4], then make it using a loop "1,2,3,4".
Then pass the string.
In teh landing page split the string with "," and you will again get the array.
Hope it helps you.
Thanks
Why don't you use uri segments for array? You can count uri segments and could use them. Even u could check how many arrays already there.
url.com/1/2/3
http://ellislab.com/codeigniter/user-guide/libraries/uri.html
Note: uri segment doesnt work on main controller index function, you have to define another function than index for example: I don't know what it happens but i think because of htaccess file I'm using do remove index.php.
url.com/uri_segment_controller/go/1/2/3
I have a simple data format that goes as follows:
stuff/stuff/stuff
An example would be:
data/test/hello/hello2
In order to retrieve a certain piece of data, one would use my parser, which tries to do the following:
In data/test/hello/hello2
You want to retrieve the data under data/test (which is hello). My parser's code is below:
function getData($data, $pattern)
{
$info = false;
$dataLineArray = explode("\n", $data);
foreach($dataLineArray as &$line)
{
if (strpos($line,$pattern) !== false) {
$lineArray = explode("/", $line);
$patternArray = explode("/", $pattern);
$iteration = 0;
foreach($lineArray as &$lineData)
{
if($patternArray[$iteration] == $lineData)
{
$iteration++;
}
else
{
$info = $lineData;
}
}
}
}
return $info;
}
However, it always seems to return the last item, which in this case is hello2:
echo getData("data/test/hello/hello2", "data/test");
Gives Me;
hello2
What am I doing wrong?
If you want the first element after the pattern, put break in the loop:
foreach($lineArray as $lineData)
{
if($patternArray[$iteration] == $lineData)
{
$iteration++;
}
elseif ($iteration == count($patternArray))
{
$info = $lineData;
break;
}
}
I also check $iteration == count($patternArray) so that it won't return intermediate elements, e.g.
/data/foo/test/hello/hello2
will return hello rather than foo.
P.S. There doesn't seem to be any reason to use references instead of ordinary variables in your loops, since you never assign to the reference variables.
I have the following in an INI file:
[country]
SE = Sweden
NO = Norway
FI = Finland
However, when var_dump()ing PHP's parse_ini_file() function, I get the following output:
PHP Warning: syntax error, unexpected BOOL_FALSE in test.ini on line 2
in /Users/andrew/sandbox/test.php on line 1
bool(false)
It appears that "NO" is reserved. Is there any other way I can set a variable named "NO"?
Another hack would be to reverse your ini keys with their values and use array_flip:
<?php
$ini =
"
[country]
Sweden = 'SE'
Norway = 'NO'
Finland = 'FI'
";
$countries = parse_ini_string($ini, true);
$countries = array_flip($countries["country"]);
echo $countries["NO"];
Still you will need to use quotes around NO (at least), if you do
Norway = NO
you don't get an error but value for $countries["NO"] will be an empty string.
This propably comes a little late but the way PHPs parse_ini_file works bothered me so much that I wrote my own little parser.
Feel free to use it, but use with care it has only been shallowly tested!
// the exception used by the parser
class IniParserException extends \Exception {
public function __construct($message, $code = 0, \Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
// the parser
function my_parse_ini_file($filename, $processSections = false) {
$initext = file_get_contents($filename);
$ret = [];
$section = null;
$lineNum = 0;
$lines = explode("\n", str_replace("\r\n", "\n", $initext));
foreach($lines as $line) {
++$lineNum;
$line = trim(preg_replace('/[;#].*/', '', $line));
if(strlen($line) === 0) {
continue;
}
if($processSections && $line{0} === '[' && $line{strlen($line)-1} === ']') {
// section header
$section = trim(substr($line, 1, -1));
} else {
$eqIndex = strpos($line, '=');
if($eqIndex !== false) {
$key = trim(substr($line, 0, $eqIndex));
$matches = [];
preg_match('/(?<name>\w+)(?<index>\[\w*\])?/', $key, $matches);
if(!array_key_exists('name', $matches)) {
throw new IniParserException("Variable name must not be empty! In file \"$filename\" in line $lineNum.");
}
$keyName = $matches['name'];
if(array_key_exists('index', $matches)) {
$isArray = true;
$arrayIndex = trim($matches['index']);
if(strlen($arrayIndex) == 0) {
$arrayIndex = null;
}
} else {
$isArray = false;
$arrayIndex = null;
}
$value = trim(substr($line, $eqIndex+1));
if($value{0} === '"' && $value{strlen($value)-1} === '"') {
// too lazy to check for multiple closing " let's assume it's fine
$value = str_replace('\\"', '"', substr($value, 1, -1));
} else {
// special value
switch(strtolower($value)) {
case 'yes':
case 'true':
case 'on':
$value = true;
break;
case 'no':
case 'false':
case 'off':
$value = false;
break;
case 'null':
case 'none':
$value = null;
break;
default:
if(is_numeric($value)) {
$value = $value + 0; // make it an int/float
} else {
throw new IniParserException("\"$value\" is not a valid value! In file \"$filename\" in line $lineNum.");
}
}
}
if($section !== null) {
if($isArray) {
if(!array_key_exists($keyName, $ret[$section])) {
$ret[$section][$keyName] = [];
}
if($arrayIndex === null) {
$ret[$section][$keyName][] = $value;
} else {
$ret[$section][$keyName][$arrayIndex] = $value;
}
} else {
$ret[$section][$keyName] = $value;
}
} else {
if($isArray) {
if(!array_key_exists($keyName, $ret)) {
$ret[$keyName] = [];
}
if($arrayIndex === null) {
$ret[$keyName][] = $value;
} else {
$ret[$keyName][$arrayIndex] = $value;
}
} else {
$ret[$keyName] = $value;
}
}
}
}
}
return $ret;
}
What does it differently? Variable names may only consist of alphanumerical characters but other than that no restrictions to them. Strings must be encapsulated with " everything else has to be a special value like no, yes, true, false, on, off, null or none. For mapping see code.
Kind of a hack but you can add backticks around the key names:
[country]
`SE` = Sweden
`NO` = Norway
`FI` = Finland
Then access them like so:
$result = parse_ini_file('test.ini');
echo "{$result['`NO`']}\n";
Output:
$ php test.php
Norway
I was getting this error when there were single quote combinations in the string such as 't or 's. To get rid of the problem, I wrapped the string in double quotes:
Before:
You have selected 'Yes' but you haven't entered the date's flexibility
After:
"You have selected 'Yes' but you haven't entered the date's flexibility"
I ran into the same problem and tried to escape the name in every possible way.
Then I remembered that because of the INI syntax both names and values will be trimmed, so the following workaround MAYBE should do the trick:
NL = Netherlands
; A whitespace before the name
NO = Norway
PL = Poland
And it works ;) As long as your co-workers read the comments (which is not always the case) and don't delete it accidentally. So, yes, the array flipping solution is a safe bet.
From the manual page for parse_ini_file:
There are reserved words which must not be used as keys for ini files. These include: null, yes, no, true, false, on, off, none.
So no, you can't set a variable NO.