Open main menu

Module:Constraints

Documentation for this module may be created at Module:Constraints/doc

local wd        = require('Module:Wikidata')
local fb        = require('Module:Fallback')
local dtype     = require('Module:Datatype')
local TableTools= require('Module:TableTools')
local SPARQL    = require('Module:Constraints/SPARQL')
local search    = require('Module:Constraints/search')
local i18n_msgs = mw.loadData('Module:i18n/constraints')

local defaultlang = mw.getCurrentFrame():preprocess("{{int:lang}}")

local pConstraint          = "P2302"
local pFormatPattern       = "P1793"
local pProperty            = "P2306"
local pItems               = "P2305"
local pClasses             = "P2308"
local pRelation            = "P2309"
local pMinQuantity         = "P2313"
local pMaxQuantity         = "P2312"
local pMinDate             = "P2310"
local pMaxDate             = "P2311"
local pNamespace           = "P2307"
local pMandatory           = "P2316"
local pGroupBy             = "P2304"
local pExceptions          = "P2303"
local pInstanceOf          = "P31"
local pSubclassOf          = "P279"
local pComment             = "P2916"
local pComment2            = "P6607"
local pSeparator           = "P4155"
local pConstraintScope     = "P4680"
local pPropertyScope       = "P5314"

local pReplacementProperty = "P6824"

local cMandatory           = "Q21502408"
local cSuggested           = "Q62026391"

local cInstanceOf          = "Q21503252"
local cSubclassOf          = "Q21514624"
local cInstanceOfSubclassOf= "Q30208840"

local cSingleValue         = "Q19474404"
local cSingleBestValue     = "Q52060874"
local cFormat              = "Q21502404"
local cUniqueValue         = "Q21502410"
local cConflictsWith       = "Q21502838"
local cItem                = "Q21503247"
local cType                = "Q21503250"
local cQualifiers          = "Q21510851"
local cCommonsLink         = "Q21510852"
local cDiffWithinRange     = "Q21510854"
--local cDiffWithinRangeWithLink = "Q21510854"
local cInverse             = "Q21510855"
local cMultiValue          = "Q21510857"
local cOneOf               = "Q21510859"
local cRange               = "Q21510860"
local cSymmetric           = "Q21510862"
local cTargetRequiredClaim = "Q21510864"
local cValueType           = "Q21510865"
local cUnits               = "Q21514353"
local cMandatoryQualifiers = "Q21510856"
local cContemporary        = "Q25796498"
local cNoBounds            = "Q51723761"
local cInteger             = "Q52848401"
local cNoneOf              = "Q52558054"
local cScope               = "Q53869507"
local cEntityType          = "Q52004125"
local cCitationNeeded      = "Q54554025"
local cOneofQualifierValue = "Q52712340" -- test
local cLexemeCategory      = "Q55819078"
local cLexemeValueCategory = "Q64006792"
local cLexemeLanguage      = "Q55819106"

local cSomevalue           = 'somevalue'
local cNovalue             = 'novalue'

local cAllConstraints =
{
	cSingleValue,
	cSingleBestValue,
	cFormat,
	cUniqueValue,
	cConflictsWith,
	cItem,
	cType,
	cQualifiers,
	cCommonsLink,
	cDiffWithinRange,
--	cDiffWithinRangeWithLink,
	cInverse,
	cMultiValue,
	cOneOf,
	cRange,
	cSymmetric,
	cTargetRequiredClaim,
	cValueType,
	cUnits,
	cMandatoryQualifiers,
	cContemporary,
	cNoBounds,
	cInteger,
	cNoneOf,
	cScope,
	cEntityType,
	cCitationNeeded,
	cLexemeCategory,
	cLexemeValueCategory,
	cLexemeLanguage,
	cOneofQualifierValue,
}

local cItemonlyConstraints     = { cContemporary }
local cItemOrPropertyonlyConstraints     = {cInverse, cOneOf, cSymmetric, cValueType, cTargetRequiredClaim, cNoneOf}
local cQuantityonlyConstraints = { cUnits, cNoBounds, cInteger }

local cFormatapplicable = {"math","commonsMedia","string","external-id","url","monolingualtext","tabular-data","geo-shape","musical-notation"} 
local cCommonsLinkapplicable = {"commonsMedia","geo-shape","string","tabular-data"}
local cRangeapplicable = {"quantity","time"}  -- also cDiffWithinRange


local function i18n(str)
	return fb._langSwitch(i18n_msgs[str], defaultlang)
end


local function throw(error_type, ...)
	error(string.format(i18n(error_type), ...), 0);
end


local function wikifyQKey(id)
	local label = wd._getLabel(id, defaultlang)
	return "[[" .. id .. "|" .. label .. " <small>(" .. id .. ")</small>]]"
end


local function wikifyPKey(id)
	local label = wd._getLabel(id, defaultlang)
	return "[[Property:" .. id .. "|" .. label .. " <small>(" .. id .. ")</small>]]"
end


local function wikifyQKeys(ids)
	local values = {}
	for _, id in pairs(ids) do
		if id == cSomevalue then
			table.insert(values, "somevalue")
		elseif id == cNovalue then
			table.insert(values, "novalue")
		elseif #ids > 100 then
			table.insert(values, "[[" .. id .. "]]")
		else
			table.insert(values, wikifyQKey(id))
		end
	end
	return table.concat(values, ", ");
end


local function wikifyPKeys(ids)
	local values = {}
	for _, id in pairs(ids) do
		table.insert(values, wikifyPKey(id))
	end
	return table.concat(values, ", ");
end


local function contains(array, value)
	for i = 1, #array do
		if array[i] == value then
			return true
		end
	end

	return false
end


local function hasQualifier(statement, qualifier_id)
	return
		(statement ~= nil) and
		(statement.qualifiers ~= nil) and
		(statement.qualifiers[qualifier_id] ~= nil) and
		(#(statement.qualifiers[qualifier_id]) > 0)
end


local function getQualifierSingleValue( statement, qualifier_id )
	if hasQualifier(statement, qualifier_id) == false then
		throw("error_missing_qualifier", wikifyPKey(qualifier_id));
	end

	local qualifiers = statement.qualifiers[qualifier_id]
	if (#qualifiers > 1) then
		error("Too many qualifiers " .. wikifyPKey(qualifier_id), 0);
	end

	local qualifier = qualifiers[1]

	if (qualifier.snaktype == "somevalue") then
		return cSomevalue;
	elseif (qualifier.snaktype == "novalue") then
		return cNovalue;
	end

	if (qualifier.datavalue == nil
	    or qualifier.datavalue.type == nil
	    or qualifier.datavalue.value == nil) then
		error("Unexpected qualifier " .. wikifyPKey(qualifier_id) .. " value", 0);
	end

	if (qualifier.datavalue.type == "wikibase-entityid") then
		if (qualifier.datavalue.value["entity-type"] == "property") then
			return "P" .. qualifier.datavalue.value["numeric-id"];
		else						
			return "Q" .. qualifier.datavalue.value["numeric-id"];
		end
	else
		return wd.formatSnak(qualifier, { displayformat = 'raw' });
	end
end


local function getQualifierValues( statement, qualifier_id )
	local result = {}
	if hasQualifier(statement, qualifier_id) then
		for _, qualifier in pairs(statement.qualifiers[qualifier_id]) do
			if (qualifier.snaktype == "somevalue") then
				table.insert(result, cSomevalue)
			elseif (qualifier.snaktype == "novalue") then
				table.insert(result, cNovalue)
			elseif (qualifier.datavalue == nil
			    or qualifier.datavalue.type == nil
			    or qualifier.datavalue.value == nil) then
				table.insert(result, nil)
			else
				if ( qualifier.datavalue.type == "string" ) then
					table.insert(result, qualifier.datavalue.value);
				elseif ( qualifier.datavalue.type == "wikibase-entityid" ) then
					if (qualifier.datavalue.value["entity-type"] == "property") then
						table.insert(result, "P" .. qualifier.datavalue.value["numeric-id"]);
					else
						table.insert(result, "Q" .. qualifier.datavalue.value["numeric-id"]);
					end
				else 
					error("Unknown qualifier type: " .. qualifier.datavalue.type, 0);
				end
			end
		end
	end
	return result;
end


local function getQualifierAtLeastOneValue(statement, qualifier_id)
	local result = getQualifierValues(statement, qualifier_id)
	if #result == 0 then
		throw("error_missing_qualifier", wikifyPKey(qualifier_id));
	end
	return result
end


local function isMandatoryConstraint(constraint)
	if hasQualifier(constraint, pMandatory) then
		local value = getQualifierSingleValue(constraint, pMandatory)
		if value == cMandatory then
			return "true"
		elseif value ~= cSuggested then
			throw("error_unsupported_value", wikifyPKey(pMandatory), wikifyQKey(cMandatory))
		end
	end
	return ""
end


local function getOneOfQualifiers(constraint, qualifier_id1, qualifier_id2)
	if hasQualifier(constraint, qualifier_id1) then
		return getQualifierSingleValue(constraint, qualifier_id1)
	else
		return getQualifierSingleValue(constraint, qualifier_id2)
	end
end


local function getTypeRelation(constraint)
	local relation = getQualifierSingleValue(constraint, pRelation)

	if (relation == cInstanceOf) then
		return pInstanceOf
	elseif (relation == cSubclassOf) then
		return pSubclassOf
	elseif (relation == cInstanceOfSubclassOf) then
		return pInstanceOf,pSubclassOf
	else
		throw("error_unsupported_value", wikifyPKey(pRelation), wikifyQKeys({cInstanceOf, cSubclassOf}))
	end
end


local function getConstraintDescription(constraint, constraint_entity, property_id)
	--TODO: if somevalue or novalue is misused, it currently displays a module error, or unrelated number 0 or 1
	local type = constraint_entity.id
	
	local res = {}

	if (type == cSingleValue) then
		res.caption     = i18n("single_value_caption")
		res.description = i18n("single_value_description")
		res.query       = table.concat( {
			SPARQL.buildSingleValue(cSingleValue),
			SPARQL.buildNewSPARQL(constraint.id)
		}, ', ' )
		res.category    = "Properties with single value constraints"

	elseif (type == cSingleBestValue) then
		res.caption     = i18n("single_best_value_caption")
		res.description = i18n("single_best_value_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with single best value constraints"

	elseif (type == cFormat) then
		res.caption     = string.format(i18n("format_caption"), 
				"<code><nowiki>" .. getQualifierSingleValue(constraint, pFormatPattern) .. "</nowiki></code>")
		res.description = i18n("format_description")
		res.query       = table.concat( {
			SPARQL.buildFormat(getQualifierSingleValue(constraint, pFormatPattern)),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with format constraints"

	elseif (type == cUniqueValue) then
		res.caption     = i18n("unique_value_caption")
		res.description = i18n("unique_value_description")
		res.query       = table.concat( {
			SPARQL.buildUniqueValue(),
			SPARQL.buildUniqueValueByValue(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with unique value constraints"

	elseif (type == cConflictsWith) then
		local value = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
		if hasQualifier(constraint, pItems) then
			value = value .. ": " .. wikifyQKeys(getQualifierValues(constraint, pItems))
		end
		res.caption     = string.format(i18n("conflicts_with_caption"), value)
		res.description = i18n("conflicts_with_description")
		res.query = table.concat( TableTools.compressSparseArray( {
			search.buildConflictsWith(
				getQualifierSingleValue(constraint, pProperty),
				getQualifierValues(constraint, pItems)
			),
			SPARQL.buildConflictsWith(
				getQualifierSingleValue(constraint, pProperty),
				getQualifierValues(constraint, pItems)
			),
			SPARQL.buildNewSPARQL(constraint.id),
		}), ', ' )
		res.category    = "Properties with conflicts with constraints"

	elseif (type == cItem) then
		local value = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
		if hasQualifier(constraint, pItems) then
			value = value .. ": " .. wikifyQKeys(getQualifierValues(constraint, pItems))
		end
		res.caption     = string.format(i18n("item_caption"),     value)
		res.description = string.format(i18n("item_description"), value)
		res.query = table.concat( TableTools.compressSparseArray( {
			search.buildRequiredClaim(
				getQualifierSingleValue(constraint, pProperty),
				getQualifierValues(constraint, pItems)
			),
			SPARQL.buildRequiredClaim(
				getQualifierSingleValue(constraint, pProperty),
				getQualifierValues(constraint, pItems)
			),
			SPARQL.buildNewSPARQL(constraint.id),
		}), ', ' )
		res.category    = "Properties with constraints on items using them"

	elseif (type == cOneofQualifierValue) then
		local value = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
		if hasQualifier(constraint, pItems) then
			value = value .. ": " .. wikifyQKeys(getQualifierValues(constraint, pItems))
		end
		res.caption     = "Qualifier:" .. string.format(i18n("item_caption"),     value)
		res.description = "test: qualifier should use one of the values listed" -- string.format(i18n("OneofQualifierValue_description"), value)
		res.category    = "Properties with constraints on for values of qualifiers being used"

	elseif (type == cType) then
		local types = wikifyQKeys(getQualifierAtLeastOneValue(constraint, pClasses))
		res.caption     = string.format(i18n("type_caption"), types)
		res.description = string.format(i18n("type_description"), wikifyPKey(getTypeRelation(constraint)), types)
		res.query       = table.concat( {
			SPARQL.buildType(
				getTypeRelation(constraint),
				getQualifierAtLeastOneValue(constraint, pClasses)
			),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with constraints on type"

	elseif (type == cQualifiers) then
		local qualifiers = getQualifierValues(constraint, pProperty)
		if qualifiers[1] == cNovalue then
			res.caption     = i18n("without_qualifier_caption")
			res.description = i18n("without_qualifier_description")
			res.query       = SPARQL.buildNewSPARQL(constraint.id)
		else
			res.caption     = string.format(i18n("qualifiers_caption"), wikifyPKeys(getQualifierValues(constraint, pProperty)))
			res.description = i18n("qualifiers_description")
			res.query       = table.concat( {
				SPARQL.buildQualifiers(qualifiers),
				SPARQL.buildNewSPARQL(constraint.id),
			}, ', ' )
		end
		res.category    = "Properties with qualifiers constraints"
	elseif (type == cCommonsLink) then
		res.caption     = string.format(i18n("commons_caption"), hasQualifier(constraint, pNamespace) and getQualifierSingleValue(constraint, pNamespace) or "") --Better message needed for main namespace
		res.description = i18n("commons_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with Commons link constraints"

	elseif (type == cDiffWithinRange) then
		local prop = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
		local min  = getQualifierSingleValue(constraint, pMinQuantity)
		if min == cNovalue then
			min = "−∞"
		end
		local max  = getQualifierSingleValue(constraint, pMaxQuantity)
		if max == cNovalue then
			max = "+∞"
		end
		res.caption     = string.format(i18n("difference_caption"),     prop, min, max)
		res.description = string.format(i18n("difference_description"), prop, min, max)
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with difference within range constraints"

--	elseif (type == cDiffWithinRangeWithLink) then
--		local prop = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
--		local min  = getQualifierSingleValue(constraint, pMinQuantity)
--		if min == cNovalue then
--			min = "−∞"
--		end
--		local max  = getQualifierSingleValue(constraint, pMaxQuantity)
--		if max == cNovalue then
--			max = "+∞"
--		end
--		local itemlinkprop = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
--		res.caption     = string.format(i18n("difference_with_link_caption"),     prop, itemlinkprop, min, max)
--		res.description = string.format(i18n("difference_with_link_description"), prop, itemlinkprop, min, max)
--		res.query       = SPARQL.buildNewSPARQL(constraint.id)

	elseif (type == cInverse) then
		res.caption     = string.format(i18n("inverse_caption"),     wikifyPKey(getQualifierSingleValue(constraint, pProperty)))
		res.description = string.format(i18n("inverse_description"), wikifyPKey(property_id), wikifyPKey(getQualifierSingleValue(constraint, pProperty)))
		res.query       = table.concat( {
			SPARQL.buildInverse(getQualifierSingleValue(constraint, pProperty)),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with inverse constraints"

	elseif (type == cMultiValue) then
		res.caption     = i18n("multi_value_caption")
		res.description = i18n("multi_value_description")
		res.query       = table.concat( {
			SPARQL.buildMultiValue(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with multi value constraints"

	elseif (type == cOneOf) then
		res.caption     = string.format(i18n("one_of_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("one_of_description")
		res.query       = table.concat( TableTools.compressSparseArray( {
			search.buildOneOf(getQualifierValues(constraint, pItems)),
			SPARQL.buildOneOf(getQualifierValues(constraint, pItems)),
			SPARQL.buildNewSPARQL(constraint.id),
		}), ', ' )
		res.category    = "Properties with one-of constraints"

	elseif (type == cLexemeCategory)  then 
		res.caption     = string.format(i18n("lexeme_category_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("lexeme_category_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with lexical category constraints"

	elseif (type == cLexemeValueCategory)  then 
		res.caption     = string.format(i18n("lexeme_value_category_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("lexeme_value_category_description")
		res.query       = "" -- SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with lexical category constraints on value"

		
	elseif (type == cLexemeLanguage)  then 
		res.caption     = string.format(i18n("lexeme_language_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("lexeme_language_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with lexeme language constraints"

	elseif (type == cRange) then
		local min = getOneOfQualifiers(constraint, pMinQuantity, pMinDate)
		if min == cNovalue then
			min = "−∞"
		end
		if min == cSomevalue then
			min = i18n("now")
		end
		local max = getOneOfQualifiers(constraint, pMaxQuantity, pMaxDate)
		if max == cNovalue then
			max = "+∞"
		end
		if max == cSomevalue then
			max = i18n("now")
		end
		res.caption     = string.format(i18n("range_caption"),     min, max)
		res.description = string.format(i18n("range_description"), min, max)
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with range constraints"

	elseif (type == cSymmetric) then
		res.caption     = i18n("symmetric_caption")
		res.description = i18n("symmetric_description")
		res.query       = table.concat( {
			SPARQL.buildSymmetric(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with symmetric constraints"

	elseif (type == cTargetRequiredClaim) then
		local value = wikifyPKey(getQualifierSingleValue(constraint, pProperty))
		if hasQualifier(constraint, pItems) then
			value = value .. ": " .. wikifyQKeys(getQualifierValues(constraint, pItems))
		end
		res.caption     = string.format(i18n("target_item_caption"),     value)
		res.description = string.format(i18n("target_item_description"), value)
		local query = {
			SPARQL.buildTargetRequiredClaim(
				getQualifierSingleValue(constraint, pProperty),
				getQualifierValues(constraint, pItems)
			),
			SPARQL.buildNewSPARQL(constraint.id),
		}
		local q = SPARQL.buildTargetRequiredClaimByValue(
			getQualifierSingleValue(constraint, pProperty),
			getQualifierValues(constraint, pItems)
		)
		if q then table.insert(query, 2, q) end
		res.query       = table.concat( query, ', ' )
		res.category    = "Properties with target required claim constraints"

	elseif (type == cValueType) then
		local types = wikifyQKeys(getQualifierAtLeastOneValue(constraint, pClasses))
		res.caption     = string.format(i18n("value_type_caption"), types)
		res.description = string.format(i18n("value_type_description"), wikifyPKey(getTypeRelation(constraint)), wikifyPKey(getTypeRelation(constraint)), types)
		res.query       = table.concat( {
			SPARQL.buildValueType(
				getTypeRelation(constraint),
				getQualifierAtLeastOneValue(constraint, pClasses)
			),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with constraints on type"

	elseif (type == cUnits) then
		if hasQualifier(constraint, pItems) then
			local units = wikifyQKeys(getQualifierValues(constraint, pItems))
			res.caption     = string.format(i18n("units_caption"),     units)
			res.description = string.format(i18n("units_description"), units)
			res.query = SPARQL.buildNewSPARQL(constraint.id)
		else
			res.caption     = i18n("nounits_caption")
			res.description = i18n("nounits_description")
			res.query       = table.concat( {
				SPARQL.buildUnits(),
				SPARQL.buildNewSPARQL(constraint.id),
			}, ', ' )
		end
		res.category    = "Properties with units constraints"

	elseif (type == cMandatoryQualifiers) then
		res.caption     = string.format(i18n("mandatory_qualifier_caption"), wikifyPKey(getQualifierSingleValue(constraint, pProperty))) -- fixme: multiple?
		res.description = i18n("mandatory_qualifier_description")
		res.query       = table.concat( {
			SPARQL.buildMandatoryQualifiers(getQualifierValues(constraint, pProperty)),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with mandatory qualifiers constraints"

	elseif (type == cContemporary) then
		res.caption     = string.format(i18n("contemporary_caption"))
		res.description = string.format(i18n("contemporary_description"), wikifyPKey(property_id))
		res.query       = table.concat( {
			SPARQL.buildContemporary(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with contemporary constraints"

	elseif (type == cNoBounds) then
		res.caption     = string.format(i18n("nobounds_caption"))
		res.description = string.format(i18n("nobounds_description"))
		res.query       = table.concat( {
			SPARQL.buildNoBounds(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with no bounds constraints"

	elseif (type == cInteger) then
		res.caption     = string.format(i18n("integer_caption"))
		res.description = string.format(i18n("integer_description"))
		res.query       = table.concat( {
			SPARQL.buildInteger(),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with integer constraints"

	elseif (type == cNoneOf) then
		res.caption     = string.format(i18n("none_of_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("none_of_description")
		res.query       = table.concat( {
			SPARQL.buildOneOf(getQualifierValues(constraint, pItems), true),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with none-of constraints"

	elseif (type == cScope) then
		res.caption     = string.format(i18n("scope_caption"), wikifyQKeys(getQualifierValues(constraint, pPropertyScope)))
		res.description = i18n("scope_description")
		res.query       = table.concat( {
			SPARQL.buildScope(getQualifierValues(constraint, pPropertyScope)),
			SPARQL.buildNewSPARQL(constraint.id),
		}, ', ' )
		res.category    = "Properties with scope constraints"

	elseif (type == cEntityType) then
		res.caption     = string.format(i18n("entity_type_caption"), wikifyQKeys(getQualifierValues(constraint, pItems)))
		res.description = i18n("entity_type_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with entity type constraints"

	elseif (type == cCitationNeeded) then
		res.caption     = string.format(i18n("citation_needed_caption"))
		res.description = i18n("citation_needed_description")
		res.query       = SPARQL.buildNewSPARQL(constraint.id)
		res.category    = "Properties with citation needed constraints"

	else
		throw("error_unsupported_value", wikifyPKey(pConstraint), wikifyQKeys(cAllConstraints));

	end
	
	
	return res
end


local function getConstraintAnchor(constraint, constraint_entity)
	-- "P1813":[{"mainsnak":{"snaktype":"value","property":"P1813","datavalue":{"value":{"text":"Format","language":"en"}

	local anchor = constraint_entity.claims.P1813[1].mainsnak.datavalue.value.text
	for _, statement in ipairs(constraint_entity.claims.P1813) do
		if statement.mainsnak.datavalue.value.language == "en" then
			anchor = statement.mainsnak.datavalue.value.text
			break
		end
	end

	local type = constraint_entity.id
	if (type == cItem) or (type == cTargetRequiredClaim) or (type == cConflictsWith) then
		anchor = anchor .. " " .. getQualifierSingleValue(constraint, pProperty)
	elseif (type == cType) or (type == cValueType) then
		anchor = anchor .. " " .. table.concat(getQualifierValues(constraint, pClasses), ", ")
	end

	return anchor
end


local function getConstraintImage(constraint_entity)
	return constraint_entity.claims.P18[1].mainsnak.datavalue.value
end


local function verifyConstraintType(type)
	if not contains(cAllConstraints, type) then
		throw("error_unsupported_value", wikifyPKey(pConstraint), wikifyQKeys(cAllConstraints));
	end
end


local function verifyPropertyDatatype(constraint_type, property_datatype)
	if (property_datatype ~= "wikibase-item") and contains(cItemonlyConstraints, constraint_type) then
		throw("error_only_datatype", dtype.display("wikibase-item", defaultlang));
	elseif (property_datatype ~= "wikibase-item") and (property_datatype ~= "wikibase-property") and contains(cItemOrPropertyonlyConstraints, constraint_type) then
		throw("error_invalid_datatype", dtype.display(property_datatype, defaultlang));
	elseif (property_datatype ~= "quantity") and contains(cQuantityonlyConstraints, constraint_type) then
		throw("error_only_datatype", dtype.display("quantity", defaultlang));
	elseif (constraint_type == cCommonsLink) and (not(contains(cCommonsLinkapplicable, property_datatype))) then
		throw("error_invalid_datatype", dtype.display(property_datatype, defaultlang));
	elseif (constraint_type == cFormat) and (not(contains(cFormatapplicable, property_datatype))) then
		throw("error_invalid_datatype", dtype.display(property_datatype, defaultlang));
	elseif (constraint_type == cRange) and (not(contains(cRangeapplicable, property_datatype))) then
		throw("error_invalid_datatype", dtype.display(property_datatype, defaultlang));
	elseif (constraint_type == cDiffWithinRange) and (not(contains(cRangeapplicable, property_datatype))) then
		throw("error_invalid_datatype", dtype.display(property_datatype, defaultlang));
	end
end


local function verifyConstraintQualifier(qualifier, constraint_entity)
	local accepted_properties = { pMandatory, pExceptions, pGroupBy, pComment, pComment2, pSeparator, pConstraintScope, pPropertyScope, pConstraintEntityType, pReplacementProperty }

	if contains(accepted_properties, qualifier) then
		return true;
	end

	if constraint_entity.claims.P1659 ~= nil then
		for i, value in pairs(constraint_entity.claims.P1659) do
			local accepted_prop = "P" .. value.mainsnak.datavalue.value["numeric-id"]
			if qualifier == accepted_prop then
				return true
			end
			table.insert(accepted_properties, accepted_prop)
		end
	end

	throw("error_unsupported_qualifier", wikifyPKey(qualifier), wikifyPKeys(accepted_properties));
end


local function verifyQualifiers(constraint, constraint_entity)
	if (constraint.qualifiers ~= nil) then
		for key, value in pairs(constraint.qualifiers) do
			verifyConstraintQualifier(key, constraint_entity)
		end
	end
end

local function getExceptions(constraint)
	local exceptions 
	-- todo: allow exception lists from several items
	if hasQualifier(constraint, pExceptions) then
		exceptions = getQualifierAtLeastOneValue(constraint, pExceptions)
	end
	local text = ""
	local sep = ""
	if exceptions ~= nil then
		if #exceptions<21 then
			for i, exception in pairs(exceptions) do
				text = text .. sep .. wikifyQKey(exception)
				if sep == "" then
					sep = ", "
				end
			end
		else
			for i, exception in pairs(exceptions) do
				text = text .. sep .. "[[" .. exception .. "]]"
				if sep == "" then
					sep = ", "
				end
			end
		end
	end
	return text
end

local function makeConstraintDocumentation(constraint, property_id, property_datatype, add_category_to_page)
	local constraint_type = "Q" .. constraint.mainsnak.datavalue.value["numeric-id"]
	local constraint_entity = mw.wikibase.getEntity(constraint_type)
	local constraint_help = constraint_entity:getSitelink("wikidatawiki")
	
	local constraint_help_link
	if (constraint_help ~= nil) then
		constraint_help =  "Special:MyLanguage/" .. constraint_help
		constraint_help_link = "([[" .. constraint_help .. "|{{int:Help}}]])"
	else		
		constraint_help = ""
		constraint_help_link = ""
	end

	verifyConstraintType(constraint_type)
	verifyPropertyDatatype(constraint_type, property_datatype)
	verifyQualifiers(constraint, constraint_entity)
	
	local data = getConstraintDescription(constraint, constraint_entity, property_id)
	local category_text
	if add_category_to_page == true then
		category_text = "[[Category:" .. data.category .. "]]"
	else
		category_text = ""
	end
	
	return mw.getCurrentFrame():preprocess(
		"{{Constraint" ..
		"  |image       = [[File:" .. getConstraintImage(constraint_entity) .. "|40px|link=" .. constraint_help .. "|alt=]]" ..
		"  |name        = " .. data.caption ..
		"  |description = " .. data.description .. " " .. constraint_help_link ..
		"  |id          = " .. property_id ..
		"  |anchor      = " .. getConstraintAnchor(constraint, constraint_entity) .. 
		"  |exceptions  = " .. getExceptions(constraint) ..
		"  |mandatory   = " .. isMandatoryConstraint(constraint) ..
		"  |additional_report = " .. (data.query or '') ..
		"}}" .. category_text)
end


local function makeConstraintDocumentationSafe(constraint, property_id, property_datatype, add_category_to_page)
	local status, result = pcall(makeConstraintDocumentation, constraint, property_id, property_datatype, add_category_to_page)
	if status then
		return result
	else
		local constraint_type = "Q" .. constraint.mainsnak.datavalue.value["numeric-id"]
		return "<strong class=\"error\">" ..
		       string.format(i18n("error_in_constraint"), wikifyQKey(constraint_type), result) ..
		       "</strong><br />[[Category:Properties with constraint declaration errors]]"
	end
end


local p = {}


function p.extractIndividualConstraintTemplate(frame)
	local id = frame.args['id']
	local constraint_type = frame.args['constraint_type']
	local propertyentity = mw.wikibase.getEntity(id)
	
	if propertyentity == nil then
		return "<strong class=\"error\">" .. 
				"Error: " .. frame:preprocess("[[Property:" .. id .. "]]") .. " doesn't exist." ..
				"</strong>"
	end
	
	local property_datatype = propertyentity.datatype
	local constraints       = (propertyentity.claims or {})[pConstraint]
	local add_category_to_page = false

	SPARQL.setLanguage(defaultlang)
	SPARQL.setId(id)
	SPARQL.setDatatype(property_datatype)

	local text = ''
	if constraints ~= nil then
		for i, constraint in pairs(constraints) do
			if constraint.mainsnak.datavalue.value["id"] == constraint_type then
				text = text .. makeConstraintDocumentationSafe(constraint, id, property_datatype, add_category_to_page)
				return text
			end
		end
	end

	return "<strong class=\"error\">" .. 
			"Error: " .. frame:preprocess("{{Q|" .. constraint_type .. "}}")
			.. " is not defined at " .. frame:preprocess("{{P|" .. id .. "}}.") ..
			"</strong>"
end

function p.makedoc(frame)
	local id = frame:preprocess("{{BASEPAGENAME}}")

	local propertyentity = mw.wikibase.getEntity(id)
	local property_datatype = propertyentity.datatype
	local constraints       = (propertyentity.claims or {})[pConstraint]
	local add_category_to_page = true

	SPARQL.setLanguage(defaultlang)
	SPARQL.setId(id)
	SPARQL.setDatatype(property_datatype)

	search.setLanguage(defaultlang)
	search.setId(id)
	search.setDatatype(property_datatype)

	local text = ''
	if constraints ~= nil then
		for i, constraint in pairs(constraints) do
			text = text .. makeConstraintDocumentationSafe(constraint, id, property_datatype, add_category_to_page)
		end
	end

	return text
end

-- temporary functions for migration period

function p.getCaption(frame)
	return string.format(i18n(frame.args[1] .. "_caption"), frame.args[2], frame.args[3], frame.args[4], frame.args[5])
end

function p.getDescription(frame)
	return string.format(i18n(frame.args[1] .. "_description"), frame.args[2], frame.args[3], frame.args[4], frame.args[5])
end

return p