Hello. I'm developing a Semantic MediaWiki extension to partition an #ask query based upon the first character of one of the queried properties so that the user can page through partitions of what would otherwise be a very large query result. For example, I'd like to query all pages in Category:Author in my wiki, but there are almost 4,000 of those, so I want to partition the results dynamically by the first character of the author's last name. I have a working version, but I'm in the process of enhancing it to use Ajax so that it only needs to refresh the <div> with the new query results rather than the entire page as well as retaining previous partitions in hidden <div>'s so previous queries don't need to be repeated. I'm having two parsing issues in the Ajax callback. I do not believe that they are related to Ajax per se. The second issue may be caused by my inexpert fix to the first issue, although I ran across the same second issue previously in a different context. I have reproduced both issues in a vastly simplified, minimal test program that I will discuss below.
I'm using MediaWiki 1.16.0 and SemanticMediaWiki 1.5.2.
The first issue is that I need access to the parser in the Ajax callback so I can call recursiveTagParse on the results for the query. I tried using $wgParser, but it does not appear to be in a good state in the callback. I had a series of errors where the parser was calling functions on non-objects. I played around with it a bit and was able to get past all of these errors with this rather ugly code:
global $wgParser; $wgParser->mOptions = new ParserOptions; $wgParser->initialiseVariables(); $wgParser->clearState(); $wgParser->setTitle(new Title($title));
where $title is the title retrieved using getTitle on the original (pre-Ajax) working parser and sent as a parameter to JavaScript and back. These calls allow me to get a parser that does not give me run-time errors, but I'm not at all confident that this is the correct approach.
The second issue I'm having is that when I call recursiveTagParse on the data returned from the query in the Ajax callback, some of the data is "disappears". I should note that I'm invoking the same query and parsing code in both the initial display of the page and in the Ajax callback. It works initially but does not work in the callback using the questionable parser instance described above. However, I had also seen the same blanking behavior in a previous task where I did not have the first contributing issue. In that case, I changed my approach completely to avoid the recursiveTagParse, but I don't believe I have the option of doing that here. I found a bug report that seems similar, but I'm not sure if it is the same problem, and there doesn't seem to be any movement on that bug: https://bugzilla.wikimedia.org/show_bug.cgi?id=24556.
I'm reproducing my simplified test case below. You can run it by placing the following wikitext on a page:
{{#testQuery:[[Category:Author]] |limit=5 |searchlabel= |format=table }}
replacing [[Category:Author]] with the query term of your choice. When you install the PHP extension code below, you'll see a page with a button and a table containing up to 5 query results. When you press the button, the same query is invoked, but the table is empty. If you comment out the line that contains the call to recursiveTagParse in the PHP code below, you'll see that the query results are indeed being returned in both cases, but they are getting obliterated in the Ajax callback by recursiveTagParse.
It may very well be that solving the first issue will solve the second one, but since I saw the second issue in another context previously, I wonder if they really are two separate issues.
Thank you very much for any assistance in working through these issues!
Cindy
PHP extension code:
<?php
/** * To activate the functionality of this extension include the following * in your LocalSettings.php file: * include_once("$IP/extensions/TestQuery/TestQuery.php"); */
if( !defined( 'MEDIAWIKI' ) ) die( "This is an extension to the MediaWiki package and cannot be run standalone." );
# credits $wgExtensionCredits['parserhook'][] = array ( 'name' => 'TestQuery', 'version' => '1.0', 'author' => "Cindy Cicalese", 'description' => "Bug test" );
$wgUseAjax = true; $wgAjaxExportList[] = 'testQueryPopulateDiv';
$wgHooks['LanguageGetMagic'][] = 'wfExtensionTestQuery_Magic'; $wgHooks['ParserFirstCallInit'][] = 'efTestQueryParserFunction_Setup';
function efTestQueryParserFunction_Setup (& $parser) { $parser->setFunctionHook('testQuery', 'testQuery'); return true; }
function wfExtensionTestQuery_Magic(& $magicWords, $langCode) { $magicWords['testQuery'] = array (0, 'testQuery'); return true; }
function testQuery($parser, $query) { $params = func_get_args(); array_shift($params); // first is $parser; strip it array_shift($params); // second is query string; strip it $testQuery = new TestQuery(); $output = $testQuery->firstVisit($parser, $query, $params); $parser->disableCache(); return array($parser->insertStripItem($output, $parser->mStripState), 'noparse' => false); }
function testQueryPopulateDiv($query, $paramString, $title) { $params = explode("|", $paramString); $testQuery = new TestQuery(); $output = $testQuery->populateDiv($query, $params, $title); return $output; }
class TestQuery {
private $template = false;
function firstVisit($parser, $query, $params) { $js = <<<EOT <script type="text/javascript"> function buttonClicked() { var query = document.forms['TestQuery'].Query.value; var params = document.forms['TestQuery'].Params.value; var title = document.forms['TestQuery'].Title.value; var div = document.getElementById('TestDiv'); sajax_do_call('testQueryPopulateDiv', [query, params, title], div); } </script> EOT; $parser->mOutput->addHeadItem($js); $this->parseParameters($params); $output = $this->buildForm($query, $params, $parser->getTitle()); $result = $this->getData($parser, $query, $params); $output .= $this->buildDiv($result); return $output; }
function populateDiv($query, $params, $title) { global $wgParser; $wgParser->mOptions = new ParserOptions; $wgParser->initialiseVariables(); $wgParser->clearState(); $wgParser->setTitle(new Title($title)); $this->parseParameters($params); $currentPartitionData = $this->getData($wgParser, $query, $params); return $currentPartitionData; }
private function parseParameters($params) { foreach ($params as $param) { if (preg_match("/^ *format *= *template *$/", $param) === 1) { $this->template = true; } } }
private function buildForm($query, $params, $title) { $paramString = implode("|", $params); $out = <<<EOT <center> <button type='button' id='TestButton' onClick="buttonClicked()">Test Button </button> <form id='TestQuery' method='post' action=''> <input type='hidden' name='Query' value='$query'> <input type='hidden' name='Params' value='$paramString'> <input type='hidden' name='Title' value='$title'> </form> </center><br> EOT; return $out; }
private function getData($parser, $query, $params) { $result = $this->doSMWAsk($parser, $query, $params); $result = $parser->recursiveTagParse($result); return $result; }
private function doSMWAsk($parser, $query, $rawParams) { SMWQueryProcessor::processFunctionParams($rawParams, $qs, $params, $printouts); $output = SMWQueryProcessor::getResultFromQueryString($query, $params, $printouts, SMW_OUTPUT_WIKI); return $output; }
private function buildDiv($result) { $output = "<div id='TestDiv' display='block'>"; $output .= $result; $output .= "</div>"; return $output; } }
-- Dr. Cynthia Cicalese Lead Software Systems Engineer The MITRE Corporation
2010/11/16 Cindy Cicalese cicalese@mitre.org:
$wgUseAjax = true; $wgAjaxExportList[] = 'testQueryPopulateDiv';
This AJAX framework is obsolete. You should use the bot API for AJAX instead. Documentation is at http://www.mediawiki.org/wiki/API . There's a section on how to create your own modules from an extension too.
If all you need to do is parse some wikitext without otherwise needing to do things in PHP (i.e. if you can generate the wikitext to parse on the JS side), you could use the existing action=parse module to parse it.
Roan Kattouw (Catrope)
* Roan Kattouw roan.kattouw@gmail.com [Tue, 16 Nov 2010 18:10:51 +0100]:
2010/11/16 Cindy Cicalese cicalese@mitre.org:
$wgUseAjax = true; $wgAjaxExportList[] = 'testQueryPopulateDiv';
This AJAX framework is obsolete. You should use the bot API for AJAX instead. Documentation is at http://www.mediawiki.org/wiki/API . There's a section on how to create your own modules from an extension too.
What if my ajax call PHP function is required for extension's client scripts only and is meaningless to bots? (On-page interactivity). Why should everything to be an API, ajax is more than bots? Dmitriy
On 2010-11-17, Dmitriy Sintsov wrote:
What if my ajax call PHP function is required for extension's client scripts only and is meaningless to bots? (On-page interactivity). Why should everything to be an API, ajax is more than bots?
Because it provides a consistent, clean framework for making and handling requests with the potential to reduce duplication in a lot of cases. The API is not just for bots.
Adding an API module is fairly trivial and is the correct way to provide AJAX interactivity.
Robert
* Robert Leverington robert@rhl.me.uk [Wed, 17 Nov 2010 07:38:23 +0000]:
Because it provides a consistent, clean framework for making and handling requests with the potential to reduce duplication in a lot of cases. The API is not just for bots.
API is supposed to be useful for another (remote) clients. Some of my ajax calls are useful only locally and only to my own extension. Why should I expose these openly.
Adding an API module is fairly trivial and is the correct way to
provide
AJAX interactivity.
In some cases it's unneeded complication, where you have to build the tables of parameter types, parameter descriptions and so on. And also expose all of that functionality in api.php help.
I use both API and wgAjaxExportList[], for different purposes. I may completely switch to API, however that doesn't look nice to me. But anyway, you decide. Dmitriy
Roan Kattouw <roan.kattouw <at> gmail.com> writes:
2010/11/16 Cindy Cicalese <cicalese <at> mitre.org>:
$wgUseAjax = true; $wgAjaxExportList[] = 'testQueryPopulateDiv';
This AJAX framework is obsolete. You should use the bot API for AJAX instead. Documentation is at http://www.mediawiki.org/wiki/API . There's a section on how to create your own modules from an extension too.
If all you need to do is parse some wikitext without otherwise needing to do things in PHP (i.e. if you can generate the wikitext to parse on the JS side), you could use the existing action=parse module to parse it.
Roan Kattouw (Catrope)
Thank you. Since I did need to do additional work in PHP before parsing, I could not use action=parse directly from the JavaScript. But, I will investigate switching from using the old Ajax framework to using the API.
Cindy
Cindy Cicalese wrote:
The first issue is that I need access to the parser in the Ajax callback so I can call recursiveTagParse on the results for the query. I tried using $wgParser, but it does not appear to be in a good state in the callback. I had a series of errors where the parser was calling functions on non-objects. I played around with it a bit and was able to get past all of these errors with this rather ugly code:
global $wgParser; $wgParser->mOptions = new ParserOptions; $wgParser->initialiseVariables(); $wgParser->clearState(); $wgParser->setTitle(new Title($title));
You are not expected to use recursiveTagParse() not being called by the Parser. You are initializing the parser by hand.
Try instead using $wgParser->parse()
Platonides <Platonides <at> gmail.com> writes:
You are not expected to use recursiveTagParse() not being called by the Parser. You are initializing the parser by hand.
Try instead using $wgParser->parse()
Thank you! Switching to parse() worked. I had to instantiate a new parser rather than using $wgParser, since I got the "UNIQ...QINU" error in my "real" code when I used $wgParser.
I'm copying my simple working test code to the bottom of this message in case anybody else is trying to get something similar working. It can be invoked with wikitext of the form:
{{#testQuery:[[Category:Author]] |searchlabel= |format=table }}
Thanks,
Cindy
<?php
/** * To activate the functionality of this extension include the following * in your LocalSettings.php file: * include_once("$IP/extensions/TestQuery/TestQuery.php"); */
if( !defined( 'MEDIAWIKI' ) ) die( "This is an extension to the MediaWiki package and cannot be run standalone." );
# credits $wgExtensionCredits['parserhook'][] = array ( 'name' => 'TestQuery', 'version' => '1.0', 'author' => "Cindy Cicalese", 'description' => "Bug test" );
$wgUseAjax = true; $wgAjaxExportList[] = 'testQueryPopulateDiv';
$wgHooks['LanguageGetMagic'][] = 'wfExtensionTestQuery_Magic'; $wgHooks['ParserFirstCallInit'][] = 'efTestQueryParserFunction_Setup';
function efTestQueryParserFunction_Setup (& $parser) { $parser->setFunctionHook('testQuery', 'testQuery'); return true; }
function wfExtensionTestQuery_Magic(& $magicWords, $langCode) { $magicWords['testQuery'] = array (0, 'testQuery'); return true; }
function testQuery($parser, $query) { $params = func_get_args(); array_shift($params); // first is $parser; strip it array_shift($params); // second is query string; strip it $testQuery = new TestQuery(); $output = $testQuery->firstVisit($parser, $query, $params); $parser->disableCache(); return array($parser->insertStripItem($output, $parser->mStripState), 'noparse' => false); }
function testQueryPopulateDiv($query, $paramString, $title, $offset, $limit) { $params = explode("|", $paramString); $testQuery = new TestQuery(); $output = $testQuery->populateDiv($query, $params, $title, $offset, $limit); return $output; }
class TestQuery {
function firstVisit($parser, $query, $params) { $js = <<<EOT <script type="text/javascript"> function buttonClicked() { var query = document.forms['TestQuery'].Query.value; var params = document.forms['TestQuery'].Params.value; var title = document.forms['TestQuery'].Title.value; var offset = document.forms['TestQuery'].Offset.value; var limit = document.forms['TestQuery'].Limit.value; offset = parseInt(offset) + parseInt(limit); document.forms['TestQuery'].Offset.value = offset; var div = document.getElementById('TestDiv'); sajax_do_call('testQueryPopulateDiv', [query, params, title, offset, limit], div); } </script> EOT; $parser->mOutput->addHeadItem($js); $title = $parser->getTitle()->getText(); $output = $this->buildForm($query, $params, $title); $result = $this->getData($query, $params, $title, 0, 5); $output .= $this->buildDiv($result); return $output; }
function populateDiv($query, $params, $title, $offset, $limit) { return $this->getData($query, $params, $title, $offset, $limit); }
private function buildForm($query, $params, $title) { $paramString = implode("|", $params); $out = <<<EOT <center> <button type='button' id='TestButton' onClick="buttonClicked()">Test Button </button> <form id='TestQuery' method='post' action=''> <input type='hidden' name='Query' value='$query'> <input type='hidden' name='Params' value='$paramString'> <input type='hidden' name='Title' value='$title'> <input type='hidden' name='Offset' value='0'> <input type='hidden' name='Limit' value='5'> </form> </center><br> EOT; return $out; }
private function getData($query, $params, $title, $offset, $limit) { $params['offset'] = $offset; $params['limit'] = $limit; $result = $this->doSMWAsk($query, $params); $parser = new Parser; $output = $parser->parse($result, Title::newFromText($title), new ParserOptions); return $output->getText(); }
private function doSMWAsk($query, $rawParams) { SMWQueryProcessor::processFunctionParams($rawParams, $qs, $params, $printouts); $output = SMWQueryProcessor::getResultFromQueryString($query, $params, $printouts, SMW_OUTPUT_WIKI); return $output; }
private function buildDiv($result) { $output = "<div id='TestDiv' display='block'>"; $output .= $result; $output .= "</div>"; return $output; } }
wikitech-l@lists.wikimedia.org