diff --git a/docs/README.md b/docs/README.md
index f0c4817..98e717e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,8 +6,9 @@ The following functions are available in the `mw.smw` package.
- Data retrieval functions
- - [`mw.smw.getQueryResult`](mw.smw.getQueryResult.md)
+ - [`mw.smw.ask`](mw.smw.ask.md)
- [`mw.smw.getPropertyType`](mw.smw.getPropertyType.md)
+ - [`mw.smw.getQueryResult`](mw.smw.getQueryResult.md)
- Data storage functions
@@ -20,10 +21,23 @@ The following functions are available in the `mw.smw` package.
## Notes
+### Difference between `mw.smw.ask` and `mw.smw.getQueryResult`
+Both functions allow you to retrieve data from your smw store. The difference lies in the returned table. Where `mw.smw.ask`
+returns a very simplistic result set (its values are all pre-formatted and already type cast), `mw.smw.getQueryResult` leaves
+you with full control over your returned data, giving you abundant information but delegates all the data processing to you.
+
+In other words:
+* `ask` is a quick and easy way to get data which is already pre-processed and may not suite your needs entirely
+(e.g. it does not link page properties). However it utilizes native SMW functionality like printout formatting
+(see [smwdoc] for more information)
+* `getQueryResult` gets you the full result set in the same format provided by the [api]
+
+For more information see the sample results in [`mw.smw.ask`](mw.smw.ask.md) and [`mw.smw.getQueryResult`](mw.smw.getQueryResult.md).
+
### Using #invoke
For a detailed description of the `#invoke` function, please have a look at the [Lua reference][lua] manual.
-[lua]: https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual
-[api]: https://www.semantic-mediawiki.org/wiki/Serialization_%28JSON%29
[smwdoc]: https://www.semantic-mediawiki.org/wiki/Semantic_MediaWiki
+[api]: https://www.semantic-mediawiki.org/wiki/Serialization_%28JSON%29
+[lua]: https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual
diff --git a/docs/mw.smw.ask.md b/docs/mw.smw.ask.md
new file mode 100644
index 0000000..f5a9f74
--- /dev/null
+++ b/docs/mw.smw.ask.md
@@ -0,0 +1,108 @@
+## mw.smw.ask
+
+With `mw.smw.ask` you can execute an smw query. It returns the result as a lua table for direct use in modules.
+For available parameters, please consult the [Semantic Media Wiki documentation hub][smwdoc].
+
+Please see the [return format below](#result) for the difference between this and [`mw.smw.getQueryResult`](mw.smw.getQueryResult.md).
+
+This is a sample call:
+```lua
+-- Module:SMW
+local p = {}
+
+-- Return results
+function p.ask(frame)
+
+ if not mw.smw then
+ return "mw.smw module not found"
+ end
+
+ if frame.args[1] == nil then
+ return "no parameter found"
+ end
+
+ local queryResult = mw.smw.ask( frame.args )
+
+ if queryResult == nil then
+ return "(no values)"
+ end
+
+ if type( queryResult ) == "table" then
+ local myResult = ""
+ for num, row in pairs( queryResult ) do
+ myResult = myResult .. '* This is result #' .. num .. '\n'
+ for k, v in pairs( row ) do
+ myResult = myResult .. '** ' .. k .. ': ' .. v .. '\n'
+ end
+ end
+ return myResult
+ end
+
+ return queryResult
+end
+
+-- another example, ask used inside another function
+function p.inlineAsk( frame )
+
+ local entityAttributes = {
+ 'has name=name',
+ 'has age=age',
+ 'has color=color'
+ }
+ local category = 'thingies'
+
+ -- build query
+ local query = {}
+ table.insert(query, '[[Category:' .. category .. ']]')
+
+ for _, v in pairs( entityAttributes ) do
+ table.insert( query, '?' .. v )
+ end
+
+ query.mainlabel = 'origin'
+ query.limit = 10
+
+ local result = mw.smw.ask( query )
+
+ local output = ''
+ if result and #result then
+
+ for num, entityData in pairs( result ) do
+ -- further process your data
+ output = output .. entityData.origin .. ' (' .. num .. ') has name ' .. entityData.name
+ .. ', color ' .. entityData.color .. ', and age ' .. entityData.age
+ end
+ end
+
+ return output
+end
+
+return p
+```
+
+### Return format
+
+The return format is a simple collection of tables (one per result set) holding your smw data,
+each indexed by property names or labels respectively. You can see an example below:
+
+```lua
+-- assuming sample call
+local result = mw.smw.ask( '[[Modification date::+]]|?Modification date|?Last editor is=editor|limit=2|mainlabel=page' )
+-- your result would look something like
+{
+ {
+ ['Modification date'] = '1 January 1970 23:59:59',
+ editor = 'User:Mwjames',
+ page = 'Main page'
+ },
+ {
+ ['Modification date'] = '2 January 1970 00:00:42',
+ editor = 'User:Oetterer',
+ page = 'User:Oetterer'
+ },
+}
+```
+
+This function is meant to be used inside lua modules and should not be directly exposed to #invoke.
+
+[smwdoc]: https://www.semantic-mediawiki.org/wiki/Semantic_MediaWiki
diff --git a/docs/mw.smw.getQueryResult.md b/docs/mw.smw.getQueryResult.md
index a2bbeb4..24d64d6 100644
--- a/docs/mw.smw.getQueryResult.md
+++ b/docs/mw.smw.getQueryResult.md
@@ -3,6 +3,8 @@
With `mw.smw.getQueryResult` you can execute an smw query. It returns the result as a lua table for direct use in modules.
For available parameters, please consult the [Semantic Media Wiki documentation hub][smwdoc].
+Please see the [return format below](#result) for the difference between this and [`mw.smw.ask`](mw.smw.ask.md).
+
This is a sample call:
```lua
-- Module:SMW
@@ -43,10 +45,13 @@ end
return p
```
+### Return format
+
The return format matches the data structure delivered by the [api]. You can see an example below:
+
```lua
-- assuming sample call
-local result = mw.smw.getQueryResult( '[[Modification date::+]]|?Modification date|?Last editor is|limit=2|mainlabel=page' )
+local result = mw.smw.getQueryResult( '[[Modification date::+]]|?Modification date|?Last editor is=editor|limit=2|mainlabel=page' )
-- your result would look something like
{
printrequests = {
@@ -134,3 +139,4 @@ Calling `{{#invoke:smw|ask|[[Modification date::+]]|?Modification date|limit=0|m
makes sense in a template or another module that can handle `table` return values.
[smwdoc]: https://www.semantic-mediawiki.org/wiki/Semantic_MediaWiki
+[api]: https://www.semantic-mediawiki.org/wiki/Serialization_%28JSON%29
\ No newline at end of file
diff --git a/src/LibraryFactory.php b/src/LibraryFactory.php
index 0485346..ddeffef 100644
--- a/src/LibraryFactory.php
+++ b/src/LibraryFactory.php
@@ -31,11 +31,14 @@ public function __construct( Store $store ) {
}
/**
+ * Creates a new SMWQueryResult from passed arguments,
+ * utilizing the {@see SMWQueryProcessor}
+ *
* @since 1.0
*
* @param string|array $rawParameters
*
- * @return QueryResult
+ * @return \SMWQueryResult
*/
public function newQueryResultFrom( $rawParameters ) {
@@ -58,36 +61,41 @@ public function newQueryResultFrom( $rawParameters ) {
}
/**
+ * Creates a new ParserParameterProcessor from passed arguments
+ *
* @since 1.0
*
* @param array $arguments
*
- * @return ParserParameterProcessor
+ * @return \SMW\ParserParameterProcessor
*/
public function newParserParameterProcessorFrom( $arguments ) {
return ParameterProcessorFactory::newFromArray( $arguments );
}
/**
+ * Creates a new SetParserFunction utilizing a Parser
+ *
* @since 1.0
*
* @param Parser $parser
*
- * @return SetParserFunction
+ * @return \SMW\SetParserFunction
*/
public function newSetParserFunction( Parser $parser ) {
return ApplicationFactory::getInstance()->newParserFunctionFactory( $parser )->newSetParserFunction( $parser );
}
/**
+ * Creates a new SubobjectParserFunction utilizing a Parser
+ *
* @since 1.0
*
* @param Parser $parser
*
- * @return SubobjectParserFunction
+ * @return \SMW\SubobjectParserFunction
*/
public function newSubobjectParserFunction( Parser $parser ) {
return ApplicationFactory::getInstance()->newParserFunctionFactory( $parser )->newSubobjectParserFunction( $parser );
}
-
}
diff --git a/src/LuaAskResultProcessor.php b/src/LuaAskResultProcessor.php
new file mode 100644
index 0000000..1ec151f
--- /dev/null
+++ b/src/LuaAskResultProcessor.php
@@ -0,0 +1,156 @@
+queryResult = $queryResult;
+ $this->msgTrue = explode( ',', wfMessage( 'smw_true_words' )->text() . ',true,t,yes,y' );
+ }
+
+ /**
+ * Extracts the data in {@see $queryResult} and returns it as a table
+ * usable in lua context
+ *
+ * @return array|null
+ */
+ public function getQueryResultAsLuaTable() {
+ if ( $this->queryResult == null ) {
+ return null;
+ }
+
+ $result = array();
+
+ while ( $resultRow = $this->queryResult->getNext() ) {
+ $result[] = $this->getDataFromQueryResultRow( $resultRow );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Extracts the data of a single result row in the {@see $queryResult}
+ * and returns it as a table usable in lua context
+ *
+ * @param array $resultRow result row as an array of {@see SMWResultArray} objects
+ *
+ * @return array
+ */
+ private function getDataFromQueryResultRow( array $resultRow ) {
+
+ $rowData = array();
+ $numberFallBack = 1;
+
+ /** @var \SMWResultArray $resultArray */
+ foreach ( $resultRow as $resultArray ) {
+ // find out, which key to use for this printRequest / query field
+ if ( $resultArray->getPrintRequest()->getLabel() === '' ) {
+ $key = $numberFallBack++;
+ } else {
+ $key = $resultArray->getPrintRequest()->getText( SMW_OUTPUT_WIKI );
+ }
+
+ // and get the data
+ $rowData[$key] = $this->getDataFromSMWResultArray( $resultArray );
+ }
+
+ return $rowData;
+ }
+
+ /**
+ * Extracts the data of a single printRequest / query field
+ *
+ * @param \SMWResultArray $resultArray
+ *
+ * @return mixed
+ */
+ private function getDataFromSMWResultArray( \SMWResultArray $resultArray ) {
+
+ $resultArrayData = array();
+
+ // get all data that is stored in this printRequest / query field
+ /** @var \SMWDataValue $dataValue */
+ while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) {
+
+ $resultArrayData[] = $this->getValueFromSMWDataValue( $dataValue );
+ }
+
+ if ( ! $resultArrayData || ! count( $resultArrayData ) ) {
+ // this key has no value(s). set to null
+ $resultArrayData = null;
+ } elseif ( count( $resultArrayData ) == 1 ) {
+ // there was only one semantic value found. reduce the array to this value
+ $resultArrayData = array_shift( $resultArrayData );
+ } else {
+ // $key has multiple values. keep the array, but un-shift it (remember: lua array counting starts with 1)
+ array_unshift( $resultArrayData, null );
+ }
+
+ return $resultArrayData;
+ }
+
+ /**
+ * Extracts the data of a single value of the current printRequest / query field
+ * The value is formatted according to the type of the property
+ *
+ * @param \SMWDataValue $dataValue
+ *
+ * @return mixed
+ */
+ private function getValueFromSMWDataValue( \SMWDataValue $dataValue ) {
+
+ $value = null;
+
+ switch ( $dataValue->getTypeID() ) {
+ case '_boo':
+ // boolean value found, convert it
+ $value = in_array( $dataValue->getShortText( SMW_OUTPUT_WIKI ), $this->msgTrue );
+ break;
+ case '_num':
+ // number value found
+ /** @var \SMWNumberValue $dataValue */
+ $value = $dataValue->getNumber();
+ $value = ( $value == (int) $value ) ? intval( $value ) : $value;
+ break;
+ case '_wpg' :
+ /** @var \SMWWikiPageValue $dataValue */
+ $value = $dataValue->getShortWikiText( );
+ // $value = $dataValue->getShortText( SMW_OUTPUT_WIKI );
+ break;
+ default:
+ $value = $dataValue->getShortText( SMW_OUTPUT_WIKI );
+ }
+
+ return $value;
+ }
+}
\ No newline at end of file
diff --git a/src/ScribuntoLuaLibrary.php b/src/ScribuntoLuaLibrary.php
index ad4dda2..24402ad 100644
--- a/src/ScribuntoLuaLibrary.php
+++ b/src/ScribuntoLuaLibrary.php
@@ -34,6 +34,7 @@ class ScribuntoLuaLibrary extends Scribunto_LuaLibraryBase {
public function register() {
$lib = array(
+ 'ask' => array( $this, 'ask' ),
'getPropertyType' => array( $this, 'getPropertyType' ),
'getQueryResult' => array( $this, 'getQueryResult' ),
'info' => array( $this, 'info' ),
@@ -45,27 +46,33 @@ public function register() {
}
/**
- * Returns query results in for of the standard API return format
+ * This mirrors the functionality of the parser function #ask and makes it
+ * available in lua.
*
* @since 1.0
*
- * @param string|array $arguments
+ * @param string|array $arguments parameters passed from lua, string or array depending on call
*
- * @return array
+ * @return null|array[]
*/
- public function getQueryResult( $arguments = null ) {
+ public function ask ( $arguments = null ) {
- $queryResult = $this->getLibraryFactory()->newQueryResultFrom(
- $this->processLuaArguments( $arguments )
- );
+ // if we have no arguments, do nothing
+ if ( !count( $arguments = $this->processLuaArguments( $arguments ) ) ) {
+ return null;
+ }
- $result = $queryResult->toArray();
+ $queryResult = $this->getLibraryFactory()->newQueryResultFrom( $arguments );
- if( !empty( $result["results"] ) ) {
- $result["results"] = array_combine( range( 1, count( $result["results"] ) ), array_values( $result["results"] ) );
+ $luaResultProcessor = new LuaAskResultProcessor( $queryResult );
+
+ $result = $luaResultProcessor->getQueryResultAsLuaTable();
+
+ if ( is_array( $result ) && count ( $result ) ) {
+ array_unshift( $result, null );
}
- return array( $result );
+ return array ( $result );
}
/**
@@ -95,6 +102,30 @@ public function getPropertyType( $propertyName = null ) {
return array( $property->findPropertyTypeID() );
}
+ /**
+ * Returns query results in for of the standard API return format
+ *
+ * @since 1.0
+ *
+ * @param string|array $arguments
+ *
+ * @return array
+ */
+ public function getQueryResult( $arguments = null ) {
+
+ $queryResult = $this->getLibraryFactory()->newQueryResultFrom(
+ $this->processLuaArguments( $arguments )
+ );
+
+ $result = $queryResult->toArray();
+
+ if( !empty( $result["results"] ) ) {
+ $result["results"] = array_combine( range( 1, count( $result["results"] ) ), array_values( $result["results"] ) );
+ }
+
+ return array( $result );
+ }
+
/**
* This mirrors the functionality of the parser function #info and makes it
* available to lua.
@@ -141,16 +172,16 @@ public function info( $text, $icon = 'info' ) {
*
* @since 1.0
*
- * @param string|array $parameters parameters passed from lua, string or array depending on call
+ * @param string|array $arguments parameters passed from lua, string or array depending on call
*
* @return null|array|array[]
*/
- public function set( $parameters ) {
+ public function set( $arguments ) {
$parser = $this->getEngine()->getParser();
// if we have no arguments, do nothing
- if ( !count( $arguments = $this->processLuaArguments( $parameters ) ) ) {
+ if ( !count( $arguments = $this->processLuaArguments( $arguments ) ) ) {
return null;
}
@@ -164,7 +195,7 @@ public function set( $parameters ) {
$result = $this->doPostProcessParserFunctionCallResult( $parserFunctionCallResult );
if ( strlen( $result ) ) {
- // if result a non empty string, assume an error message
+ // if result is a non empty string, assume an error message
return array( [ 1 => false, self::SMW_ERROR_FIELD => preg_replace( '/<[^>]+>/', '', $result ) ] );
} else {
// on success, return true
@@ -176,17 +207,17 @@ public function set( $parameters ) {
* This mirrors the functionality of the parser function #subobject and
* makes it available to lua.
*
- * @param string|array $parameters parameters passed from lua, string or array depending on call
+ * @param string|array $arguments parameters passed from lua, string or array depending on call
* @param string $subobjectId if you need to manually assign an id, do this here
*
* @return null|array|array[]
*/
- public function subobject( $parameters, $subobjectId = null ) {
+ public function subobject( $arguments, $subobjectId = null ) {
$parser = $this->getEngine()->getParser();
// if we have no arguments, do nothing
- if ( !count( $arguments = $this->processLuaArguments( $parameters ) ) ) {
+ if ( !count( $arguments = $this->processLuaArguments( $arguments ) ) ) {
return null;
}
@@ -251,6 +282,26 @@ private function doPostProcessParserFunctionCallResult( $parserFunctionResult )
return trim( $result );
}
+ /**
+ * Returns the LibraryFactory singleton (and creates it first, if not already present)
+ *
+ * @since 1.0
+ *
+ * @return LibraryFactory
+ */
+ private function getLibraryFactory() {
+
+ if ( $this->libraryFactory !== null ) {
+ return $this->libraryFactory;
+ }
+
+ $this->libraryFactory = new LibraryFactory(
+ ApplicationFactory::getInstance()->getStore()
+ );
+
+ return $this->libraryFactory;
+ }
+
/**
* Takes the $arguments passed from lua and pre-processes them: make sure,
* we have a sequence array (not associative)
@@ -285,17 +336,4 @@ private function processLuaArguments( $arguments ) {
return $processedArguments;
}
- private function getLibraryFactory() {
-
- if ( $this->libraryFactory !== null ) {
- return $this->libraryFactory;
- }
-
- $this->libraryFactory = new LibraryFactory(
- ApplicationFactory::getInstance()->getStore()
- );
-
- return $this->libraryFactory;
- }
-
}
diff --git a/src/mw.smw.lua b/src/mw.smw.lua
index a02bbb0..9dd9f07 100644
--- a/src/mw.smw.lua
+++ b/src/mw.smw.lua
@@ -24,11 +24,9 @@ function smw.setupInterface()
package.loaded['mw.smw'] = smw
end
--- getQueryResult
-function smw.getQueryResult( queryString )
- local queryResult = php.getQueryResult( queryString )
- if queryResult == nil then return nil end
- return queryResult
+-- ask
+function smw.ask( parameters )
+ return php.ask( parameters )
end
-- getPropertyType
@@ -36,6 +34,12 @@ function smw.getPropertyType( name )
return php.getPropertyType( name )
end
+-- getQueryResult
+function smw.getQueryResult( queryString )
+ local queryResult = php.getQueryResult( queryString )
+ if queryResult == nil then return nil end
+ return queryResult
+end
-- info
function smw.info( text, icon )
diff --git a/tests/phpunit/Unit/LuaAskResultProcessorTest.php b/tests/phpunit/Unit/LuaAskResultProcessorTest.php
new file mode 100644
index 0000000..7422799
--- /dev/null
+++ b/tests/phpunit/Unit/LuaAskResultProcessorTest.php
@@ -0,0 +1,49 @@
+queryResult = $this->getMockBuilder( '\SMWQueryResult' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\SMW\Scribunto\LuaAskResultProcessor',
+ new LuaAskResultProcessor(
+ $this->queryResult
+ )
+ );
+
+ }
+}
diff --git a/tests/phpunit/Unit/ScribuntoLuaLibraryAskTest.php b/tests/phpunit/Unit/ScribuntoLuaLibraryAskTest.php
new file mode 100644
index 0000000..a1ceb72
--- /dev/null
+++ b/tests/phpunit/Unit/ScribuntoLuaLibraryAskTest.php
@@ -0,0 +1,61 @@
+ __DIR__ . '/' . 'mw.smw.ask.tests.lua',
+ );
+ }
+
+
+ /**
+ * Tests method info
+ *
+ * @return void
+ */
+ public function testAsk() {
+ $this->assertEmpty(
+ $this->getScribuntoLuaLibrary()->ask( null )[0]
+ );
+ $this->assertEmpty(
+ $this->getScribuntoLuaLibrary()->ask( '' )[0]
+ );
+ $this->assertArrayHasKey(
+ 0,
+ $this->getScribuntoLuaLibrary()->ask( '[[Modification date::+]]|?Modification date|limit=2|mainlabel=main' )
+ );
+ $this->assertArrayHasKey(
+ 1,
+ $this->getScribuntoLuaLibrary()->ask( '[[Modification date::+]]|?Modification date|limit=2|mainlabel=main' )[0]
+ );
+ $this->assertArrayHasKey(
+ 'main',
+ $this->getScribuntoLuaLibrary()->ask( '[[Modification date::+]]|?Modification date|limit=2|mainlabel=main' )[0][1]
+ );
+ $this->assertEquals(
+ 3, # (sic!) there is a null entry for key 0 to suite lua's table indexing
+ count ( $this->getScribuntoLuaLibrary()->ask( '[[Modification date::+]]|?Modification date|limit=2|mainlabel=main' )[0] )
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/phpunit/Unit/mw.smw.ask.tests.lua b/tests/phpunit/Unit/mw.smw.ask.tests.lua
new file mode 100644
index 0000000..64682eb
--- /dev/null
+++ b/tests/phpunit/Unit/mw.smw.ask.tests.lua
@@ -0,0 +1,44 @@
+--[[
+ Tests for smw.info module
+
+ @since 1.0
+
+ @licence GNU GPL v2+
+ @author Tobias Oetterer
+]]
+
+local testframework = require 'Module:TestFramework'
+
+-- Tests
+local tests = {
+ { name = 'ask (nil argument)', func = mw.smw.ask,
+ args = { nil },
+ expect = { nil }
+ },
+ { name = 'ask (no argument)', func = mw.smw.ask,
+ args = { '' },
+ expect = { nil }
+ },
+ { name = 'ask (limit 0)', func = mw.smw.ask,
+ args = { { '[[Modification date::+]]', '?Modification date', limit = 0, mainlabel = 'main' } },
+ expect = { {} }
+ },
+ { name = 'ask (count two results)',
+ func = function ( args )
+ local ret = mw.smw.ask( args )
+ return #ret
+ end,
+ args = { '[[Modification date::+]]|?Modification date|limit=2|mainlabel=-' },
+ expect = { 2 }
+ },
+ { name = 'ask (for mainlabel)',
+ func = function ( args )
+ local result = mw.smw.ask( args )
+ return type( result[1].main )
+ end,
+ args = { { '[[Modification date::+]]', '?Modification date', limit = 1, mainlabel = 'main' } },
+ expect = { 'string' }
+ },
+}
+
+return testframework.getTestProvider( tests )