I'm trying to write a program that will grab a bunch of images from a webpage and figure out which of the images is the largest.
So far I've taken the images, put them into an array, used the getimagesize() function to determine the heights. I then placed all of the heights into another array and sorted them in reverse order to get the largest one. So far so good.
My problem now is that I must find a way to re-associate the largest image with its initial image link. I've thought of potentially running the initial code to fetch the images again from the web page. Then comparing the 1st value in the array I've used to determine the biggest image with the images a second time, but this seems like a waste of bandwidth and I get a feeling there is an easier way to re-associate the height value with its initial image. Am I right?
<?php
$url = 'http://lockerz.com/s/104049300';
// Fetch page
$string = FetchPage($url);
// Regex that extracts the images (full tag)
$image_regex_src_url = '/<img[^>]*'.
'src=[\"|\'](.*)[\"|\']/Ui';
preg_match_all($image_regex_src_url, $string, $out, PREG_PATTERN_ORDER);
$img_tag_array = $out[0];
echo "<pre>"; print_r($img_tag_array); echo "</pre>";
// Regex for SRC Value
$image_regex_src_url = '/<img[^>]*'.
'src=[\"|\'](.*)[\"|\']/Ui';
preg_match_all($image_regex_src_url, $string, $out, PREG_PATTERN_ORDER);
$images_url_array = $out[1];
$image_heights_array = array();
foreach ($images_url_array as $imagelink)
{
if (substr($imagelink,0,7)=="http://")
{
$getheight = getimagesize($imagelink);
array_push($image_heights_array,"$getheight[1]");
}
}
rsort($image_heights_array);
echo "<pre>"; print_r($image_heights_array); echo "</pre>";
// Fetch Page Function
function FetchPage($path)
{
$file = fopen($path, "r");
if (!$file)
{
exit("The was a connection error!");
}
$data = '';
while (!feof($file))
{
// Extract the data from the file / url
$data .= fgets($file, 1024);
}
return $data;
}
?>
First off:
// Regex that extracts the images (full tag)
$image_regex_src_url = '/<img[^>]*'.
'src=[\"|\'](.*)[\"|\']/Ui';
preg_match_all($image_regex_src_url, $string, $out, PREG_PATTERN_ORDER);
$img_tag_array = $out[0];
echo "<pre>"; print_r($img_tag_array); echo "</pre>";
// Regex for SRC Value
$image_regex_src_url = '/<img[^>]*'.
'src=[\"|\'](.*)[\"|\']/Ui';
preg_match_all($image_regex_src_url, $string, $out, PREG_PATTERN_ORDER);
HTML parsing regex ewww. Let's simplify this... with an HTML parser.
<?php
// EDIT: Use a custom function to do the
// reverse of SORT_NUMERIC with asort
function height_compare($a, $b)
{
if ($a == $b) {
return 0;
}
return ($a > $b) ? -1 : 1;
}
$url = 'http://lockerz.com/s/104049300';
$doc = new DOMDocument();
// Okay this is HTML is kind of screwy
// So we're going to supress errors
#$doc->loadHTMLFile($url);
// Get all images
$images_list = $doc->getElementsByTagName('img');
$images = array();
foreach($images_list as $image) {
// Get the src attribute
$image_source = $image->getAttribute('src');
$image_size_info = getimagesize($image_source);
$images[$image_source] = $image_size_info[1];
}
// Do a numeric sort on the height
uasort($images, "height_compare");
print_r($images);
?>
Much shorter and more readable. The result:
$ php test.php
Array
(
[http://c0013784.cdn1.cloudfiles.rackspacecloud.com/x2_633aa94] => 328
[http://ad.doubleclick.net/ad/buz.plixi/photos;pos=300a;celeb=;kw=;tile=2;sz=300x250,300x600;ord=123456789?] => 250
[http://static.lockerz.com/pegasus/images/video_thumb.jpg?1.0.0] => 207
[http://static.lockerz.com/pegasus/images/plixi-banner.png?1.0.0] => 107
[http://ad.doubleclick.net/ad/buz.plixi/photos;pos=728a;celeb=;kw=;tile=1;sz=728x90;ord=123456789?] => 90
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636f30c] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_637676e] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_63735a0] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636e73c] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_63795d0] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636a2c7] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636bf79] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636ca08] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636e419] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_636deca] => 79
[http://c0013787.cdn1.cloudfiles.rackspacecloud.com/x2_6384277] => 79
Keep sizes and URLs in one array and use usort to sort multidimensional array.
$images_url_array = $out[1];
$images = array();
foreach ($images_url_array as $imagelink)
{
if (substr($imagelink, 0, 7)=="http://")
{
$getheight = getimagesize($imagelink);
$images[] = array('height' => $getheight[1], 'url' => $imagelink);
}
}
usort($images, function ($a, $b)
{
if ($a['height'] > $b['height']) return 1;
elseif ($a['height'] < $b['height']) return -1;
else return 0;
});
I don't know PHP so I can't help you with the code, but maybe you could try this idea. Have the first array store an ImageObject. This class to have three properties, imagesize, id, and src.
You can go through the first array, find the imagesize and then sort it by biggest size. Use the id to get individual objects.
if you use arsort() the array keys will be preserved, then you can reset() the array pointer to start of the array, and get the first key().
But you will also need to preserve the same keys across the images_url_array and image_height_array
foreach ($images_url_array as $key => $imagelink)
{
if (substr($imagelink,0,7)=="http://")
{
$getheight = getimagesize($imagelink);
$image_heights_array[$key] = $getheight[1];
}
}
//...
rsort($image_heights_array);
reset($image_heights_array);
$largest_image_key = key($image_heights_array);
$largest_image_url = $images_url_array[$key];
Related
i have a string and i need to add some html tag at certain index of the string.
$comment_text = 'neethu and Dilnaz Patel check this'
Array ( [start_index_key] => 0 [string_length] => 6 )
Array ( [start_index_key] => 11 [string_length] => 12 )
i need to split at start index key with long mentioned in string_length
expected final output is
$formattedText = '<span>#neethu</span> and <span>#Dilnaz Patel</span> check this'
what should i do?
This is a very strict method that will break at the first change.
Do you have control over the creation of the string? If so, you can create a string with placeholders and fill the values.
Even though you can do this with regex:
$pattern = '/(.+[^ ])\s+and (.+[^ ])\s+check this/i';
$string = 'neehu and Dilnaz Patel check this';
$replace = preg_replace($pattern, '<b>#$\1</b> and <b>#$\2</b> check this', $string);
But this is still a very rigid solution.
If you can try creating a string with placeholders for the names. this will be much easier to manage and change in the future.
<?php
function my_replace($string,$array_break)
{
$break_open = array();
$break_close = array();
$start = 0;
foreach($array_break as $key => $val)
{
// for tag <span>
if($key % 2 == 0)
{
$start = $val;
$break_open[] = $val;
}
else
{
// for tag </span>
$break_close[] = $start + $val;
}
}
$result = array();
for($i=0;$i<strlen($string);$i++)
{
$current_char = $string[$i];
if(in_array($i,$break_open))
{
$result[] = "<span>".$current_char;
}
else if(in_array($i,$break_close))
{
$result[] = $current_char."</span>";
}
else
{
$result[] = $current_char;
}
}
return implode("",$result);
}
$comment_text = 'neethu and Dilnaz Patel check this';
$my_result = my_replace($comment_text,array(0,6,11,12));
var_dump($my_result);
Explaination:
Create array parameter with: The even index (0,2,4,6,8,...) would be start_index_key and The odd index (1,3,5,7,9,...) would be string_length
read every break point , and store it in $break_open and $break_close
create array $result for result.
Loop your string, add , add or dont add spann with break_point
Result:
string '<span>neethu </span>and <span>Dilnaz Patel </span> check this' (length=61)
I am trying to convert CSV file to JSON using PHP.
Here is my code
<?php
date_default_timezone_set('UTC');
$today = date("n_j"); // Today is 1/23/2015 -> $today = 1_23
$file_name = $today.'.CSV'; // My file name is 1_23.csv
$file_path = 'C:\\Users\\bheng\\Desktop\\qb\\'.$file_name;
$file_handle = fopen($file_path, "r");
$result = array();
if ($file_handle !== FALSE) {
$column_headers = fgetcsv($file_handle);
foreach($column_headers as $header) {
$result[$header] = array();
}
while (($data = fgetcsv($file_handle)) !== FALSE) {
$i = 0;
foreach($result as &$column) {
$column[] = $data[$i++];
}
}
fclose($file_handle);
}
// print_r($result); // I see all data(s) except the header
$json = json_encode($result);
echo $json;
?>
print_r($result); // I see all data(s)
Then I json_encode($result); and tried to display it, but nothing is displaying on the screen at all. All I see is the blank screen, and 0 error message.
Am I doing anything wrong ? Can someone help me ?
Added Result of print_r($result);
Array (
[Inventory] => Array (
[0] => bs-0468R(20ug)
[1] => bs-1338R(1ml)
[2] => bs-1557G(no bsa)
[3] => bs-3295R(no BSA)
[4] => bs-0730R-Cy5"
[5] => bs-3889R-PE-Cy7"
[6] => 11033R
[7] => 1554R-A647
[8] => 4667
[9] => ABIN731018
[10] => Anti-DBNL protein
.... more ....
Try like this:
$file="1_23.csv";
$csv= file_get_contents($file);
$array = array_map("str_getcsv", explode("\n", $csv));
$json = json_encode($array);
print_r($json);
data.csv
Game,Skill
Treasure Hunter,pilipala
Rocket Launcher,bibobibo
Rocket Engine,hehehohoho
To convert with column name, this is how I do it.
csv2json.php
<?php
if (($handle = fopen("data.csv", "r")) !== FALSE) {
$csvs = [];
while(! feof($handle)) {
$csvs[] = fgetcsv($handle);
}
$datas = [];
$column_names = [];
foreach ($csvs[0] as $single_csv) {
$column_names[] = $single_csv;
}
foreach ($csvs as $key => $csv) {
if ($key === 0) {
continue;
}
foreach ($column_names as $column_key => $column_name) {
$datas[$key-1][$column_name] = $csv[$column_key];
}
}
$json = json_encode($datas);
fclose($handle);
print_r($json);
}
The output result
[
{
"Game": "Treasure Hunter",
"Skill": "pilipala"
},
{
"Game": "Rocket Launcher",
"Skill": "bibobibo"
},
{
"Game": "Rocket Engine",
"Skill": "hehehohoho"
}
]
You can try this way too.
<?php
function csvtojson($file,$delimiter)
{
if (($handle = fopen($file, "r")) === false)
{
die("can't open the file.");
}
$csv_headers = fgetcsv($handle, 4000, $delimiter);
$csv_json = array();
while ($row = fgetcsv($handle, 4000, $delimiter))
{
$csv_json[] = array_combine($csv_headers, $row);
}
fclose($handle);
return json_encode($csv_json);
}
$jsonresult = csvtojson("./doc.csv", ",");
echo $jsonresult;
I ran into a similar problem, I ended up using this to recursively convert the data to UTF-8 on an array before encoding to JSON.
function utf8_converter($array)
{
array_walk_recursive($array, function(&$item, $key){
if(!mb_detect_encoding($item, 'utf-8', true)){
$item = utf8_encode($item);
}
});
return $array;
}
From:
http://nazcalabs.com/blog/convert-php-array-to-utf8-recursively/
This issue is pretty old by now, but hoping this helps someone, as it seemed like the simplest example I found, and I know this is a pretty common thing devs might need to do as a beginner, and lots of answers gloss over the magic.
$file = storage_path('app/public/waitlist_users_test.csv'); //--> laravel helper, but you can use any path here
function csv_to_json($file)
{
// file() loads each row as an array value, then array map uses the 'str_getcsv' callback to
$csv = array_map('str_getcsv', file($file));
// array_walk - "walks" through each item of the array and applies the call back function. the & in "&row" means that alterations to $row actually change the original $csv array, rather than treating it as immutable (*sort of immutable...)
array_walk($csv, function(&$row) use ($csv) {
// array_combine takes the header row ($csv[0]) and uses it as array keys for each column in the row
$row = array_combine($csv[0], $row);
});
array_shift($csv); # removes now very redundant column header --> contains {'col_1':'col_1', 'col_2':'col_2'...}
$json = json_encode($csv);
return $json;
}
There's a lot of magic going on with these functions that accept callback functions, that didn't seem to be explained thoroughly above. I'm self taught and have been programming for years, and find that it's often just glossed over without detailing how callbacks work, so I'll dive in just a little bit for the array_map('str_getcsv', file($file)) function - if you pass a function you've written, or inbuilt php function name as a string, it will take the value of whatever (in this case - array) element is being evaluated by the calling function (in this case array_map), and pass that to the callback function without the need to explicitly pass in a variable - super helpful once you get the hang of it, but I find it's not explained thoroughly very often which leaves beginners to not understand why it works, just that it works.
I've linked most of these above, but here's a little more information:
str-getcsv do? Array Walk Array Map Callables/Callbacks
as #MoonCactus noted, the file() function only loads 1 row at a time which helps save on memory usage for large .csv files.
Also, some other posts reference using explode - why not use explode() instead of str_getcsv() to parse rows? Because explode() would not treat possible enclosured parts of string or escaped characters correctly.
Hope somebody finds this helpful!
If you are converting a dynamic CSV file, you can pass the URL through a parameter (url=http://example.com/some.csv) and it will show you the most up-to-date version:
<?php
// Lets the browser and tools such as Postman know it's JSON
header( "Content-Type: application/json" );
// Get CSV source through the 'url' parameter
if ( isset( $_GET['url'] ) ) {
$csv = explode( "\n", file_get_contents( $_GET['url'] ) );
$index = str_getcsv( array_shift( $csv ) );
$json = array_map(
function ( $e ) use ( $index ) {
return array_combine( $index, str_getcsv( $e ) );
}, $csv
);
}
else {
$json = "Please set the path to your CSV by using the '?url=' query string.";
}
// Output JSON
echo json_encode( $json );
Alternate solution that uses similar method as #Whirlwind's solution but returns a more standard JSON result (with named fields for each object/record):
// takes a string of CSV data and returns a JSON representing an array of objects (one object per row)
function convert_csv_to_json($csv_data){
$flat_array = array_map("str_getcsv", explode("\n", $csv_data));
// take the first array item to use for the final object's property labels
$columns = $flat_array[0];
for ($i=1; $i<count($flat_array)-1; $i++){
foreach ($columns as $column_index => $column){
$obj[$i]->$column = $flat_array[$i][$column_index];
}
}
$json = json_encode($obj);
return $json; // or just return $obj if that's a more useful return value
}
The accepted answer uses file_get_contents() to read the entire file as a string in memory, and then explode() it to make it an array.
But it can be made faster, smaller in memory, and more useful:
function ReadCsv($fn)
{
$lines= file($fn); // read file directly as an array of lines
array_pop($lines); // you can remove the last empty line (if required)
$json= json_encode(array_map("str_getcsv", $lines), JSON_NUMERIC_CHECK);
print_r($json);
}
Nb: I used JSON_NUMERIC_CHECK here to avoid numbers being double quoted into strings. It also reduces the output size and it usually helps javascript on the other side (e.g. to compute or plot the data). Beware of phone numbers though!
I liked #ian-d-miller's solution for converting the data into a key / value style format, but I kept running into issues with his code.
Here's what worked for me:
function convert_CSV_to_JSON($csv_data){
// convert csv data to an array
$data = array_map("str_getcsv", explode("\n", $csv_data));
// use the first row as column headers
$columns = $data[0];
// create array to hold our converted data
$json = [];
// iterate through each row in the data
foreach ($data as $row_index => $row_data) {
// skip the first row, since it's the headers
if($row_index === 0) continue;
// make sure we establish each new row as an array
$json[$row_index] = [];
// iterate through each column in the row
foreach ($row_data as $column_index => $column_value) {
// get the key for each entry
$label = $columns[$column_index];
// add this column's value to this row's index / column's key
$json[$row_index][$label] = $column_value;
}
}
// bam
return $json;
}
Usage:
// as is
$json = convert_CSV_to_JSON($csv);
// encoded
$json = json_encode($json);
Something that i've made for myself and may be useful for others :)
This will convert CSV into JSON array with objects (key => value pair).
function csv2json($a, $e = true) {
$b = ["\r\n","\r","\n",];
foreach ($b as $c => $d) {
$a = explode($d, $a);
$a = isset($b[$c + 1]) ? implode($b[$c + 1], $a) : implode(PHP_EOL, $a);
}
// Convert to CSV
$a = array_map("str_getcsv", explode(PHP_EOL, $a));
// Get the first part of the array as the keys
$a = [
"keys" => array_shift($a),
"rows" => $a,
"row" => null,
];
// Define JSON
$b = [];
foreach ($a["rows"] as $a["row"]) {
$a["row"] = [ "csv" => $a["row"], "json" => (object)[], ];
for ($c = 0; $c < count($a["row"]["csv"]); $c++) {
$a["row"]["csv"][$c] = [#json_decode($a["row"]["csv"][$c]),$a["row"]["csv"][$c]];
// Switch from string to booleans, numbers and others
$a["row"]["csv"][$c] = isset($a["row"]["csv"][$c][0]) ? $a["row"]["csv"][$c][0] : $a["row"]["csv"][$c][1];
// Push it back
$a["row"]["json"]->{$a["keys"][$c]} = $a["row"]["csv"][$c];
}
$a["row"] = $a["row"]["json"];
$b[] = $a["row"];
unset($a["row"]);
}
// $e will be "return"
$e = $e ? json_encode($b) : $b;
// Unset useless variables
unset($a, $b, $c, $d);
return $e;
}
How to use?
If you want to return the JSON as a string, Leave it as default.
If you want to return the JSON as an object / array, set the second parameter to false.
Examples:
$csv = "name,age,gender
John Doe,35,male
Jane Doe,32,female";
echo csv2json($csv, true); // Or without the second parameter, just csv2json($csv)
The example above (^) will return a JSON stringified, Like this:
[{"name":"John Doe","age":35,"gender":"male"},{"name":"Jane Doe","age":32,"gender":"female"}]
and the example below:
var_dump(csv2json($csv, false));
will return a JSON array with these objects:
array(2) {
[0]=>
object(stdClass)#1 (3) {
["name"]=>
string(8) "John Doe"
["age"]=>
int(35)
["gender"]=>
string(4) "male"
}
[1]=>
object(stdClass)#2 (3) {
["name"]=>
string(8) "Jane Doe"
["age"]=>
int(32)
["gender"]=>
string(6) "female"
}
}
public function CsvToJson($fileContent){
//Convert CSV To Json and Return
$all_rows = array();
$newhead =array();
//Extract csv data to array on \n
$array = explode("\n",$fileContent);
//Extract csv header to array on 0 Index
$header = explode(",",$array[0]);
//Remove Header Row From Main Data Array
array_shift($array);
//Extract All Arrays To Saperate Orders
foreach($array as $arr){
$sliced = explode(",",$arr);
array_push($all_rows,$sliced);
}
//Extract All Orders Element To Saperate Array Item
foreach($all_rows as $row){
$sliced = explode(",",$arr);
array_push($all_rows,$sliced);
}
//Remove \r From Header Elements
foreach($header as $key=>$value){
$sliced = str_replace ("\r", "", $value);
array_push($newhead,$sliced);
}
//COMBINE Header as KEY And Row Element As Value
$arrrr = array();
foreach($all_rows as $row) {
//Remove Last Element of ROW if it is \r (Break given in css file for next row)
$count= count($row);
if ($row[$count-1] == "\r") {
array_splice($row, count($row) - 1, 1);
}
//CHECK IF HADER COUNT == ROW COUNT
if (count($header) == count($row)) {
array_push($arrrr,array_combine($newhead,$row));
}
}
//CONVERT ARRAY TO JSON
$json = json_encode($arrrr);
//Remove backslasesh from json key and and value to remove \r
$clean = stripslashes($json);
//CONVERT ARRAY TO JSON AGAIN FOR EXPORT
$jsonagain = json_encode($clean);
return $jsonagain;
}
After migrating some content from WordPress to Drupal, I've got som shortcodes that I need to convert:
String content:
Irrelevant tekst...
[sublimevideo class="sublime"
poster="http://video.host.com/_previews/600x450/sbx-60025-00-da-ANA.png"
src1="http://video.host.com/_video/H.264/LO/sbx-60025-00-da-ANA.m4v"
src2="(hd)http://video.host.com/_video/H.264/HI/sbx-60025-00-da-ANA.m4v"
width="560" height="315"]
..more irrelevant text.
I need to find all variables within the shortcode [sublimevideo ...] and turn it into an array:
Array (
class => "sublime"
poster => "http://video.host.com/_previews/600x450/sbx-60025-00-da-FMT.png"
src1 => "http://video.host.com/_video/H.264/LO/sbx-60025-00-da-FMT.m4v"
src2 => "(hd)http://video.host.com/_video/H.264/HI/sbx-60025-00-da-FMT.m4v"
width => "560"
height => "315"
)
And preferably handle multiple instances of the shortcode.
I guess it can be done with preg_match_all() but I've had no luck.
This will give you what you want.
$data = 'Irrelevant tekst... [sublimevideo class="sublime" poster="http://video.host.com/_previews/600x450/sbx-60025-00-da-ANA.png" src1="http://video.host.com/_video/H.264/LO/sbx-60025-00-da-ANA.m4v" src2="(hd)http://video.host.com/_video/H.264/HI/sbx-60025-00-da-ANA.m4v" width="560" height="315"] ..more irrelevant text.';
$dat = array();
preg_match("/\[sublimevideo (.+?)\]/", $data, $dat);
$dat = array_pop($dat);
$dat= explode(" ", $dat);
$params = array();
foreach ($dat as $d){
list($opt, $val) = explode("=", $d);
$params[$opt] = trim($val, '"');
}
print_r($params);
In anticipation of the next challenge you will face with processing short codes you can use preg_replace_callback to replace the short tag data with it's resultant markup.
$data = 'Irrelevant tekst... [sublimevideo class="sublime" poster="http://video.host.com/_previews/600x450/sbx-60025-00-da-ANA.png" src1="http://video.host.com/_video/H.264/LO/sbx-60025-00-da-ANA.m4v" src2="(hd)http://video.host.com/_video/H.264/HI/sbx-60025-00-da-ANA.m4v" width="560" height="315"] ..more irrelevant text.';
function processShortCode($matches){
// parse out the arguments
$dat= explode(" ", $matches[2]);
$params = array();
foreach ($dat as $d){
list($opt, $val) = explode("=", $d);
$params[$opt] = trim($val, '"');
}
switch($matches[1]){
case "sublimevideo":
// here is where you would want to return the resultant markup from the shorttag call.
return print_r($params, true);
}
}
$data = preg_replace_callback("/\[(\w+) (.+?)]/", "processShortCode", $data);
echo $data;
You could use the following RegEx to match the variables:
$regex = '/(\w+)\s*=\s*"(.*?)"/';
I would suggest to first match the sublimevideo shortcode and get that into a string with the following RegEx:
$pattern = '/\[sublimevideo(.*?)\]/';
To get the correct array keys I used this code:
// $string is string content you specified
preg_match_all($regex, $string, $matches);
$sublimevideo = array();
for ($i = 0; $i < count($matches[1]); $i++)
$sublimevideo[$matches[1][$i]] = $matches[2][$i];
This returns the following array: (the one that you've requested)
Array
(
[class] => sublime
[poster] => http://video.host.com/_previews/600x450/sbx-60025-00-da-ANA.png
[src1] => http://video.host.com/_video/H.264/LO/sbx-60025-00-da-ANA.m4v
[src2] => (hd)http://video.host.com/_video/H.264/HI/sbx-60025-00-da-ANA.m4v
[width] => 560
[height] => 315
)
This is my interpretation, I come from a WordPress background and tried to recreate the setup for a custom php project.
It'll handle things like [PHONE] [PHONE abc="123"] etc
The only thing it falls flat on is the WordPress style [HERE] to [HERE]
Function to build a list of available shortcodes
// Setup the default global variable
function create_shortcode($tag, $function)
{
global $shortcodes;
$shortcodes[$tag] = $function;
}
define shortcodes individually, e.g. [IFRAME url="https://www.bbc.co.uk"]:
/**
* iframe, allows the user to add an iframe to a page with responsive div wrapper
*/
create_shortcode('IFRAME', function($atts) {
// ... some validation goes here
// The parameters that can be set in the shortcode
if (empty($atts['url'])) {
return false;
}
return '
<div class="embed-responsive embed-responsive-4by3">
<iframe class="embed-responsive-item" src="' . $atts['url'] . '">
</iframe>
</div>';
});
Then when you want to pass a block of html via the shortcode handling do... handle_shortcodes($some_html_with_shortcodes);
function handle_shortcodes($content)
{
global $shortcodes;
// Loop through all shortcodes
foreach($shortcodes as $key => $function){
$matches = [];
// Look for shortcodes, returns an array of ALL matches
preg_match_all("/\[$key([^_^\]].+?)?\]/", $content, $matches, PREG_UNMATCHED_AS_NULL);
if (!empty($matches))
{
$i = 0;
$full_shortcode = $matches[0];
$attributes = $matches[1];
if (!empty($attributes))
{
foreach($attributes as $attribute_string) {
// Decode the values (e.g. " to ")
$attribute_string = htmlspecialchars_decode($attribute_string);
// Find all the query args, looking for `arg="anything"`
preg_match_all('/\w+\=\"(.[^"]+)\"/', $attribute_string, $query_args);
$params = [];
foreach ($query_args[0] as $d) {
// Split the
list($att, $val) = explode('=', $d, 2);
$params[$att] = trim($val, '"');
}
$content = str_replace($full_shortcode[$i], $function($params), $content);
$i++;
}
}
}
}
return $content;
}
I've plucked these examples from working code so hopefully it's readable and doesn't have any extra functions exclusive to our setup.
As described in this answer, I'd suggest letting WordPress do the work for you using the get_shortcode_regex() function.
$pattern = get_shortcode_regex();
preg_match_all("/$pattern/",$wp_content,$matches);
This will give you an array that is easy to work with and shows the various shortcodes and affiliated attributes in your content. It isn't the most obvious array format, so print it and take a look so you know how to manipulate the data you need.
I have a set of items with numbers already assigned to them and am trying to fill in the gaps that the person before me left in the spreadsheet. I figured I could write a php script to do this for me yet it's placing the assigned numbers in weird spots.
Here's an example:
I have an associative array of numbers / names
[0] => 3502 "Scallops, Bay" [1] => 3503 "Oysters, Chesepeake" [2] => 3504 "Clams, Cherry Stone"
The script to order these is:
$d = file("list.txt");
$j=0;
for ($i=2000;$i<8000;$i++) { //I want the codes to begin at the 2000 and end at 8000
if (strpos($d[$j], $i) !== false) {
echo $d[$j]."<br/>";
$j++;
} else {
echo $i."<br/>";
}
}
But here's what I'm getting:
2000-2056 print out fine, because they don't match [0] of $d, but then on 2057 it prints
2056
3502 "Scallops, Bay"
3503 "Oysters, Chesepeake"
2059
2060
3504 "Clams, Chery Stone"
Then goes to print on until 2080 where it prints [3] of $d.
I'm really confused. I don't see 2057 anywhere in "3502 'Scallops, Bay'"
Should I be trying a different approach?
The second argument to strpos() can be an integer or a string; if it's an integer, it's ordinal value is used to search. From the manual:
If needle is not a string, it is converted to an integer and applied as the ordinal value of a character.
You should cast the index to a string first:
if (strpos($d[$j], "$i") !== false) {
Btw, it would be better to check whether the line starts with $i and whether $d[$j] is still a valid entry:
if (isset($d[$j]) && strpos($d[$j], "$i\t") === 0) {
It's because of the order. If the script reache lets say 5000 with index of 1, it won't find 3000 with index 2.
My solution:
$A = array('3000 abc street', '2000 something', '5000 somthing other');
function ScanFor($Number, &$A) //& is realy important
{
foreach($A as $I => $V)
if(strpos($Number, $V) === 0) // so it starts with it
{
unset($A[$I]); //We don't want it anymore
list(, $Name) = explode(' ', $V, 1); //After Number there is always space, so we split it to 2 parts
return $Name;
}
return '';
}
for($I = 2000; $I < 10000; $I++)
{
printf("%d", $I);
if($Name = ScanFor($I, $A))
{
printf("\t%s", $Name)
}
printf("<br>\n");
}
Try working with the file like csv and use SplMinHeap to sort
Example:
// Where to store Result
$store = new SortedValue();
// Read File
$fp = fopen("list.txt", "r");
// Read each file content
while(($data = fgetcsv($fp, 1000, " ")) !== FALSE) {
// Filter Empty space
$data = array_filter($data);
// Your Filter
if ($data[0] > 2000 && $data[0] < 8000) {
$store->insert($data);
}
}
// Print_r all result
print_r(iterator_to_array($store));
Class Used
class SortedValue extends SplMinHeap {
function compare($a, $b) {
return $b[0] - $a[0];
}
}
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.