So I have the following string:
{family:Open Sans,name:Open Sans,import_family:Open+Sans:300,300italic,regular,italic,600,600italic,700,700italic,800,800italic,classname:opensans}
I'd like to "vectorize" it, so maybe it would look something like this:
XX['family'] = "Open Sans',
XX['name'] = 'Open Sans',
XX['import_family'] = 'Open+Sans:300,300italic,regular,italic,600,600italic,700,700italic,800,800italic',
XX['classname'] = 'opensans';
Any ideas on how I could achieve this in PHP? It's getting on my nerves, been trying to work it out with regular expressions for the last couple of hours with no results so far.
Thanks in advance!
Here is a simple parser for this format you could use. It will handle all fields and values and return them as a key/value array. It assumes that the string starts and ends with curly braces and uses the field:optional:optional,a,b,c format.
<?php
header('Content-Type: text/plain');
function parse($str) {
$obj = [];
$str = substr($str, 1, -1);
$candidates = explode(',', $str);
$lastKey = null;
foreach ($candidates as $candidate) {
if (strpos($candidate, ':')) {
$parts = explode(':', $candidate);
$key = $parts[0];
$value = substr($candidate, strlen($key) + 1);
$obj[$key] = $value;
$lastKey = $key;
} else {
$obj[$lastKey] .= ',' . $candidate;
}
}
return $obj;
}
$example = '{family:Open Sans,name:Open Sans,import_family:Open+Sans:300,300italic,regular,italic,600,600italic,700,700italic,800,800italic,classname:opensans}';
print_r(parse($example));
?>
Output from the example string you specified:
Array
(
[family] => Open Sans
[name] => Open Sans
[import_family] => Open+Sans:300,300italic,regular,italic,600,600italic,700,700italic,800,800italic
[classname] => opensans
)
Try this:
$s = "{family:Open Sans,name:Open Sans,import_family:Open+Sans:300,300italic,regular,italic,600,600italic,700,700italic,800,800italic,classname:opensans}";
$s = rtrim(ltrim($s, '{'), '}');
preg_match_all('#([^:,]+):((?:(?!(,[^:,]+:)).)*)#', $s, $matches);
$vector = array_combine($matches[1], $matches[2]);
EDIT
As HamZa explains here, a shorter regular expression is:
([^:,]+):(.+?)(?=,[^,]+:|$)
Related
I have string like this
$string = 'title,id,user(name,email)';
and I want result to be like this
Array
(
[0] => title
[1] => id
[user] => Array
(
[0] => name
[1] => email
)
)
so far I tried with explode function and multiple for loop the code getting ugly and i think there must be better solution by using regular expression like preg_split.
Replace the comma with ### of nested dataset then explode by a comma. Then make an iteration on the array to split nested dataset to an array. Example:
$string = 'user(name,email),office(title),title,id';
$string = preg_replace_callback("|\(([a-z,]+)\)|i", function($s) {
return str_replace(",", "###", $s[0]);
}, $string);
$data = explode(',', $string);
$data = array_reduce($data, function($old, $new) {
preg_match('/(.+)\((.+)\)/', $new, $m);
if(isset($m[1], $m[2]))
{
return $old + [$m[1] => explode('###', $m[2])];
}
return array_merge($old , [$new]);
}, []);
print '<pre>';
print_r($data);
First thanks #janie for enlighten me, I've busied for while and since yesterday I've learnt a bit regular expression and try to modify #janie answer to suite with my need, here are my code.
$string = 'user(name,email),title,id,office(title),user(name,email),title';
$commaBetweenParentheses = "|,(?=[^\(]*\))|";
$string = preg_replace($commaBetweenParentheses, '###', $string);
$array = explode(',', $string);
$stringFollowedByParentheses = '|(.+)\((.+)\)|';
$final = array();
foreach ($array as $value) {
preg_match($stringFollowedByParentheses, $value, $result);
if(!empty($result))
{
$final[$result[1]] = explode('###', $result[2]);
}
if(empty($result) && !in_array($value, $final)){
$final[] = $value;
}
}
echo "<pre>";
print_r($final);
function extractConnect($str,$connect_type){
$connect_array = array();
$connect_counter = 0;
$str = trim($str).' ';
for($i =0; $i<strlen($str);$i++) {
$chr = $str[$i];
if($chr==$connect_type){ //$connect_type = '#' or '#' etc
$connectword = getConnect($i,$str);
$connect_array[$connect_counter] = $connectword;
$connect_counter++;
}
}
if(!empty($connect_array)){
return $connect_array;
}
}
function getConnect($tag_index,$str){
$str = trim($str).' ';
for($j = $tag_index; $j<strlen($str);$j++) {
$chr = $str[$j];
if($chr==' '){
$hashword = substr($str,$tag_index+1,$j-$tag_index);
return trim($hashword);
}
}
}
$at = extractConnect("#stackoverflow is great. #google.com is the best search engine","#");
$hash = extractConnect("#stackoverflow is great. #google.com is the best search engine","#");
print_r($at);
print_r($hash);
What this method does is it extracts # or # from a string and return an array of those words.
e.g input #stackoverflow is great. #google.com is the best search engine and outputs this
Array ( [0] => google.com ) Array ( [0] => stackoverflow )
But it seems like this method is to slow is there any alternative ?
You could use a regex to achieve this:
/<char>(\S+)\b/i
Explanation:
/ - starting delimiter
<char> - the character you're searching for (passed as a function argument)
(\S+) - any non-whitespace character, one or more times
\b - word boundary
i - case insensitivity modifier
/ - ending delimiter
Function:
function extractConnect($string, $char) {
$search = preg_quote($char, '/');
if (preg_match('/'.$search.'(\S+)\b/i', $string, $matches)) {
return [$matches[1]]; // Return an array containing the match
}
return false;
}
With your strings, this would produce the following output:
array(1) {
[0]=>
string(10) "google.com"
}
array(1) {
[0]=>
string(13) "stackoverflow"
}
Demo
You can do it like this:
<?php
function extractConnect($strSource, $tags) {
$matches = array();
$tags = str_split($tags);
$strSource = explode(' ', $strSource);
array_walk_recursive($strSource, function(&$item) {
$item = trim($item);
});
foreach ($strSource as $strPart) {
if (in_array($strPart[0], $tags)) {
$matches[$strPart[0]][] = substr($strPart, 1);
}
}
return $matches;
}
var_dump(extractConnect(
"#stackoverflow is great. #twitter is good. #google.com is the best search engine",
"##"
));
Outputs:
This seemed to work for me. Provide it with the symbol you want.
function get_stuff($str) {
$result = array();
$words = explode(' ', $str);
$symbols = array('#', '#');
foreach($words as $word) {
if (in_array($word[0], $symbols)) {
$result[$word[0]][] = substr($word, 1);
}
}
return $result;
}
$str = '#stackoverflow is great. #google.com is the best search engine';
print_r(get_stuff($str));
This outputs Array ( [#] => Array ( [0] => stackoverflow ) [#] => Array ( [0] => google.com ) )
I want to parse shortcode like Wordpress with attributes:
Input:
[include file="header.html"]
I need output as array, function name "include" and attributes with values as well , any help will be appreciated.
Thanks
Here's a utility class that we used on our project
It will match all shortcodes in a string (including html) and it will output an associative array including their name, attributes and content
final class Parser {
// Regex101 reference: https://regex101.com/r/pJ7lO1
const SHORTOCODE_REGEXP = "/(?P<shortcode>(?:(?:\\s?\\[))(?P<name>[\\w\\-]{3,})(?:\\s(?P<attrs>[\\w\\d,\\s=\\\"\\'\\-\\+\\#\\%\\!\\~\\`\\&\\.\\s\\:\\/\\?\\|]+))?(?:\\])(?:(?P<content>[\\w\\d\\,\\!\\#\\#\\$\\%\\^\\&\\*\\(\\\\)\\s\\=\\\"\\'\\-\\+\\&\\.\\s\\:\\/\\?\\|\\<\\>]+)(?:\\[\\/[\\w\\-\\_]+\\]))?)/u";
// Regex101 reference: https://regex101.com/r/sZ7wP0
const ATTRIBUTE_REGEXP = "/(?<name>\\S+)=[\"']?(?P<value>(?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?/u";
public static function parse_shortcodes($text) {
preg_match_all(self::SHORTOCODE_REGEXP, $text, $matches, PREG_SET_ORDER);
$shortcodes = array();
foreach ($matches as $i => $value) {
$shortcodes[$i]['shortcode'] = $value['shortcode'];
$shortcodes[$i]['name'] = $value['name'];
if (isset($value['attrs'])) {
$attrs = self::parse_attrs($value['attrs']);
$shortcodes[$i]['attrs'] = $attrs;
}
if (isset($value['content'])) {
$shortcodes[$i]['content'] = $value['content'];
}
}
return $shortcodes;
}
private static function parse_attrs($attrs) {
preg_match_all(self::ATTRIBUTE_REGEXP, $attrs, $matches, PREG_SET_ORDER);
$attributes = array();
foreach ($matches as $i => $value) {
$key = $value['name'];
$attributes[$i][$key] = $value['value'];
}
return $attributes;
}
}
print_r(Parser::parse_shortcodes('[include file="header.html"]'));
Output:
Array
(
[0] => Array
(
[shortcode] => [include file="header.html"]
[name] => include
[attrs] => Array
(
[0] => Array
(
[file] => header.html
)
)
)
)
Using this function
$code = '[include file="header.html"]';
$innerCode = GetBetween($code, '[', ']');
$innerCodeParts = explode(' ', $innerCode);
$command = $innerCodeParts[0];
$attributeAndValue = $innerCodeParts[1];
$attributeParts = explode('=', $attributeAndValue);
$attribute = $attributeParts[0];
$attributeValue = str_replace('"', '', $attributeParts[1]);
echo $command . ' ' . $attribute . '=' . $attributeValue;
//this will result in include file=header.html
$command will be "include"
$attribute will be "file"
$attributeValue will be "header.html"
I also needed this functionality in my PHP framework. This is what I've written, it works pretty well. It works with anonymous functions, which I really like (it's a bit like the callback functions in JavaScript).
<?php
//The content which should be parsed
$content = '<p>Hello, my name is John an my age is [calc-age day="4" month="10" year="1991"].</p>';
$content .= '<p>Hello, my name is Carol an my age is [calc-age day="26" month="11" year="1996"].</p>';
//The array with all the shortcode handlers. This is just a regular associative array with anonymous functions as values. A very cool new feature in PHP, just like callbacks in JavaScript or delegates in C#.
$shortcodes = array(
"calc-age" => function($data){
$content = "";
//Calculate the age
if(isset($data["day"], $data["month"], $data["year"])){
$age = date("Y") - $data["year"];
if(date("m") < $data["month"]){
$age--;
}
if(date("m") == $data["month"] && date("d") < $data["day"]){
$age--;
}
$content = $age;
}
return $content;
}
);
//http://stackoverflow.com/questions/18196159/regex-extract-variables-from-shortcode
function handleShortcodes($content, $shortcodes){
//Loop through all shortcodes
foreach($shortcodes as $key => $function){
$dat = array();
preg_match_all("/\[".$key." (.+?)\]/", $content, $dat);
if(count($dat) > 0 && $dat[0] != array() && isset($dat[1])){
$i = 0;
$actual_string = $dat[0];
foreach($dat[1] as $temp){
$temp = explode(" ", $temp);
$params = array();
foreach ($temp as $d){
list($opt, $val) = explode("=", $d);
$params[$opt] = trim($val, '"');
}
$content = str_replace($actual_string[$i], $function($params), $content);
$i++;
}
}
}
return $content;
}
echo handleShortcodes($content, $shortcodes);
?>
The result:
Hello, my name is John an my age is 22.
Hello, my name is Carol an my age is 17.
This is actually tougher than it might appear on the surface. Andrew's answer works, but begins to break down if square brackets appear in the source text [like this, for example]. WordPress works by pre-registering a list of valid shortcodes, and only acting on text inside brackets if it matches one of these predefined values. That way it doesn't mangle any regular text that might just happen to have a set of square brackets in it.
The actual source code of the WordPress shortcode engine is fairly robust, and it doesn't look like it would be all that tough to modify the file to run by itself -- then you could use that in your application to handle the tough work. (If you're interested, take a look at get_shortcode_regex() in that file to see just how hairy the proper solution to this problem can actually get.)
A very rough implementation of your question using the WP shortcodes.php would look something like:
// Define the shortcode
function inlude_shortcode_func($attrs) {
$data = shortcode_atts(array(
'file' => 'default'
), $attrs);
return "Including File: {$data['file']}";
}
add_shortcode('include', 'inlude_shortcode_func');
// And then run your page content through the filter
echo do_shortcode('This is a document with [include file="header.html"] included!');
Again, not tested at all, but it's not a very hard API to use.
I have modified above function with wordpress function
function extractThis($short_code_string) {
$shortocode_regexp = "/(?P<shortcode>(?:(?:\\s?\\[))(?P<name>[\\w\\-]{3,})(?:\\s(?P<attrs>[\\w\\d,\\s=\\\"\\'\\-\\+\\#\\%\\!\\~\\`\\&\\.\\s\\:\\/\\?\\|]+))?(?:\\])(?:(?P<content>[\\w\\d\\,\\!\\#\\#\\$\\%\\^\\&\\*\\(\\\\)\\s\\=\\\"\\'\\-\\+\\&\\.\\s\\:\\/\\?\\|\\<\\>]+)(?:\\[\\/[\\w\\-\\_]+\\]))?)/u";
preg_match_all($shortocode_regexp, $short_code_string, $matches, PREG_SET_ORDER);
$shortcodes = array();
foreach ($matches as $i => $value) {
$shortcodes[$i]['shortcode'] = $value['shortcode'];
$shortcodes[$i]['name'] = $value['name'];
if (isset($value['attrs'])) {
$attrs = shortcode_parse_atts($value['attrs']);
$shortcodes[$i]['attrs'] = $attrs;
}
if (isset($value['content'])) {
$shortcodes[$i]['content'] = $value['content'];
}
}
return $shortcodes;
}
I think this one help for all :)
Updating the #Duco's snippet, As it seems like, it's exploding by spaces which ruins when we have some like
[Image source="myimage.jpg" alt="My Image"]
To current one:
function handleShortcodes($content, $shortcodes){
function read_attr($attr) {
$atList = [];
if (preg_match_all('/\s*(?:([a-z0-9-]+)\s*=\s*"([^"]*)")|(?:\s+([a-z0-9-]+)(?=\s*|>|\s+[a..z0-9]+))/i', $attr, $m)) {
for ($i = 0; $i < count($m[0]); $i++) {
if ($m[3][$i])
$atList[$m[3][$i]] = null;
else
$atList[$m[1][$i]] = $m[2][$i];
}
}
return $atList;
}
//Loop through all shortcodes
foreach($shortcodes as $key => $function){
$dat = array();
preg_match_all("/\[".$key."(.*?)\]/", $content, $dat);
if(count($dat) > 0 && $dat[0] != array() && isset($dat[1])){
$i = 0;
$actual_string = $dat[0];
foreach($dat[1] as $temp){
$params = read_attr($temp);
$content = str_replace($actual_string[$i], $function($params), $content);
$i++;
}
}
}
return $content;
}
$content = '[image source="one" alt="one two"]';
Result:
array(
[source] => myimage.jpg,
[alt] => My Image
)
Updated (Feb 11, 2020)
It appears to be following regex under preg_match only identifies shortcode with attributes
preg_match_all("/\[".$key." (.+?)\]/", $content, $dat);
to make it work with as normal [contact-form] or [mynotes]. We can change the following to
preg_match_all("/\[".$key."(.*?)\]/", $content, $dat);
I just had the same problem. For what I have to do, I am going to take advantage of existing xml parsers instead of writing my own regex. I am sure there are cases where it won't work
example.php
<?php
$file_content = '[include file="header.html"]';
// convert the string into xml
$xml = str_replace("[", "<", str_replace("]", "/>", $file_content));
$doc = new SimpleXMLElement($xml);
echo "name: " . $doc->getName() . "\n";
foreach($doc->attributes() as $key => $value) {
echo "$key: $value\n";
}
$ php example.php
name: include
file: header.html
to make it work on ubuntu I think you have to do this
sudo apt-get install php-xml
(thanks https://drupal.stackexchange.com/a/218271)
If you have lots of these strings in a file, then I think you can still do the find replace, and then just treat it all like xml.
I have a string that contains elements from array.
$str = '[some][string]';
$array = array();
How can I get the value of $array['some']['string'] using $str?
This will work for any number of keys:
$keys = explode('][', substr($str, 1, -1));
$value = $array;
foreach($keys as $key)
$value = $value[$key];
echo $value
You can do so by using eval, don't know if your comfortable with it:
$array['some']['string'] = 'test';
$str = '[some][string]';
$code = sprintf('return $array%s;', str_replace(array('[',']'), array('[\'', '\']'), $str));
$value = eval($code);
echo $value; # test
However eval is not always the right tool because well, it shows most often that you have a design flaw when you need to use it.
Another example if you need to write access to the array item, you can do the following:
$array['some']['string'] = 'test';
$str = '[some][string]';
$path = explode('][', substr($str, 1, -1));
$value = &$array;
foreach($path as $segment)
{
$value = &$value[$segment];
}
echo $value;
$value = 'changed';
print_r($array);
This is actually the same principle as in Eric's answer but referencing the variable.
// trim the start and end brackets
$str = trim($str, '[]');
// explode the keys into an array
$keys = explode('][', $str);
// reference the array using the stored keys
$value = $array[$keys[0][$keys[1]];
I think regexp should do the trick better:
$array['some']['string'] = 'test';
$str = '[some][string]';
if (preg_match('/\[(?<key1>\w+)\]\[(?<key2>\w+)\]/', $str, $keys))
{
if (isset($array[$keys['key1']][$keys['key2']]))
echo $array[$keys['key1']][$keys['key2']]; // do what you need
}
But I would think twice before dealing with arrays your way :D
I've got a multidimensional associative array which includes an elements like
$data["status"]
$data["response"]["url"]
$data["entry"]["0"]["text"]
I've got a strings like:
$string = 'data["status"]';
$string = 'data["response"]["url"]';
$string = 'data["entry"]["0"]["text"]';
How can I convert the strings into a variable to access the proper array element? This method will need to work across any array at any of the dimensions.
PHP's variable variables will help you out here. You can use them by prefixing the variable with another dollar sign:
$foo = "Hello, world!";
$bar = "foo";
echo $$bar; // outputs "Hello, world!"
Quick and dirty:
echo eval('return $'. $string . ';');
Of course the input string would need to be be sanitized first.
If you don't like quick and dirty... then this will work too and it doesn't require eval which makes even me cringe.
It does, however, make assumptions about the string format:
<?php
$data['response'] = array(
'url' => 'http://www.testing.com'
);
function extract_data($string) {
global $data;
$found_matches = preg_match_all('/\[\"([a-z]+)\"\]/', $string, $matches);
if (!$found_matches) {
return null;
}
$current_data = $data;
foreach ($matches[1] as $name) {
if (key_exists($name, $current_data)) {
$current_data = $current_data[$name];
} else {
return null;
}
}
return $current_data;
}
echo extract_data('data["response"]["url"]');
?>
This can be done in a much simpler way. All you have to do is think about what function PHP provides that creates variables.
$string = 'myvariable';
extract(array($string => $string));
echo $myvariable;
done!
You can also use curly braces (complex variable notation) to do some tricks:
$h = 'Happy';
$n = 'New';
$y = 'Year';
$wish = ${$h.$n.$y};
echo $wish;
Found this on the Variable variables page:
function VariableArray($data, $string) {
preg_match_all('/\[([^\]]*)\]/', $string, $arr_matches, PREG_PATTERN_ORDER);
$return = $arr;
foreach($arr_matches[1] as $dimension) { $return = $return[$dimension]; }
return $return;
}
I was struggling with that as well,
I had this :
$user = array('a'=>'alber', 'b'=>'brad'...);
$array_name = 'user';
and I was wondering how to get into albert.
at first I tried
$value_for_a = $$array_name['a']; // this dosen't work
then
eval('return $'.$array_name['a'].';'); // this dosen't work, maybe the hoster block eval which is very common
then finally I tried the stupid thing:
$array_temp=$$array_name;
$value_for_a = $array_temp['a'];
and this just worked Perfect!
wisdom, do it simple do it stupid.
I hope this answers your question
You would access them like:
print $$string;
You can pass by reference with the operator &. So in your example you'll have something like this
$string = &$data["status"];
$string = &$data["response"]["url"];
$string = &$data["entry"]["0"]["text"];
Otherwise you need to do something like this:
$titular = array();
for ($r = 1; $r < $rooms + 1; $r ++)
{
$title = "titular_title_$r";
$firstName = "titular_firstName_$r";
$lastName = "titular_lastName_$r";
$phone = "titular_phone_$r";
$email = "titular_email_$r";
$bedType = "bedType_$r";
$smoker = "smoker_$r";
$titular[] = array(
"title" => $$title,
"first_name" => $$firstName,
"last_name" => $$lastName,
"phone" => $$phone,
"email" => $$email,
"bedType" => $$bedType,
"smoker" => $$smoker
);
}
There are native PHP function for this:
use http://php.net/manual/ru/function.parse-str.php (parse_str()).
don't forget to clean up the string from '"' before parsing.
Perhaps this option is also suitable:
$data["entry"]["0"]["text"];
$string = 'data["entry"]["0"]["text"]';
function getIn($arr, $params)
{
if(!is_array($arr)) {
return null;
}
if (array_key_exists($params[0], $arr) && count($params) > 1) {
$bf = $params[0];
array_shift($params);
return getIn($arr[$bf], $params);
} elseif (array_key_exists($params[0], $arr) && count($params) == 1) {
return $arr[$params[0]];
} else {
return null;
}
}
preg_match_all('/(?:(\w{1,}|\d))/', $string, $arr_matches, PREG_PATTERN_ORDER);
array_shift($arr_matches[0]);
print_r(getIn($data, $arr_matches[0]));
P.s. it's work for me.