Module:ConceptRelations
Jump to navigation
Jump to search
Documentation for this module may be created at Module:ConceptRelations/doc
--------------------------------------------------------------------------------
-- Module:ConceptRelations (Enhanced Version 4.0)
-- Handles rendering of schools, contributors, and relationships for Infobox_concept
-- Includes Cargo storage integration and semantic validation
--------------------------------------------------------------------------------
local p = {}
local cargo = mw.ext.cargo
-- Mapping of contribution types to CSS classes
local contributionTypes = {
["originator"] = "originator",
["reinterpretation"] = "reinterpretation",
["elaboration"] = "elaboration",
["systematic elaboration"] = "systematic-elaboration",
["systematic-elaboration"] = "systematic-elaboration",
["clinical extension"] = "clinical-extension",
["clinical-extension"] = "clinical-extension",
["clinical"] = "clinical",
["interdisciplinary application"] = "interdisciplinary-application",
["interdisciplinary-application"] = "interdisciplinary-application",
["interdisciplinary"] = "interdisciplinary",
["critique"] = "critique",
["influence"] = "elaboration"
}
-- Mapping of relationship types to CSS classes
local relationTypes = {
["foundational"] = "foundational",
["foundational dependency"] = "foundational-dependency",
["foundational-dependency"] = "foundational-dependency",
["prerequisite"] = "prerequisite",
["elaborates"] = "elaborates",
["extension"] = "extension",
["elaborates-refines"] = "elaborates-refines",
["elaborates/refines"] = "elaborates-refines",
["clinical manifestation"] = "clinical-manifestation",
["clinical-manifestation"] = "clinical-manifestation",
["clinical application"] = "clinical-application",
["clinical-application"] = "clinical-application",
["clinical"] = "clinical",
["opposition"] = "opposition",
["theoretical opposition"] = "theoretical-opposition",
["theoretical-opposition"] = "theoretical-opposition",
["contradicts"] = "contradicts",
["supersedes"] = "supersedes",
["temporal-sequence"] = "temporal-sequence",
["mutual-constitution"] = "mutual-constitution",
["clinical-alternative"] = "clinical-alternative",
["translational-equivalent"] = "translational-equivalent",
["interdisciplinary"] = "interdisciplinary",
["interdisciplinary parallel"] = "interdisciplinary-parallel",
["interdisciplinary-parallel"] = "interdisciplinary-parallel",
["interdisciplinary influence"] = "interdisciplinary-influence",
["interdisciplinary-influence"] = "interdisciplinary-influence",
["translation"] = "translation",
["school translation"] = "school-translation",
["school-translation"] = "school-translation",
["lacanian translation"] = "lacanian-translation",
["lacanian-translation"] = "lacanian-translation",
["theoretical evolution"] = "theoretical-evolution",
["theoretical-evolution"] = "theoretical-evolution"
}
-- School names and their CSS classes
local schoolClasses = {
["freudian"] = "freudian",
["lacanian"] = "lacanian",
["kleinian"] = "kleinian",
["laplanchian"] = "laplanchian",
["ego psychology"] = "ego-psychology",
["ego-psychology"] = "ego-psychology",
["object relations"] = "object-relations",
["object-relations"] = "object-relations",
["relational"] = "relational",
["contemporary"] = "contemporary",
["trauma theory"] = "contemporary",
["trauma-theory"] = "contemporary",
["self psychology"] = "contemporary",
["self-psychology"] = "contemporary",
["intersubjective"] = "contemporary",
["neuropsychoanalytic"] = "contemporary"
}
--------------------------------------------------------------------------------
-- RENDER SCHOOL INTERPRETATIONS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.renderSchools(frame)
local args = frame:getParent().args
local output = {}
local schoolData = {}
-- Check if any school views exist
local hasSchools = false
for k, v in pairs(args) do
if k:match("^%a+_view$") and v ~= '' then
hasSchools = true
break
end
end
if not hasSchools then
return ''
end
table.insert(output, '<div class="concept-section-header mw-collapsible' ..
(args.schools_collapsed == 'yes' and ' mw-collapsed' or '') ..
'">School Interpretations</div>')
table.insert(output, '<div class="mw-collapsible-content">')
-- Define school order
local schools = {
'freudian', 'lacanian', 'kleinian', 'laplanchian',
'ego_psychology', 'object_relations', 'relational',
'self_psychology', 'intersubjective', 'trauma_theory',
'neuropsychoanalytic', 'feminist'
}
local conceptName = args.concept_name or mw.title.getCurrentTitle().text
local order = 1
for _, school in ipairs(schools) do
local view = args[school .. '_view']
local keyTexts = args[school .. '_key_texts']
local keyTerms = args[school .. '_key_terms']
if view and view ~= '' then
local schoolName = school:gsub('_', ' '):gsub("(%a)([%w_']*)",
function(first, rest) return first:upper() .. rest end)
local cssClass = schoolClasses[school:lower()] or 'contemporary'
-- Store to Cargo
table.insert(schoolData, {
concept_name = conceptName,
school_name = schoolName,
school_view = view,
key_texts = keyTexts or '',
key_terms = keyTerms or '',
view_order = order
})
-- Render school box
table.insert(output, '<div class="school-box ' .. cssClass .. '">')
table.insert(output, '<div class="school-header">' .. schoolName .. '</div>')
table.insert(output, '<div class="school-content">' .. view .. '</div>')
if keyTexts and keyTexts ~= '' then
table.insert(output, '<div class="school-texts"><strong>Key texts:</strong> ' ..
keyTexts .. '</div>')
end
if keyTerms and keyTerms ~= '' then
table.insert(output, '<div class="school-terms"><strong>Related:</strong> ' ..
keyTerms .. '</div>')
end
table.insert(output, '</div>')
order = order + 1
end
end
table.insert(output, '</div>')
-- Store all school data to Cargo
if #schoolData > 0 then
for _, data in ipairs(schoolData) do
cargo.store('Concept_Schools', data)
end
end
return table.concat(output, '\n')
end
--------------------------------------------------------------------------------
-- RENDER CONTRIBUTOR BLOCKS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.contributors(frame)
local args = frame:getParent().args
local output = {}
local contributorData = {}
local conceptName = args.concept_name or mw.title.getCurrentTitle().text
for i = 1, 30 do
local name = args['contributor_' .. i .. '_name']
local year = args['contributor_' .. i .. '_year']
local ctype = args['contributor_' .. i .. '_type']
local content = args['contributor_' .. i .. '_content']
if name and name ~= '' then
local typeLower = (ctype or ''):lower()
local cssClass = contributionTypes[typeLower] or 'elaboration'
-- Store to Cargo
table.insert(contributorData, {
concept_name = conceptName,
contributor_name = name,
contribution_year = year or '',
contribution_type = ctype or '',
contribution_content = content or '',
contribution_order = i
})
-- Render contributor block
table.insert(output, '<div class="contributor-block ' .. cssClass .. '">')
-- Name with auto-linking
local linkedName = name
if not name:match('%[%[') then
linkedName = '[[' .. name .. ']]'
end
table.insert(output, '<div class="contributor-name">' .. linkedName)
-- Type tag
if ctype and ctype ~= '' then
table.insert(output, '<span class="contributor-type-tag">' .. ctype .. '</span>')
end
table.insert(output, '</div>')
-- Year
if year and year ~= '' then
table.insert(output, '<div class="contributor-year">' .. year .. '</div>')
end
-- Content
if content and content ~= '' then
table.insert(output, '<div class="contributor-content">' .. content .. '</div>')
end
table.insert(output, '</div>')
end
end
-- Store all contributor data to Cargo
if #contributorData > 0 then
for _, data in ipairs(contributorData) do
cargo.store('Concept_Contributors', data)
end
end
return table.concat(output, '\n')
end
--------------------------------------------------------------------------------
-- RENDER RELATIONSHIP BLOCKS WITH CARGO STORAGE
--------------------------------------------------------------------------------
function p.relations(frame)
local args = frame:getParent().args
local output = {}
local relationData = {}
local conceptName = args.concept_name or mw.title.getCurrentTitle().text
for i = 1, 50 do
local target = args['relation_' .. i .. '_target']
local rtype = args['relation_' .. i .. '_type']
local content = args['relation_' .. i .. '_content']
if target and target ~= '' then
local typeLower = (rtype or ''):lower()
local cssClass = relationTypes[typeLower] or 'foundational'
-- Determine if bidirectional
local bidirectional = false
if typeLower:match('mutual') or typeLower:match('parallel') then
bidirectional = true
end
-- Store to Cargo
table.insert(relationData, {
source_concept = conceptName,
target_concept = target,
relation_type = rtype or '',
relation_content = content or '',
relation_order = i,
bidirectional = bidirectional
})
-- Render relation block
table.insert(output, '<div class="relation-block ' .. cssClass .. '">')
-- Target with auto-linking
local linkedTarget = target
if not target:match('%[%[') then
linkedTarget = '[[' .. target .. ']]'
end
table.insert(output, '<div class="relation-target">' .. linkedTarget)
-- Type tag
if rtype and rtype ~= '' then
table.insert(output, '<span class="relation-type-tag">' .. rtype .. '</span>')
end
table.insert(output, '</div>')
-- Content
if content and content ~= '' then
table.insert(output, '<div class="relation-content">' .. content .. '</div>')
end
table.insert(output, '</div>')
end
end
-- Store all relation data to Cargo
if #relationData > 0 then
for _, data in ipairs(relationData) do
cargo.store('Concept_Relations', data)
end
end
return table.concat(output, '\n')
end
--------------------------------------------------------------------------------
-- VALIDATION: Check for reciprocal relationships
--------------------------------------------------------------------------------
function p.checkReciprocal(sourceConcept, targetConcept, relationType)
-- Query Cargo to see if reverse relationship exists
local tables = 'Concept_Relations'
local fields = 'source_concept, relation_type'
local where = 'source_concept="' .. targetConcept .. '" AND target_concept="' .. sourceConcept .. '"'
local results = cargo.query(tables, fields, {where=where})
if #results > 0 then
return true, results[1].relation_type
else
return false, nil
end
end
--------------------------------------------------------------------------------
-- UTILITY: Get all concepts related to current concept
--------------------------------------------------------------------------------
function p.getRelatedConcepts(conceptName)
local tables = 'Concept_Relations'
local fields = 'target_concept, relation_type'
local where = 'source_concept="' .. conceptName .. '"'
local orderBy = 'relation_order'
local results = cargo.query(tables, fields, {where=where, orderBy=orderBy})
return results
end
--------------------------------------------------------------------------------
-- UTILITY: Get all contributors to a concept
--------------------------------------------------------------------------------
function p.getContributors(conceptName, contributionType)
local tables = 'Concept_Contributors'
local fields = 'contributor_name, contribution_year, contribution_type'
local where = 'concept_name="' .. conceptName .. '"'
if contributionType then
where = where .. ' AND contribution_type="' .. contributionType .. '"'
end
local orderBy = 'contribution_order'
local results = cargo.query(tables, fields, {where=where, orderBy=orderBy})
return results
end
return p