Для документации этого модуля может быть создана страница Модуль:WDBackend/doc

require('strict')

local p = {}
local base = require('Модуль:WDBase')

local Backend = {}

function Backend:new(lang)
	local obj = { lang=lang }
	setmetatable(obj, self)
	self.__index = self
	
	return obj
end

function Backend:safeField(source, fieldName, parentField)
	if not parentField then
		parentField = source
	end
	local info = parentField[fieldName]
	if not info then
		info = {}
	end
	return info
end

function Backend:trySetField(source, fieldName, info, parentField)
	if info.value or info.entity then
		if not parentField then
			parentField = source
		end
		parentField[fieldName] = info
	end
end

local function inArray(value, array)
	for _, currValue in ipairs(array) do
		if currValue == value then
			return true
		end
	end
	return false
end


function Backend:fetchFieldsByQualifiers(source, fieldMap, qualifiers, parentField)
	for _, map in ipairs(fieldMap) do
		local qualifier = qualifiers[map.property]
		if qualifier then
			if map.filter then
				qualifier = map.filter(qualifier, self.lang)
			end
			local currParentField = parentField
			if map.isLocal then
				currParentField = parentField.components or {}
			end
			self:fetchFieldByMap(source, map, qualifier, base.dataBySnak, currParentField)
			if map.isLocal and next(currParentField) ~= nil then
				parentField.components = currParentField
			end
		end
	end
end

function Backend:fetchFieldItem(source, map, statementOrSnak, getData)
	local item = getData(statementOrSnak, self.lang)
	if item then
		item.retrieved = true
	else
		item = {}
	end
	if map.mapEntity then
		local entity = map.mapEntity[item.entity]
		if not entity then
			return nil
		end
		item = base.dataByEntity(entity)
		if item then
			item.retrieved = true
		end
	end

	if map.getValue then
		item.value = map.getValue(item.entity, self.lang)
	end
	if map.allowedEntities and item.entity and not inArray(item.entity, map.allowedEntities) then
		return nil
	end

	if map.defaultUnit and not item.unitEntity then
		item.unitEntity = map.defaultUnit
	end
	if map.allowedUnits then
		if not item.unitEntity or not inArray(item.unitEntity, map.allowedUnits) then
			return nil
		end
	end

	if not item.value and not item.entity then
		return nil
	end
	
	if map.exact then
		item.exact = true
	end

	return item
end

local function skipGetIf(item, cond)
	if not cond then
		return false
	end
	
	for key, value in pairs(cond) do
		if item[key] == value then
			return true
		end
	end
	
	return false
end

function Backend:fetchFieldByMap(source, map, statementsOrSnaks, getData, parentField)
	local fieldName = map.name
	local currParentField = source
	if map.isLocal or not fieldName then
		currParentField = parentField
	end
	if fieldName then
		local fieldTable = self:safeField(source, fieldName, currParentField)
		if map.match and getData == base.dataByStatement and fieldTable.value then
			local statement = base.searchStatementByValue(statementsOrSnaks, fieldTable.value)
			if not statement then
				return
			end
			local item = self:fetchFieldItem(source, map, statement, getData)
			if item then
				currParentField[fieldName] = item
				if map.qualifiers and statement.qualifiers then
					self:fetchFieldsByQualifiers(source, map.qualifiers, statement.qualifiers, item)
				end
				if map.get and not skipGetIf(item, map.skipGetIf) then
					self:fetchFieldsByMap(source, item.entity, map.get, currParentField)
				end
			else
				if map.elseGet then
					self:fetchFieldsByMap(source, parentField.entity, map.elseGet, parentField)
				end
			end
			return
		end
		if fieldTable.value and not map.overwrite then
			return
		end
	end

	local count = table.getn(statementsOrSnaks)
	if map.max then
		if count > map.max then
			count = map.max
		end
	end

	local items = {}
	local indices = {}
	for i=1, count do
		local statementOrSnak = statementsOrSnaks[i]
		if statementOrSnak ~= nil then
			local item = self:fetchFieldItem(source, map, statementOrSnak, getData)
			if item then
				table.insert(items, item)
				indices[i] = table.getn(items)
			else
				indices[i] = 0
			end
		end
	end

	local triggerElseGet = false
	if fieldName then
		if table.getn(items) == 1 and not map.isArray then
			currParentField[fieldName] = items[1]
		elseif next(items) ~= nil then
			currParentField[fieldName] = items
		else
			triggerElseGet = true
		end
	elseif next(items) ~= nil then
		if map.overwriteValue then
			currParentField.value = items[1].value
			currParentField.exact = map.exact
		end
		if map.overwriteEntity then
			currParentField.entity = items[1].entity
		end
	else
		triggerElseGet = true
	end

	for i=1, count do
		local statementOrSnak = statementsOrSnaks[i]
		local item = items[indices[i]]
		if map.qualifiers and statementOrSnak.qualifiers then
			self:fetchFieldsByQualifiers(source, map.qualifiers, statementOrSnak.qualifiers, item)
		end				
	end

	if fieldName then
		for j, item in ipairs(items) do
			if map.get and not skipGetIf(item, map.skipGetIf) then
				local components = {}
				self:fetchFieldsByMap(source, item.entity, map.get, components)
				if next(components) ~= nil then
					item.components = components
				end
			end
		end
	else
		for j, item in ipairs(items) do
			if map.get and not skipGetIf(item, map.skipGetIf) then
				self:fetchFieldsByMap(source, item.entity, map.get, currParentField)
			end
		end
	end
	
	if triggerElseGet and map.elseGet then
		self:fetchFieldsByMap(source, parentField.entity, map.elseGet, parentField)
	end
end

function Backend:fetchFieldByCustomFunc(source, entity, map, parentField)
	local currParentField = source
	if map.isLocal then
		currParentField = parentField
	end

	local fieldTable = self:safeField(source, map.name, currParentField)
	if fieldTable.value and not map.overwrite then
		return
	end

	fieldTable.value = map.getValue(entity, self.lang)
	self:trySetField(source, map.name, fieldTable, currParentField)
end

local function getPropertyByPath(source, propertyPath)
	local currField = source
	for _, pathEntry in ipairs(propertyPath) do
		currField = currField[pathEntry]
		if not currField then
			return nil
		end
	end
	return currField
end

local function filterStatementsByPropertiesAndValues(statements, properties)
	local filtered = {}
	local propsCount = table.getn(properties)
	for _, statement in ipairs(statements) do
		local matched = 0
		local qualifiers = statement.qualifiers
		if qualifiers then
			for _, propInfo in ipairs(properties) do
				local propQualifiers = qualifiers[propInfo.property]
				if propQualifiers then
					for _, propQualifier in ipairs(propQualifiers) do
						if propInfo.value then
							local value = base.valueBySnak(propQualifier)
							if value == propInfo.value then
								matched = matched + 1
							else
								break
							end
						else
							matched = matched + 1
						end
					end
				end
			end
		end
		if matched == propsCount then
			table.insert(filtered, statement)
		end
	end

	if not next(filtered) then
		return nil
	end
	return filtered
end

local function statementsByCache(entity, property, cache)
	if cache then
		local entityCache = cache[entity]
		if entityCache then
			local statements = entityCache[property]
			if statements then
				return statements
			end
		end
	end
	return base.statements(entity, property)
end

function Backend:fetchFieldsByMap(source, entity, fieldMap, parentField)
	for _, map in ipairs(fieldMap) do
		local currEntity = entity
		if map.entity then
			currEntity = map.entity
		end
		if currEntity then
			local propertySpecified = false
			local statements

			if map.property then
				statements = statementsByCache(currEntity, map.property, map.cache)
				propertySpecified = true
			elseif map.properties then
				statements = base.statementsByProperties(currEntity, map.properties)
				propertySpecified = true
			elseif map.propertyPath then
				local property = getPropertyByPath(source, map.propertyPath)
				if property then
					statements = statementsByCache(currEntity, property, map.cache)
				end
				propertySpecified = true
			end

			if propertySpecified then
				if statements then
					if map.filter then
						statements = map.filter(statements, self.lang)
					end
					if map.has then
						statements = filterStatementsByPropertiesAndValues(statements, map.has)
					end
					self:fetchFieldByMap(source, map, statements, base.dataByStatement, parentField)
				else
					if map.elseGet then
						self:fetchFieldsByMap(source, currEntity, map.elseGet, parentField)
					end
				end
			else
				if not propertySpecified and map.getValue then
					self:fetchFieldByCustomFunc(source, currEntity, map, parentField)
				end
			end
		end
	end
end

function Backend:fetch(source, fieldsMap)
	for _, map in ipairs(fieldsMap) do
		local fieldTable = self:safeField(source, map.name)
		if fieldTable then
			self:fetchFieldsByMap(source, fieldTable.entity, map.get, source)
		end
	end
end

function Backend:fetchEntity(source, entity, fieldMap)
	self:fetchFieldsByMap(source, entity, fieldMap)
end

function Backend:ensureLang()
	if self.lang then
		return
	end

	local defaultLangObj = mw.getContentLanguage()
	local defaultLang = defaultLangObj:getCode()
	self.lang = defaultLang
end

function Backend:assertLang()
	assert(self.lang, 'No language selected.')
end

function p.new(lang)
	return Backend:new(lang)
end

return p