« Module:SSC base » : différence entre les versions

De Semantic MediaWiki - Sandbox

(Page créée avec « -- start the class local class = {} function class:new( config ) if type( config ) ~= 'table' then error 'You must provide a configuration table!' end o = {} setm... »)
 
Aucun résumé des modifications
Ligne 21 : Ligne 21 :
self.output = ''
self.output = ''
if self.config.headline then
if self.config.headline then
self.output = self.output .. '==' .. pageHeadline .. ' ==\n'
self.output = self.output .. '==' .. self.config.headline .. ' ==\n'
end
end


Ligne 73 : Ligne 73 :
local counter = 1 -- needed to set the correct arguments for the ibArgs table
local counter = 1 -- needed to set the correct arguments for the ibArgs table
for _, row in pairs ( self.config.infoboxConfig ) do
for _, row in pairs ( self.config.infoboxConfig ) do
if row.argument and data[row.argument] then
if row.field and data[row.field] then
if row.label then
if row.label then
ibArgs['label' .. counter] = row.label
ibArgs['label' .. counter] = row.label
end
end
ibArgs['data' .. counter] = getPrintout( row.argument, self.data[row.argument] )
ibArgs['data' .. counter] = getPrintout( row.field )
counter = counter + 1
counter = counter + 1
end
end
Ligne 173 : Ligne 173 :
local semanticData = {}
local semanticData = {}
for arg, property in pairs( self.config.parameters ) do
for arg, property in pairs( self.config.parameters ) do
if self.data[arg] then
if self.data[arg] and type( self.data[arg] ) == 'string' then
if type( self.data[arg] ) == 'table' then
if type( self.data[arg] ) == 'table' then
table.insert( semanticData, property .. '=' .. table.concat( self.data[arg], self.config.delimiter ) )
table.insert( semanticData, property .. '=' .. table.concat( self.data[arg], self.config.delimiter ) )

Version du 19 décembre 2016 à 23:27

This is a base class to be inherited by special entity classes. It provides basic functionality like argument plausibility tests, semantic storage, display of an infobox, etc.

Access to the semantíc data (reading and writing) is provided by Extension:Semantic Scribunto.

Usage

To build an entity class, create an appropriate config module. Use Module:SSC base/config as a template. Then put the following code on a module page to build the entity class:

local config = mw.loadData( 'Module:<entity module>/config' )
local baseClass = require( 'Module:SSC base' )

local class = baseClass:new( config )

return class

Suggested article structure for new sub classes

Module:Entity
Holds all the functions, that can be invoked on a template or a normal page
Module:Entity/class
Here you inherit from Module:SSC base and extend your entity class
Module:Entity/config
Holds the configuration for your entity. A template can be found on Module:SSC base/config.

For templates (as in boilerplates), please see below.

"Abstract" methods

There are several methods in this class that should/can be implemented by your entity class.

setDefaults( data )

Can be used to insert some defaults into the datastream after arguments from the template call are processed but before they are stored semantically. Here is the husk:

function class:setDefaults( data )
	local data = data
	-- for example
	data.title = data.title or mw.title.getCurrentTitle().text
	return data
end

data is a table that holds your arguments after preprocessing. Insert your defaults but don't forget to return the data table at the end.

alterDataAfterStorage( data )

Manipulates the data table after semantic data is stored but before the infobox is displayed.

function class:alterDataAfterStorage( data )
	local data = data
	-- for example
	data.header1 = 'Base data'
	return data
end

Note: a field in your data table is only added to your infobox, if the class' configuration says so.

Public methods

renderPage()

This is usually called in a function, which is invoked on the entity's module page. It provides this functions:

  1. filter known and existant argument data from template arguments
  2. checks for existance of mandatory arguments
  3. converts all list fields into a table
  4. saves the semantic data
  5. builds a nice infobox (utilizing Module:Infobox)
  6. places the page in the appropriate category
local entity = require( 'Module:<entity module>/class' ):new()
return entity:renderPage()

retrieveClassData( filter )

This can be used to retrieve all data stored for entities of that class.

If you want a subset, use string filter like the selector in an ask query (e.g. '[[property::value]] [[property2::value2]]').

local entityClass = require( 'Module:<entity module>/class' )
local classData = entityClass:retrieveClassData()
-- now classData is a table of up to 500 rows, each containing data about an entity
-- each row is a table, indexed by fieldnames as defined in the parameters table in entity class' config

Module boilerplates

You need a base module. This bridges your template and your class module and "exposes" some functions.

Boilerplate for your Module:Entity
local p = {}

function p.main( frame )
	local entity = require( 'Module:Entity/class' ):new()
	return entity:renderPage()
end

return p

Then there is the class, which inherits its main functionality from Module:SSC base. Remember, that you should fill the two "abstract" methods setDefaults() and alterDataAfterStorage.

Boilerplate for your Module:Entity/class
local config = mw.loadData( 'Module:Entity/config' )
local class = require( 'Module:SSC base' )

local entity = class:new( config )

function entity:alterDataAfterStorage( data )
	local data = data

	if not data.nonEmtyField then
		data.profession = 'unknown'
	end
	
	if data['date of birth'] and mw.ustring.find( data['date of birth'], '/', 1, true ) then
		local date = mw.text.split( data['date of birth'], '/', true )
		data['date of birth'] = date[2] .. '.' .. date[1] .. '.' .. date[3]
	end
	
	return data
end

function entity:setDefaults( data )
	local data = data
	data.title = data.title or mw.title.getCurrentTitle().text
	return data
end

function entity:addSomeData()
	local cbData, query = comicBooks:retrieveClassData( '[[Has comic book writer::' .. mw.title.getCurrentTitle().text .. ']]' )
	cbData = cbData or {}
	return '\n=== Works ===\n' .. self:extractWorks( cbData ) .. '\n'
		.. '=== Collaborations ===\n' .. self:extractCollaborations( cbData ) .. '\n'
end

function entity:renderPage()
	return class.renderPage( self )
		.. self:addSomeData()
end

return person

Finally, each class needs a config file. Here is the structure to fill:

Boilerplate for your Module:Entity/config
return {
	-- **********************
	-- * mandatory settings *
	-- **********************
	
	-- this is your type of entity
	entityType = '',
	
	-- this is the category, the entities of the class will be put in
	category = '',
	
	-- lists all available pairs "template parameter" -> "semantic property"
	-- if you want to have a field in the template without storing it semantically, set it to true
	parameters = {
		-- example entries
		firstname			= 'has firstname',
		lastname			= 'has lastname',
		nostore				= true,
	},

	-- tell the class, which fields are mandatory
	-- for every argument in this list not present on your page's template call,
	-- an error will be displayed
	mandatory = { 'firstname', 'firstname', ... },
	
	-- these fields possibly contain more than one value
	listFields = { '' },
	
	-- separator used in list fields
	delimiter = ',',

	-- *********************
	-- * optional settings *
	-- *********************
	-- the headline of the entity's page
	headline = 'This is a page about an entity',

	-- INFOBOX CONFIG HERE
	-- here you can disable your infobox
	omitInfoBox = false,

	-- this is the name of the field, used as title in the infobox
	titleField = 'name',
	
	-- this defines, which fields are put into your infobox and in which order
	infoboxConfig = {
		-- for every row in your infobox, add a table here, containing at least the entry "field" which
		-- refers to the data field to display. if you omit the entry "label", the field will be displayed
		-- over both columns
		{ field = 'firstname', label = 'Firstname' },
		{ field = 'lastname', label = 'Lastname' },
		{ field = 'nostore', label = 'No Store' },
		-- why is this not defined as an array: lua does not maintain the order of items in an array but accesses them randomly
	},
	
	-- configure here, which fields should be linked (or form-linked)
	linkFields = {
		-- if a field is set to true, it will be linked
		-- if it is set to a string, #formlink will be used
		firstname	= true,
		formEntry	= 'FormForEntrys'
	},
}

Example

See Module:Person/class, method person:getPersonVitae() for an example.


-- start the class
local class = {}

function class:new( config )
	if type( config ) ~= 'table' then
		error 'You must provide a configuration table!'
	end
	
	o = {}
	setmetatable( o, self )
	self.__index = self
	
	-- store class configuration
	self.config = config
	
	return o
end

function class:renderPage()
	
	self.output = ''
	if self.config.headline then
		self.output = self.output .. '==' .. self.config.headline .. ' ==\n'
	end

	self:getArguments()
	
	self:setDefaults()
	
	self:checkForMandatoryFields()
	
	self:convertListFields()
	
	self:storeSemanticData()
	
	if not self.config.omitInfoBox then
		self:addInfobox()
	end
	
	self:addCategory()
	
	return tostring( self.output )
end

function class:addCategory()
	local text = text
	if not in_array( mw.title.getCurrentTitle().namespace, { 10, 11, 828, 829 } ) and self.config.category then
		self.ouput = self.ouput .. '[[Category:' .. self.config.category .. ']]'
	end
end

function class:addInfobox()
	if self.config.infoboxConfig  and type( self.config.infoboxConfig  ) == 'table' then

		local ibArgs = {
			aboveclass = 'objtitle titletext',
			headerclass = 'headertext',
			labelstyle = 'width: 30%;',
			datastyle = 'width: 70%;',
			title = self.data.title,
		}
		if self.config.entityType then
			ibArgs.bodyclass = 'infobox_' .. mw.ustring.gsub( mw.ustring.lower( self.config.entityType ), ' ', '_' )
			ibArgs.subheader = self.config.entityType
		end
		
		if self.config.titleField and self.data[self.config.titleField] then
			ibArgs.title = self.data[self.config.titleField]
		else
			ibArgs.title = self.data.title or self.data.name
		end

		local counter = 1 -- needed to set the correct arguments for the ibArgs table
		for _, row in pairs ( self.config.infoboxConfig ) do
			if row.field and data[row.field] then
				if row.label then
					ibArgs['label' .. counter] = row.label
				end
				ibArgs['data' .. counter] = getPrintout( row.field )
				counter = counter + 1
			end
		end
	
		self.output = require( 'Module:Infobox' ).infobox( ibArgs ) .. '\n'
			.. self.output
	else
		self.output = self.output .. formatError( 'An infobox was requested, but no class configuration for an infobox is present!' )
	end
end

function class:checkForMandatoryFields()
	if self.config.mandatory and type( self.config.mandatory ) == 'table' then
		for _, m in pairs( self.config.mandatory ) do
			if not self.data[m] then
				self.output = self.output .. formatError( 'Mandatory argument "' .. m .. '" is missing!' )
			end
		end
	end
end

function class:convertListFields()
	if self.config.listFields and type( self.config.listFields ) == 'table' then
		for _, lf in pairs( self.config.listFields ) do
			if self.data[lf] then
				self.data[lf] = mw.text.split( self.data[lf], delimiter, true )
				-- clear empty fields
				for k, v in pairs( self.data[lf] ) do
					if #v == 0 then
						self.data[lf][k] = nil
					end
				end
			end
			if self.data[lf] and #self.data[lf] > 0 then
				if #self.data[lf] == 1 then
					self.data[lf] = self.data[lf][1]
				end
			else
				self.data[lf] = nil
			end
		end
	end
end

function class:getArguments()
	
	local args = require( 'Module:Arguments' ).getArgs( frame, { parentOnly = true } )
	local data = {}
	
	if self.config.parameters and type( self.config.parameters ) == 'table' then
		for k, _ in pairs( self.config.parameters ) do
			data[k] = args[k]
		end
	end
	
	self.data = data
end

function class:getPrintout( field, data )
	--[[
	the printout of field depends on certain things:
	* is it a table (aka list) or a single item
	* should it be linked (aka is it present in linkFields)
	* if so, should it be form linked? (aka not simply set to true but to a string)
	--]]
	
	local data = data or self.data[field]
	
	if type( data ) == 'table' then
		for k, v in pairs( data ) do
			data[k] = getPrintout( field, v )
		end
		return mw.text.listToText( data, ', ', ' and ' )
	end
	
	if self.config.linkFields and type(self.config.linkFields) == 'table' and self.config.linkFields[field] then
		if type( self.config.linkFields[field] ) == 'string' then
			local frame = mw.getCurrentFrame()
			local args = {
				form = self.config.linkFields[field],
				['link text'] = data,
				['existing page link text'] = data
			}
			return frame:callParserFunction{ name='#formredlink:target=' .. data, args=args }
		else
			return '[[' .. data .. ']]'
		end
	else
		return data
	end
end

function class:storeSemanticData()
	local semanticData = {}
	for arg, property in pairs( self.config.parameters ) do
		if self.data[arg] and type( self.data[arg] ) == 'string' then
			if type( self.data[arg] ) == 'table' then
				table.insert( semanticData, property .. '=' .. table.concat( self.data[arg], self.config.delimiter ) )
				table.insert( semanticData, '+sep=' .. self.config.delimiter )
				--[[
				-- instead of using a delimiter with +set, you could do something like this:
				for _, v in pairs( self.data[arg] ) do
					table.insert( semanticData, property .. '=' .. v )
				end
				--]]
			else
				table.insert( semanticData, property .. '=' .. self.data[arg] )
			end
		end
	end
	if #semanticData > 0 then
		mw.smw.set( semanticData )
	else
		self.output = self.output .. formatError( 'There was no semantic data to store!' )
	end	
end


-- override when necessary
function class:setDefaults()
end


-- non class functions
local formatError = function( text )
	return '<div style="color:red; font-weight:700">' .. text .. '</div>\n'
end

local in_array = function( needle, haystack )
	local invertedHaystack = {}
	for _, v in pairs( haystack ) do
		invertedHaystack[v] = true
	end
	return invertedHaystack[needle]
end

-- return class
return class
Les cookies nous aident à fournir nos services. En utilisant nos services, vous acceptez notre utilisation de cookies.