Sari la conținut

Modul:Citat Q

De la Wikipedia, enciclopedia liberă

local p = {}

-- =============================================================================
-- CONFIGURATION & CONSTANTS
-- =============================================================================

local MONTHS = {
	"ianuarie", "februarie", "martie", "aprilie", "mai", "iunie",
	"iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie"
}

-- Internal property mapping
local PROP_MAP = {
	-- Basic Metadata
	title       = { id = 'P1476', type = 'monolingualtext' },
	subtitle    = { id = 'P1680', type = 'monolingualtext' },
	pubDate     = { id = 'P577',  type = 'time' },
	accessDate  = { id = 'P813',  type = 'time' },
	
	publisher   = { id = 'P123',  type = 'item_linked' },
	place       = { id = 'P291',  type = 'item_sitelink' },
	language    = { id = 'P407',  type = 'item_code' },
	
	archiveurl  = { id = 'P1065', type = 'url' },
    archivedate = { id = 'P2960', type = 'time' },
	
	-- Container / Parent
	journal     = { id = 'P1433', type = 'item_label' },      
	series      = { id = 'P179',  type = 'item_label' },
	
	-- Specifics
	volume      = { id = 'P478',  type = 'string' },
	issue       = { id = 'P433',  type = 'string' },
	pages       = { id = 'P304',  type = 'string' },
	at          = { id = 'P958',  type = 'string' },
	chapter     = { id = 'P792',  type = 'string' },
	edition     = { id = 'P393',  type = 'string' },
	version     = { id = 'P348',  type = 'string' },
	
	-- Identifiers (Fallback enabled)
	doi         = { id = 'P356',  type = 'string' },
	isbn        = { id = 'P212',  type = 'string' },
	isbn10      = { id = 'P957',  type = 'string' },
	issn        = { id = 'P236',  type = 'string' },
	oclc        = { id = 'P243',  type = 'string' },
	pmid        = { id = 'P698',  type = 'string' },
	pmc         = { id = 'P932',  type = 'string' },
	arxiv       = { id = 'P818',  type = 'string' },
	bibcode     = { id = 'P819',  type = 'string' },
	jstor       = { id = 'P888',  type = 'string' },
	s2cid       = { id = 'P8299', type = 'string' },
	lccn        = { id = 'P1144', type = 'string' },
	hdl         = { id = 'P1184', type = 'string' },
	ismn        = { id = 'P1208', type = 'string' },
	citeseerx   = { id = 'P3784', type = 'string' },
	ol          = { id = 'P648',  type = 'string' },
}

-- Contributors
local CONTRIB_MAP = {
	author      = { id = 'P50',   fallback = 'P2093', label = 'Autor' },
	editor      = { id = 'P98',   fallback = 'P5769', label = 'Editor' },
	translator  = { id = 'P655',  label = 'Traducător' }, 
	illustrator = { id = 'P110',  label = 'Ilustrator' },
	composer    = { id = 'P86',   label = 'Compozitor' },
	screenwriter= { id = 'P58',   label = 'Scenarist' },
	director    = { id = 'P57',   label = 'Regizor' },
}

local URL_PROPS = {
    { id = 'P854', type = 'url' },  -- reference URL
    { id = 'P953', type = 'url' },  -- full work available at URL
    { id = 'P4945', type = 'url' }, -- download URL  
    { id = 'P973', type = 'url' },  -- described at URL
    { id = 'P856', type = 'url' },  -- official website
}

-- =============================================================================
-- HELPERS
-- =============================================================================

-- Helper: Generalized extractor for both Reference Snaks and Entity Claims
local function get_prop_values(source, propId)
    if not source or not source[propId] then return nil end
    
    local values = {}
    -- Handle both Reference structure (snaks) and Entity structure (claims)
    local list = source[propId] 
    
    for _, item in ipairs(list) do
        local snak = item.mainsnak or item -- specific to how wikidata Lua tables are structured
        if snak.snaktype == 'value' and snak.datavalue then
            if snak.datatype == 'wikibase-item' then
                table.insert(values, snak.datavalue.value.id) -- Store QID
            elseif snak.datatype == 'string' or snak.datatype == 'monolingualtext' then
                table.insert(values, snak.datavalue.value) -- Store String
            -- Add other datatypes (time, quantity) if needed
            end
        end
    end
    return (#values > 0) and values or nil
end

local function getLabel(id)
	if not id then return nil end
	
	-- Try ro first
	local label = mw.wikibase.getLabelByLang(id, 'ro')
	if label then return label end
	
	-- Try mul second
	label = mw.wikibase.getLabelByLang(id, 'mul')
	if label then return label end
	
	-- Try the entity's own language (P407) third
	local entity = mw.wikibase.getEntity(id)
	if entity and entity.claims and entity.claims['P407'] then
		for _, langClaim in ipairs(entity.claims['P407']) do
			if langClaim.mainsnak.snaktype == 'value' then
				local langId = langClaim.mainsnak.datavalue.value.id
				-- Get language code
				local langEntity = mw.wikibase.getEntity(langId)
				if langEntity and langEntity.claims and langEntity.claims['P424'] then
					local langCode = langEntity.claims['P424'][1].mainsnak.datavalue.value
					label = mw.wikibase.getLabelByLang(id, langCode)
					if label then return label end
				end
			end
		end
	end
	
	-- Finally try en, fr, de
	for _, lang in ipairs({'en', 'fr', 'de'}) do
		label = mw.wikibase.getLabelByLang(id, lang)
		if label then return label end
	end
	
	return nil
end

local function getLinkedLabel(id)
	local label = getLabel(id) or id
	local sitelink = mw.wikibase.getSitelink(id)
	
	if sitelink then
		return "[[" .. sitelink .. "|" .. label .. "]]"
	else
		return "[[:d:" .. id .. "|" .. label .. "]]<abbr title='Articolul încă nu există în acest wiki'>[*]</abbr>"
	end
end

local function getSitelinkOrLabel(id)
	local label = getLabel(id) or id
	local sitelink = mw.wikibase.getSitelink(id)
	
	if sitelink then
		return "[[" .. sitelink .. "|" .. label .. "]]"
	else
		return label
	end
end

local function getExternalIdData(refSnaks, statedInId, refData, subjectQid)
	if not refSnaks then return nil end
	
	-- If no P248, try to infer statedInId from ID properties in the reference
	if not statedInId then
		for propId, snakList in pairs(refSnaks) do
			if propId ~= 'P304' and propId ~= 'P813' and propId ~= 'P854' and propId ~= 'P1810' then
				local propEntity = mw.wikibase.getEntity(propId)
				if propEntity and propEntity.claims and propEntity.claims['P9073'] then
					-- This ID property has P9073, use its first value as statedInId
					for _, claim in ipairs(propEntity.claims['P9073']) do
						if claim.mainsnak.snaktype == 'value' then
							statedInId = claim.mainsnak.datavalue.value.id
							break
						end
					end
					if statedInId then break end
				end
			end
		end
	end
	
	if not statedInId then return nil end	
	-- First pass: Check if any ID in refSnaks has P9073 match
	for propId, snakList in pairs(refSnaks) do
		if propId ~= 'P248' and propId ~= 'P304' and propId ~= 'P813' and propId ~= 'P854' and propId ~= 'P1810' then
			local propEntity = mw.wikibase.getEntity(propId)
			
			if propEntity and propEntity.claims and propEntity.claims['P9073'] then
				for _, claim in ipairs(propEntity.claims['P9073']) do
					if claim.mainsnak.snaktype == 'value' then
						local serviceId = claim.mainsnak.datavalue.value.id
						if serviceId == statedInId then
							-- Found a match with ID in reference!
							local result = {}
							
							for _, snak in ipairs(snakList) do
								if snak.snaktype == 'value' then
									local idValue = snak.datavalue.value
									
									if propEntity.claims['P1630'] and idValue then
										local formatterUrl = propEntity.claims['P1630'][1].mainsnak.datavalue.value
										result.url = formatterUrl:gsub("$1", idValue)
									end
									
									break
								end
							end
							
							-- Get title from P1810 or subject
							if refData['P1810'] then
								result.title = refData['P1810']
							elseif subjectQid then
								result.title = getLabel(subjectQid)
							end
							
							result.publisher = getLinkedLabel(statedInId)
							return result
						end
					end
				end
			end
		end
	end
	
	-- Second pass: No ID in reference, check if subject has matching ID
	if subjectQid then
		-- Find all properties with P9073 pointing to statedInId
		local candidateProps = {}
		
		-- We need to search for properties that have P9073 = statedInId
		-- This is expensive, so let's check the subject's claims for common ID properties
		local subjectEntity = mw.wikibase.getEntity(subjectQid)
		if subjectEntity and subjectEntity.claims then
			for propId, claims in pairs(subjectEntity.claims) do
				-- Check if this property has P9073 pointing to our statedInId
				local propEntity = mw.wikibase.getEntity(propId)
				if propEntity and propEntity.claims and propEntity.claims['P9073'] then
					for _, claim in ipairs(propEntity.claims['P9073']) do
						if claim.mainsnak.snaktype == 'value' then
							local serviceId = claim.mainsnak.datavalue.value.id
							if serviceId == statedInId then
								-- Found a matching property on the subject!
								local result = {}
								
								-- Get the ID value from subject's claims
								for _, subjectClaim in ipairs(claims) do
									if subjectClaim.mainsnak.snaktype == 'value' then
										local idValue = subjectClaim.mainsnak.datavalue.value
										
										-- Build URL
										if propEntity.claims['P1630'] and idValue then
											local formatterUrl = propEntity.claims['P1630'][1].mainsnak.datavalue.value
											result.url = formatterUrl:gsub("$1", idValue)
										end
										
										break
									end
								end
								
								-- Get title from P1810 or subject
								if refData['P1810'] then
									result.title = refData['P1810']
								else
									result.title = getLabel(subjectQid)
								end
								
								result.publisher = getLinkedLabel(statedInId)
								return result
							end
						end
					end
				end
			end
		end
	end
	
	return nil
end
local function formatDate(timeValue)
	if not timeValue then return nil end
	
	local sign, y, m, d = timeValue.time:match("^([%+%-]?)(%d+)-(%d+)-(%d+)T")
	if not y then return nil end
	
	local yearStr = (sign == '-') and ("-" .. y) or y
	
	if timeValue.precision <= 9 then
		return yearStr
	elseif timeValue.precision == 10 then
		local monthNum = tonumber(m)
		if monthNum and MONTHS[monthNum] then
			return MONTHS[monthNum] .. " " .. yearStr
		else
			return yearStr .. "-" .. m 
		end
	else
		if d == '00' then
			local monthNum = tonumber(m)
			if monthNum and MONTHS[monthNum] then
				return MONTHS[monthNum] .. " " .. yearStr
			else
				return yearStr .. "-" .. m
			end
		end
		return yearStr .. "-" .. m .. "-" .. d
	end
end

local function getPropValue(entity, propId, valueType)
	if not entity or not entity.claims or not entity.claims[propId] then return nil end
	
	for _, statement in ipairs(entity.claims[propId]) do
		if statement.mainsnak.snaktype == 'value' then
			local val = statement.mainsnak.datavalue.value
			
			if valueType == 'item' then return val.id
			elseif valueType == 'item_label' then return getLabel(val.id)
			elseif valueType == 'item_linked' then return getLinkedLabel(val.id)
			elseif valueType == 'item_sitelink' then return getSitelinkOrLabel(val.id)
			elseif valueType == 'item_code' then
				local langItem = mw.wikibase.getEntity(val.id)
				if langItem and langItem.claims and langItem.claims['P424'] then
					return langItem.claims['P424'][1].mainsnak.datavalue.value
				end
				return getLabel(val.id)
			elseif valueType == 'time' then return formatDate(val)
			elseif valueType == 'string' or valueType == 'url' or valueType == 'external-id' then return val
			elseif valueType == 'monolingualtext' then 
				-- Check language preference
				if val.language == 'ro' or val.language == 'mul' then
					return val.text
				end
				-- Continue to next statement if language doesn't match
			end
		end
	end
	
	-- If no ro/mul found, try other preferred languages
	if valueType == 'monolingualtext' then
		for _, lang in ipairs({'en', 'fr', 'de'}) do
			for _, statement in ipairs(entity.claims[propId]) do
				if statement.mainsnak.snaktype == 'value' then
					local val = statement.mainsnak.datavalue.value
					if val.language == lang then
						return val.text
					end
				end
			end
		end
	end
	
	return nil
end

local function getPeople(entity, propId, fallbackPropId, max)
	local results = {}
	if not entity or not entity.claims then return results end
	
	local count = 0
	local max_results = max or 10
	
	local function add(claims, isString)
	    if not claims then return end
	    for _, claim in ipairs(claims) do
	        if count >= max_results then break end
	        if claim.mainsnak.snaktype == 'value' then
	            local val = claim.mainsnak.datavalue.value
	            local person = { name = nil, link = nil }
	            
	            if isString then
	                person.name = val
	            else
	                local itemId
	                if type(val) == 'table' then
	                    itemId = val.id
	                else
	                    itemId = val
	                end
	                
	                if itemId then
	                    person.name = getLabel(itemId)
	                    person.link = mw.wikibase.getSitelink(itemId)
	                end
	            end
	            
	            -- Only add if we have a valid name
	            if person.name and type(person.name) == 'string' then
	                table.insert(results, person)
	                count = count + 1
	            end
	        end
	    end
	end
	
	add(entity.claims[propId], false) 
	if fallbackPropId and count < max_results then 
		add(entity.claims[fallbackPropId], true) 
	end

	return results
end

local function getFirstUrlFromProps(entity, urlProps)
    if not entity then return nil end
    
    for _, propConfig in ipairs(urlProps) do
        local val = getPropValue(entity, propConfig.id, propConfig.type)
        if val then return val end
    end
    return nil
end

local function parseRefSnaks(refSnaks)
	local data = {}
	if not refSnaks then return data end
	
	for propId, snakList in pairs(refSnaks) do
		for _, snak in ipairs(snakList) do
			if snak.snaktype == 'value' then
				local val = snak.datavalue.value
				if snak.datatype == 'wikibase-item' then data[propId] = val.id
				elseif snak.datatype == 'time' then data[propId] = formatDate(val)
				elseif snak.datatype == 'monolingualtext' then data[propId] = val.text
				elseif snak.datatype == 'string' or snak.datatype == 'url' or snak.datatype == 'external-id' then data[propId] = val
				end
				break 
			end
		end
	end
	return data
end

-- =============================================================================
-- MAIN RESOLVER
-- =============================================================================

local function resolveCitationData(args, refSnaks, itemQid, subjectQid)
    local manual = args or {}
    if manual['p'] then manual['pages'] = manual['p'] end
    if manual['pp'] then manual['pages'] = manual['pp'] end
    
    local refData = parseRefSnaks(refSnaks)
    local statedInId = refData['P248']
    local workId = itemQid
    
    local statedInItem = statedInId and mw.wikibase.getEntity(statedInId) or nil
    local workItem = workId and mw.wikibase.getEntity(workId) or nil
    
    -- Use the proper getExternalIdData function (defined outside this function)
    local externalIdData = getExternalIdData(refSnaks, statedInId, refData, subjectQid)
    
    -- Standard Metadata Logic
    local p629 = nil
    if workItem then p629 = getPropValue(workItem, 'P629', 'item') end
    if not p629 and statedInItem then p629 = getPropValue(statedInItem, 'P629', 'item') end
    local parentItem = p629 and mw.wikibase.getEntity(p629) or nil

    local journalId = nil
    if workItem then journalId = getPropValue(workItem, 'P1433', 'item') end
    if not journalId and parentItem then journalId = getPropValue(parentItem, 'P1433', 'item') end
    local journalItem = journalId and mw.wikibase.getEntity(journalId) or nil

    local function getValue(key, type_override)
        local config = PROP_MAP[key]
        if not config then return manual[key] end
        
        local propId = config.id
        local vType = type_override or config.type
        
        if manual[key] then return manual[key] end
        
        -- Priority: Reference > Work > Journal > StatedIn > Parent
        if refData[propId] then
            local val = refData[propId]
            if vType == 'item_label' and val:match("^Q%d+") then return getLabel(val) end
            if vType == 'item_linked' and val:match("^Q%d+") then return getLinkedLabel(val) end
            if vType == 'item_sitelink' and val:match("^Q%d+") then return getSitelinkOrLabel(val) end
            if vType == 'item_code' and val:match("^Q%d+") then
                local langItem = mw.wikibase.getEntity(val)
                if langItem and langItem.claims and langItem.claims['P424'] then
                    return langItem.claims['P424'][1].mainsnak.datavalue.value
                end
                return getLabel(val)
            end
            return val
        end
        
        local function check(e) return e and getPropValue(e, propId, vType) end
        return check(workItem) or check(journalItem) or check(statedInItem) or check(parentItem)
    end

    local c = {}
    for key, _ in pairs(PROP_MAP) do
        c[key] = getValue(key)
    end

    -- Apply external ID data
    if externalIdData then
        if externalIdData.publisher and not manual['publisher'] then 
            c.publisher = externalIdData.publisher 
        end
        if externalIdData.title and not manual['title'] then 
            c.title = externalIdData.title 
        end
    end
    
    if not c.title then c.title = getValue('at') end
    if not c.title and parentItem then c.title = getPropValue(parentItem, 'P1476', 'monolingualtext') end
    if not c.title and workItem then c.title = getLabel(workItem.id) end
    if not c.title and statedInItem then c.title = getLabel(statedInItem.id) end
    
    if not externalIdData then
        local subtitle = getValue('subtitle')
        if c.title and subtitle then c.title = c.title .. ": " .. subtitle end
    end

    -- URL Construction
    local function getUrl()
        if manual['url'] then return manual['url'] end
        if externalIdData and externalIdData.url then return externalIdData.url end
        
        for _, propConfig in ipairs(URL_PROPS) do
            if refData[propConfig.id] then return refData[propConfig.id] end
        end
        
        local function checkUrl(e) return e and getFirstUrlFromProps(e, URL_PROPS) end
        return checkUrl(statedInItem) or checkUrl(workItem) or checkUrl(journalItem) or checkUrl(parentItem)
    end
    c.url = getUrl()

    -- Authors
    local function resolvePeople(typeKey, propConfig)
        if manual[typeKey] then return { { name = manual[typeKey], link = nil } } end
        
        if refData[propConfig.id] then
            local qid = refData[propConfig.id]
            if qid and type(qid) == 'string' and qid:match("^Q%d+") then
                local name = getLabel(qid)
                if name then return { { name = name, link = mw.wikibase.getSitelink(qid) } } end
            end
        end
        if propConfig.fallback and refData[propConfig.fallback] then
             return { { name = refData[propConfig.fallback], link = nil } }
        end
        
        local function check(e) return e and getPeople(e, propConfig.id, propConfig.fallback, 10) end
        local res = check(workItem) or check(parentItem) or check(statedInItem) or check(journalItem)
        return res or {}
    end

    c.authors = resolvePeople('author', CONTRIB_MAP.author)
    c.editors = resolvePeople('editor', CONTRIB_MAP.editor)
    c.translators = resolvePeople('translator', CONTRIB_MAP.translator)
    
    local others = {}
    for key, config in pairs(CONTRIB_MAP) do
        if key ~= 'author' and key ~= 'editor' and key ~= 'translator' then
            local list = resolvePeople(key, config)
            if #list > 0 then
                local names = {}
                for _, person in ipairs(list) do table.insert(names, person.name) end
                table.insert(others, config.label .. ": " .. table.concat(names, ", "))
            end
        end
    end
    if #others > 0 then c.others = table.concat(others, "; ") end

    if workItem then c.qid = workItem.id
    elseif statedInItem then c.qid = statedInItem.id
    end

    -- =========================================================================
    -- FINAL FALLBACK: STANDALONE EXTERNAL IDS (Fixes P1047 / veralz)
    -- =========================================================================
    if (not c.title or not c.url) and refSnaks then
        for propId, snakList in pairs(refSnaks) do
            if propId ~= 'P248' and propId ~= 'P304' and propId ~= 'P813' and propId ~= 'P577' then
                local snak = snakList[1]
                if snak and snak.snaktype == 'value' and snak.datavalue then
                    local dtype = snak.datatype
                    local val = snak.datavalue.value
                    
                    if dtype == 'external-id' or dtype == 'string' then
                        local success, propEntity = pcall(mw.wikibase.getEntity, propId)
                        if success and propEntity then
                            -- Generate URL
                            if not c.url and propEntity.claims and propEntity.claims['P1630'] then
                                for _, claim in ipairs(propEntity.claims['P1630']) do
                                    if claim.mainsnak.snaktype == 'value' then
                                        local fmt = claim.mainsnak.datavalue.value
                                        -- FIX: Escape the $ symbol for Lua patterns
                                        c.url = fmt:gsub("%$1", val) 
                                        break
                                    end
                                end
                            end
                            -- Generate Title
                            if not c.title then
                                local label = getLabel(propId) or propId
                                c.title = label .. ": " .. val
                            end
                            if c.title and c.url then break end
                        end
                    end
                end
            end
        end
    end

    return c
end

-- =============================================================================
-- OUTPUT BUILDER
-- =============================================================================

local function buildTemplate(data)
	local template = "Citation"
	
	if data.isbn or data.isbn10 then
		template = "Citat carte"
	elseif data.issn or data.doi or (data.journal and (data.volume or data.issue)) then
		template = "Citat revistă"
	elseif data.url and not data.journal then
		template = "Citat web"
	end
	
	local mapping = {
		{ 'title', 'titlu' },
		{ 'publisher', 'editură' },
		{ 'place', 'loc' },
		{ 'pubDate', 'data' },
		{ 'accessDate', 'access-date' },
		{ 'series', 'serie' },
		{ 'volume', 'volum' },
		{ 'issue', 'număr' },
		{ 'pages', 'pagini' },
		{ 'edition', 'ediție' },
		{ 'language', 'limba' },
		{ 'format', 'format' },
		{ 'chapter', 'capitol' },
		{ 'others', 'alții' },
		
		{ 'doi', 'doi' },
		{ 'isbn', 'isbn' },
		{ 'isbn10', 'isbn' },
		{ 'issn', 'issn' },
		{ 'oclc', 'oclc' },
		{ 'pmid', 'pmid' },
		{ 'pmc', 'pmc' },
		{ 'arxiv', 'arxiv' },
		{ 'bibcode', 'bibcode' },
		{ 'jstor', 'jstor' },
		{ 'lccn', 'lccn' },
		{ 'ismn', 'ismn' },
		{ 'ol', 'ol' },
		
		{ 'url', 'url' },
		{ 'archiveurl', 'archive-url' },
		{ 'archivedate', 'archive-date' },
	}

	if template == "Citat web" then
		table.insert(mapping, { 'journal', 'website' })
	elseif template == "Citat revistă" then
		table.insert(mapping, { 'journal', 'revistă' })
	else
		table.insert(mapping, { 'journal', 'lucrare' })
	end

	local parts = { "{{" .. template }
	
	for i, person in ipairs(data.authors) do
	    
		local nameParam = (i == 1) and "autor" or ("autor" .. i)
		table.insert(parts, "| " .. nameParam .. " = " .. person.name)
		
		if person.link then
			local linkParam = (i == 1) and "author-link" or ("author-link" .. i)
			table.insert(parts, "| " .. linkParam .. " = " .. person.link)
		end
	end
	
	for i, person in ipairs(data.editors) do
    
		local nameParam = (i == 1) and "editor" or ("editor" .. i)
		table.insert(parts, "| " .. nameParam .. " = " .. person.name)
		
		if person.link then
			local linkParam = (i == 1) and "editor-link" or ("editor-link" .. i)
			table.insert(parts, "| " .. linkParam .. " = " .. person.link)
		end
	end
	
	for i, person in ipairs(data.translators) do
		table.insert(parts, "| nume-traducător" .. i .. " = " .. person.name)
		if person.link then
			table.insert(parts, "| translator-link" .. i .. " = " .. person.link)
		end
	end

	for _, map in ipairs(mapping) do
		local val = data[map[1]]
		if val and val ~= '' then
			table.insert(parts, "| " .. map[2] .. " = " .. val)
		end
	end
	
	if data.qid then
		local qidStr = "[[Wikidata]] [[d:" .. data.qid .. "|" .. data.qid .. "]]"
		table.insert(parts, "| id = " .. qidStr)
	end
	
	table.insert(parts, "}}")
	return table.concat(parts)
end

-- =============================================================================
-- PUBLIC INTERFACE
-- =============================================================================

function p.cite_q(frame)
	local args = {}
	
	if frame:getParent() then
		for k, v in pairs(frame:getParent().args) do
			if v ~= "" then args[k] = v end
		end
	end
	for k, v in pairs(frame.args) do
		if v ~= "" then args[k] = v end
	end

	local qid = args['q'] or args[1]
	
	if not qid or qid == "" then 
		return "Eroare: Lipsă QID" 
	end
	
	args['q'] = nil
	args[1] = nil
	
	local data = resolveCitationData(args, nil, qid)
	return frame:preprocess(buildTemplate(data))
end

function p.cite_ref(ref, args)
	if not ref or not ref.snaks then return "" end
	
	-- Extract subject QID from args if provided
	local subjectQid = nil
	if args then
		subjectQid = args['subject']
	end
	
	local data = resolveCitationData(args or {}, ref.snaks, nil, subjectQid)
	return buildTemplate(data)
end

function p.cite_item_lua(qid, args)
	local data = resolveCitationData(args or {}, nil, qid)
	return buildTemplate(data)
end

return p