Format CSV into json to be consumed by ui.autocomplete - php

I have php problem with formatting output from a CSV to make it available as json for jquery ui.autocomplete.
fruit.csv:
apple, bananna, jackfruit,
... etc
jquery from here:
http://jqueryui.com/demos/autocomplete/#default
$( "#fruits" ).autocomplete({
source: '/path/to/fruit.json'
});
PHP to convert CSV into json:
// Callback function to output CSV as json object
function _custom_json_from_csv() {
$fruit_path = '/path/to/fruit.csv';
$fruits = array_map("str_getcsv", file($fruit_path));
drupal_json_output(array(array_values($fruits)));
exit;
}
// Below are CMS codes for detailed illustration
function drupal_json_output($var = NULL) {
// We are returning JSON, so tell the browser.
drupal_add_http_header('Content-Type', 'application/json');
if (isset($var)) {
echo drupal_json_encode($var);
}
}
function drupal_json_encode($var) {
// The PHP version cannot change within a request.
static $php530;
if (!isset($php530)) {
$php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
}
if ($php530) {
// Encode <, >, ', &, and " using the json_encode() options parameter.
return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
}
// json_encode() escapes <, >, ', &, and " using its options parameter, but
// does not support this parameter prior to PHP 5.3.0. Use a helper instead.
include_once DRUPAL_ROOT . '/includes/json-encode.inc';
return drupal_json_encode_helper($var);
}
// parts of json-encode.inc - drupal_json_encode_helper(), responsible for json output:
case 'array':
// Arrays in JSON can't be associative. If the array is empty or if it
// has sequential whole number keys starting with 0, it's not associative
// so we can go ahead and convert it as an array.
if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
$output = array();
foreach ($var as $v) {
$output[] = drupal_json_encode_helper($v);
}
return '[ ' . implode(', ', $output) . ' ]';
}
// Otherwise, fall through to convert the array as an object.
case 'object':
$output = array();
foreach ($var as $k => $v) {
$output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v);
}
return '{' . implode(', ', $output) . '}';
If there is a solution to directly consume CSV by jquery, that will be great. But no clue by now.
My problem is function _custom_json_from_csv() outputs non-expected format for ui.autocomplete. Note excessive of [[[...]]]:
[[["apple", "bananna", "jackfruit"]]]
While ui.autocomplete wants:
["apple", "bananna", "jackfruit"]
Any direction to format the function as expected by jquery ui.autocomplete?
PS: I don't use #autocomplete_path form API, and instead using ui.autocomplete, for reasons:
1) the code is stored in a theme settings, no hook_menu is available by theme, I want to avoid a module for this need whenever possible.
2) There is a plan somewhere at d.o. to use ui.autocomplete, so consider this adventurous
3) My previous question from jquery viewpoint has led me to instead correct the output of json, rather than making jquery adapt to json.
4) This is more my php issue rather than drupal
Thanks
UPDATE:
Removing one array from drupal_json_output(array(array_values($fruits))); to drupal_json_output(array_values($fruits)); successfully reduced one [] (what is the name of this?). Obviously a miss from previous format with the leading group.
[["apple", "bananna", "jackfruit"]]
I need to remove one more []

I think your code is "correct", if you had:
apple, bananna, jackfruit
peanut, walnut, almont
carrot, potato, pea
then your function would end up as ... etc
[[apple, bananna, jackfruit],[peanut, walnut, almont],[carrot, potato, pea]]
Which appears sensible.
If you only want one row, why can't you just use the result of
$FileContents = file($fruit_path);
$fruits = str_getcsv($FileContents[0]);
as that will turn an array of values in the first row, not an array of arrays of all the rows

Maybe simple is using in js array[0][0]( for drupal_json_output(array(array_values($fruits)));
in php) or array[0] in js( for drupal_json_output(array_values($fruits));) in php

Related

PHP Testing JSON Contains with SQLite

My Web App uses Laravel & MySQL but for my tests I am using SQLite in memory.
This is my code that I use in my Controller:
$opportunities = DB::table('opportunities')
->whereRaw('JSON_CONTAINS(businesses, \'"' . $business . '"\')')
->get();
When testing this throws an exception because of how SQLite doesn't have a JSON_CONTAINS function. How can I get around this so that my tests pass and I don't have to make any massive changes to the structure? Does SQLite have a function for this or something along these lines?
Thanks
You could emulate JSON_CONTAINS during testing using sqlite_create_function e.g.
function json_contains($json, $val) {
$array = json_decode($json, true);
// trim double quotes from around the value to match MySQL behaviour
$val = trim($val, '"');
// this will work for a single dimension JSON value, if more dimensions
// something more sophisticated will be required
// that is left as an exercise for the reader
return in_array($val, $array);
}
sqlite_create_function(<your db handle>, 'JSON_CONTAINS', 'json_contains');
You may also want to emulate the optional third parameter of JSON_CONTAINS e.g.
function json_contains($json, $val, $path = null) {
$array = json_decode($json, true);
// trim double quotes from around the value to match MySQL behaviour
$val = trim($val, '"');
// this will work for a single dimension JSON value, if more dimensions
// something more sophisticated will be required
// that is left as an exercise for the reader
if ($path)
return $array[$path] == $val;
else
return in_array($val, $array);
}
A bit more explanation for the accepted answer:
/**
* Call this method in your test.
* #PHP 7.4+
*/
private function setupSqlite(): void
{
DB::connection()->getPdo()->sqliteCreateFunction('JSON_CONTAINS', function ($json, $val, $path = null) {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// trim double quotes from around the value to match MySQL behaviour
$val = trim($val, '"');
// this will work for a single dimension JSON value, if more dimensions
// something more sophisticated will be required
// that is left as an exercise for the reader
if ($path) {
return $array[$path] == $val;
}
return in_array($val, $array, true);
});
Then you can use this query, that works for both mysql/mariadb and sqlite:
$result = Product::whereRaw('JSON_CONTAINS(tags, \'"' . $tag . '"\')')->get();
In above example I was searching json column with simple array:
$column = ["example", "test", "simple"];
I gave up on running my test suite on SQLite because there are just too many differences between the implementations. The first issue was it's lack of support for enums, but when even simple things like ALTER statements were causing problems I changed to MySQL for testing too.
What I do now is specify the name of my testing db in phpunit.xml.
<phpunit>
<php>
<env name="DB_DATABASE" value="mydb_testing"/>
</php>
</phpunit>
The down side of this is being required to create two databases to develop the app, but I need to be sure that the migrations will work on production, which uses MySQL and testing on SQLite doesn't prove that.

Formatting JSON formatted text file in PHP

So I got a HTML page with a button. When I click the button, a separate javascript file sends a GET request to my PHP file, expecting a JSON object in return. My PHP reads a JSON formatted text file and should convert it into a JSONObject and echo it out for my javascipt. I had some code working before, but it doesn't seem to do it anymore since I changed to a Ajax aproach instead of having everything in the same file. This is my code:
readLog.php
<?php
class test{
function clean($string){
return json_decode(rtrim(trim($string),','),true);
}
function getLog(){
header('Content-Type: application/json');
$logLines = file('../../../home/shares/flower_hum/humid.log');
$entries = array_map("clean",$logLines);
$finalOutput = ['log' => $entries];
echo json_encode($logLines);
}
}
?>
My humid.log file looks like this:
{"date":"26/09/2016", "time":"22:40:46","temp":"16.0", "humidity":"71.0" }
{"date":"26/09/2016", "time":"23:10:47","temp":"16.0", "humidity":"71.0" }
Now If I press my button, this is the response I get checking the console in my web browser:
Response:
["{\"date\":\"26\/09\/2016\", \"time\":\"22:40:46\",\"temp\":\"16.0\", \"humidity\":\"71.0\" }{\"date\":\"26\/09\/2016\", \"time\":\"23:10:47\",\"temp\":\"16.0\", \"humidity\":\"71.0\" }\n"]
JSON:
"{"date":"26/09/2016", "time":"22:40:46","temp":"16.0", "humidity":"71.0" }{"date":"26/09/2016", "time":"23:10:47","temp":"16.0", "humidity":"71.0" }\n"
obviously something is wrong with the formatting, but I don't know what. As I said, this code worked just fine when I had my php and HTML in the same file.
EDIT:
I have also tried formatting the JSON with something like this, but it just prints the brackets:
function getLog(){
$text = file('../../../home/shares/flower_hum/humid.log');
$textRemoved ="["; //Add opening bracket.
$textRemoved .= substr($text, 0, strlen($text)-1); (Remove last comma)
$textRemoved .="]";//Add closing bracket
$json = json_encode($textRemoved);
echo $json;
}
So I managed to solve it myself. Basicly The formatting of the textfile was wrong and as some commentors said, I don't need to encode it if I am doing it myself. What I ended up doing was in my application that generates the log file to add comma after each row. Then in my PHP I added brackets and removed the last comma.
function getLog(){
header('Content-Type: application/json');
$file = file_get_contents('../../../home/shares/flower_hum/humid.log');
$lengthOfFile = strlen($file)-2;
$subFile = substr($file, 0, $lengthOfFile);
$res ="[";
$res .= $subFile;
$res .="]";
echo $res;
}
You can't just jam two+ JSON strings togther. It's JSON, which means it HAS to be syntactically correct Javascript CODE. If you want to join two json strings together, you HAVE to decode them to a native data structure, join those structures together, then re-encode the new merged structure:
$temp1 = json_decode('{"foo":"bar"}', true);
$temp2 = json_decode('{"baz":"qux"}', true);
$new = array_merge($temp1, $temp2);
echo json_encode($new);
which will produce:
{"foo":"bar","baz":"qux"}
and remain valid JSON/Javascript.
Why? Consider that bare integers are valid json:
json_encode(42) -> 42
json_encode(123) -> 123
If you have two json-encoded integers and jam together, you get a "new" integer:
42123
and on the receiving end, you'll be going "Ok, so where is the split between the two", because 4 and 2123 are just as valid as possible original values as 4212 and 3.
Sending the two integers as distinct and SEPARATABLE values would require an array:
[42,123]

How can I break out segments of a 'PHP' file into raw PHP, -and- possible raw HTML -in order-

So I've got a concept of how to do this - but actually implementing me is a bit of a stumper for myself; mostly due to my lack of regex experience - but let's get into it.
I'd like to 'parse' through a 'php' file that could contain something like the following:
<?php
function Something()
{
}
?>
<html>
<body>
<? Something(); ?>
</body>
</html>
<?php
// Some more code or something
?>
If interpreted exactly - the above is worthless jibberish - but it is a good example of what I'd like to be able to parse, or interpret...
The idea is that I would read the contents of the above file, and break it out into an ordered array of its respective pieces; while tracking what 'type' each 'segment' is, so that I can either simply echo it, or run an 'eval()' on it.
Effectively, I'd like to end up with an array something like this:
$FileSegments = array();
$FileSegments[0]['type'] = "PHP";
$FileSegments[0]['content'] = "
function Something()
{
}";
$FileSegments[1]['type'] = "HTML";
$FileSegments[1]['content'] = "
<html>
<body>";
$FileSegments[2]['type'] = "PHP";
$FileSegments[2]['content'] = "Something();"
And so on...
The initial idea was to simply 'include()' or 'require()' the file in question, and grab its output from the output buffer - but it dawned on me that I would like to be able to inject some 'top level' variables into each one of these files before evaluating the code. To do this, I would have to 'eval()' my injected code, with the contents of the file after said injection - but in order to do this with the ability to handle raw HTML in the file too, I would have to basically write a temporary clone of the whole file, that just had my injected code written before the actual contents... Cumbersome, and slow.
I hope you're all following here... If not I can clarify...
The only other piece I feel I should note before finalizing this question; is that I would like to retain any variables or symbols in general ( for instance the 'Something() function ) created in segments 0 and 2, for instance, and pass them down to segment '4'... I feel like this might be achievable using the extract method, and then manually writing in those pieces of data before my next segment executes - but again I'm shooting a little in the dark on that.
If anyone has a better approach, or can give me some brief code on just extracting these 'segments' out of a file, I would be ecstatic.
cheers
ETA: It dawns on me that I can probably pose this question a little more simply: If there isn't a 'simple' way to do the above, is there a way to handle a String in the exact same way that 'require()' and 'include()' handle a File?
<?php
$str = file_get_contents('filename.php');
// get values from starting characters
$php_full = array_filter(explode('<?php', $str));
$php = array_filter(explode('<?', $str));
$html = array_filter(explode('?>', $str));
// remove values after last expected characters
foreach ($php_full as $key => $value) {
$php_full_result[] = substr($value, 0, strpos($value, '?>'));
}
foreach ($php as $key => $value) {
if( strpos($value,'php') !== 0 )
{
$php_result[] = substr($value, 0, strpos($value, '?>'));
}
}
$html_result[] = substr($str, 0, strpos($str, '<?'));
foreach ($html as $key => $value) {
$html_result[] = substr($value, 0, strpos($value, '<?'));
}
$html_result = array_filter($html_result);
echo '<pre>';
print_r($php_full_result);
echo '</pre>';
echo '<pre>';
print_r($php_result);
echo '</pre>';
echo '<pre>';
var_dump($html_result);
echo '</pre>';
?>
This will give you 3 arrays of file segments you want, not the exact format you wanted but you can easily modify this arrays to your needs.
For "I'd like to break all of my '$GLOBALS' variables out into their 'simple' names" part you can use extract like
extract($GLOBALS);

How to save regex backreferences to an array during preg_replace or preg_replace_callback

Here's the problem: I have a database full of articles marked up in XHTML. Our application uses Prince XML to generate PDFs. An artifact of that is that footnotes are marked up inline, using the following pattern:
<p>Some paragraph text<span class="fnt">This is the text of a footnote</span>.</p>
Prince replaces every span.fnt with a numeric footnote marker, and renders the enclosed text as a footnote at the bottom of the page.
We want to render the same content in ebook formats, and XHTML is a great starting point, but the inline footnotes are terrible. What I want to do is convert the footnotes to endnotes in my ebook build script.
This is what I'm thinking:
Create an empty array called $endnotes to store the endnote text.
Set a variable $endnote_no to zero. This variable will hold the current endnote number, to display inline as an endnote marker, and to be used in linking the endnote marker to the particular endnote.
Use preg_replace or preg_replace_callback to find every instance of <span class="fnt">(.*?)</span>.
Increment $endnote_no for each instance, and replace the inline span with '<sup><a href="#endnote_' . $endnote_no . '">' .$endnote_no . ''`
Push the footnote text to the $endnotes array so that I can use it at the end of the document.
After replacing all the footnotes with numeric endnote references, iterate through the $endnotes array to spit out the endnotes as an ordered list in XHTML.
This process is a bit beyond my PHP comprehension, and I get lost when I try to translate this into code. Here's what I have so far, which I mainly cobbled together based on code examples I found in the PHP documentation:
$endnotes = array();
$endnote_no = 0;
class Endnoter {
public function replace($subject) {
$this->endnote_no = 0;
return preg_replace_callback('`<span class="fnt">(.*?)</span>`', array($this, '_callback'), $subject);
}
public function _callback($matches) {
array_push($endnotes, $1);
return '<sup>' . $this->endnote_no . '</sup>';
}
}
...
$replacer = new Endnoter();
$replacer->replace($body);
echo '<pre>';
print_r($endnotes); // Just checking to see if the $endnotes are there.
echo '</pre>';
Any guidance would be helpful, especially if there is a simpler way to get there.
Don't know about a simpler way, but you were halfway there. This seems to work.
I just cleaned it up a bit, moved the variables inside your class and added an output method to get the footnote list.
class Endnoter
{
private $number_of_notes = 0;
private $footnote_texts = array();
public function replace($input) {
return preg_replace_callback('#<span class="fnt">(.*)</span>#i', array($this, 'replace_callback'), $input);
}
protected function replace_callback($matches) {
// the text sits in the matches array
// see http://php.net/manual/en/function.preg-replace-callback.php
$this->footnote_texts[] = $matches[1];
return '<sup>'.$this->number_of_notes.'</sup>';
}
public function getEndnotes() {
$out = array();
$out[] = '<ol>';
foreach($this->footnote_texts as $text) {
$out[] = '<li>'.$text.'</li>';
}
$out[] = '</ol>';
return implode("\n", $out);
}
}
First, you're best off not using a regex for HTML manipulation; see here:
How do you parse and process HTML/XML in PHP?
However, if you really want to go that route, there are a few things wrong with your code:
return '<sup>' . $this->endnote_no . '</sup>';
if endnote_no is 1, for example this will produce
'<sup>2</sup>';
If those values are both supposed to be the same, you want to increment endnote_no first:
return '<sup>' . $this->endnote_no . '</sup>';
Note the ++ in front of the call instead of after.
array_push($endnotes, $1);
$1 is not a defined value. You're looking for the array you passed in to the callback, so you want $matches[1]
print_r($endnotes);
$endnotes is not defined outside the class, so you either want a getter function to retrieve $endnotes (usually preferable) or make the variable public in the class. With a getter:
class Endnotes {
private $endnotes = array();
//replace any references to $endnotes in your class with $this->endnotes and add a function:
public function getEndnotes() {
return $this->endnotes;
}
}
//and then outside
print_r($replacer->getEndnotes());
preg_replace_callback doesn't pass by reference, so you aren't actually modifying the original string. $replacer->replace($body); should be $body = $replacer->replace($body); unless you want to pass body by reference into the replace() function and update its value there.

Searching within JSON with PHP for server side implementation of autocomplete jQuery plugin

I am trying to use jQuery Autocomplete Plugin in my PHP web application.
I have a JSON file on the server that has the data for the search. It looks like this:
{
"_E161": {
"keggId":"rn:R05223",
"abbrev":"ADOCBLS",
"name":"Adenosylcobalamin 5'-phosphate synthase",
"equation":"agdpcbi[c] + rdmbzi[c] -> h[c] + adocbl[c] + gmp[c] ",
},
"_E163": {
....
}
}
I would like to go through this JSON file (has 3500 entries) with PHP script that gets search term from the jQuery autocomplete plugin. Then return the entries that contain search term back to client side to populate autocomplete.
What would be a better way to implement this? My first guess is to loop through the JSON file and use strpos() But I suspect that might be slow?
You can make use on preg_grep (Return array entries that match the pattern),
// sanitize, and perform some processing to ensure is a valid regex pattern
$pattern = ...;
$json = json_decode( ... );
$arr = array();
foreach ($json as $key=>$arr)
{
$arr[$key] = $arr['name'];
}
$matches = preg_grep("/$pattern/i", $arr);
// $matches will hold the matches
// and you refer back to the $json using associate key

Categories