Module:MathModDBHelperMethods

From MaRDI portal
Revision as of 11:01, 19 September 2025 by TA4Shehu (talk | contribs)

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

-- Module for executing SPARQL queries
local sparql = require('SPARQL')
-- Required module containing helper methods
local helper = require('Module:HelperMethods')
-- MediaWiki library for logging and utilities
local mw = require('mw')
local json = require("mw.text") 

-- Main table to hold all functions
local p = {}

-- Function to replace pattern in the comma pattern separated list 
function p.replace_pattern(frame)
    local input = frame.args[1] or ""
    local sep = frame.args[2] or " "  -- default to space if no separator given
    -- Replace comma+pattern (,xxxx) with sep
    local result = string.gsub(input, ",xxxx%s*", sep)
    return result
end

-- Function to generate a table listing a type of individuals
function p.getList(frame)
	
	local entityType = frame.args[1] or 'Model'
	local height = frame.args[2] or '400px' -- Default height if not specified
    local width = frame.args[3] or '800px' -- Default width if not specified
    local baseUrl = mw.site.server -- Get the current URL
    
    -- Define mapping of entity types to their corresponding Q IDs and URL prefixes
    local entityConfig = {
        ['Model'] = {qid = 'Q68663', urlPrefix = 'Model:', title = 'mathematical models'},
        ['Academic discipline'] = {qid = 'Q60231', urlPrefix = 'Academic_discipline:', title = 'academic disciplines'},
        ['Research problem'] = {qid = 'Q6534292', urlPrefix = 'Research_problem:', title = 'research problems'},
        ['Task'] = {qid = 'Q6534247', urlPrefix = 'Task:', title = 'computational tasks'},
        ['Quantity'] = {qid = 'Q6534237', urlPrefix = 'Quantity:', title = 'quantities'},
        ['Quantity kind'] = {qid = 'Q6534245', urlPrefix = 'Quantity:', title = 'quantity kind items'},
        ['Formula'] = {qid = 'Q6481152', urlPrefix = 'Formula:', title = 'mathematical expressions'}
    }
    
    -- Get configuration for the specified entity type
    local config = entityConfig[entityType]
    if not config then
        return "Invalid entity type. Valid options are: Model, Academic discipline, Research problem, Task, Quantity, Quantity kind, Formula"
    end

    -- Q6534265 refers to MathModDB community
    local sparqlQuery = [[
	SELECT ?itemLabel ?modelURL
    WHERE {
      ?item wdt:P31 wd:]] .. config.qid .. [[;
            wdt:P1495 wd:Q6534265 .
      ?item rdfs:label ?itemLabel .
      FILTER(LANG(?itemLabel) = "en")
      BIND(REPLACE(STR(?item), "^.*/Q", "]] .. baseUrl .. [[/wiki/]] .. config.urlPrefix .. [[") AS ?modelURL)
    }
    ORDER BY LCASE(?itemLabel)
    ]]

    local jsonResults = sparql.runQuery(sparqlQuery)

	-- Handle error in SPARQL query execution
	if jsonResults and jsonResults.error then
    	mw.log("Error in SPARQL query: " .. tostring(jsonResults.error))
    	return nil
	end

	if not jsonResults then
        return "Could not fetch data."
	end

	if helper.countElementsInBindings(jsonResults.results.bindings) == 0 then
        return "No records found."
	end
	
	-- Convert the JSON results into a Lua table
    local fieldOrder = {"modelURL", "itemLabel"}
	local dataTable = helper.convertJsonToTableOrdered(jsonResults, fieldOrder)
	
	-- Create and return HTML table from the data
    local headers = {entityType}
    local htmlTable =  helper.createHtmlTableWithMergedCols(dataTable, headers, {{1, 2}})
	
	-- Create a parent container for the table
	local parentContainer = mw.html.create('div')
    	:addClass('parent-container')
    	:css('width', width)

	local heading = mw.html.create('h2')
    	:wikitext('List of ' .. config.title)

    -- Add the table and chart to the parent container
	parentContainer
	    :node(heading)
    	:node(htmlTable)

    return tostring(parentContainer)
end


-- Function to get linked items
function p.getLinkedItems(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>

SELECT DISTINCT ?itemLabel ?item ?propertyLabel ?property 
WHERE {
  # Replace with your specific entity
  ?itemInter ?ps entityId: .

  # Convert property URL to entity-style URL
 BIND(
    IRI(REPLACE(STR(?ps),
                 "^https://portal.mardi4nfdi.de/prop/(direct|qualifier|statement)/",
                 "https://portal.mardi4nfdi.de/entity/"))
    AS ?property
 )
 # Skip unwanted property
 FILTER(?property != <http://schema.org/about>)

  # Clean URL: keep only QID, remove /statement/ and trailing UUID
  BIND(
    IRI(
      REPLACE(
        STR(?itemInter),
        ".*/(Q[0-9]+).*",
        "https://portal.mardi4nfdi.de/entity/$1"
      )
    ) AS ?item
  )

  # Get labels
  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "en". 
    ?item rdfs:label ?itemLabel .
    ?property rdfs:label ?propertyLabel .
  }
}
ORDER BY LCASE(?itemLabel)

]]


	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return " "
    end
	
	local linkedItems = {}
	local totalLinkedItems = #jsonResults.results.bindings

	if totalLinkedItems >= 0 then
    	-- Loop through the bindings 
    	for index = 0, totalLinkedItems do
        	local item = jsonResults.results.bindings[index]
        	
        	if not item then
        		return ""
    		end

        	if not item.itemLabel or not item.itemLabel.value then
        		return ""
        	elseif not item.item or not item.item.value then
            	return ""
        	else
            	local itemLabel = item.itemLabel.value
            	local itemURL = item.item.value
            	local propertyLabel = item.propertyLabel and item.propertyLabel.value or ""
            	local propertyURL = item.property and item.property.value or ""

            	local itemWithURL = string.format('[%s %s]', tostring(itemURL), tostring(itemLabel))
            	local propertyWithURL = string.format('[%s %s]', tostring(propertyURL), tostring(propertyLabel))

            	table.insert(linkedItems, '| ' .. itemWithURL .. ' || ' .. propertyWithURL)
        	end
    	end
    	-- if there are linked items 
    	-- Construct the Wikitext table
		local wikitextTable = "{| class='wikitable'\n" ..
    	"! Item\n! Property\n" ..          -- header row
    	"|-\n" ..                          -- separator before first data row
    	table.concat(linkedItems, "\n|-\n") ..
    	"\n|}"
		return wikitextTable
	else
		return ""
	end

end

-- Function to get subclass of items
function p.getSubclassOf(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P36: subclass of property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label  
	WHERE {
		entityId: wdt:P36 ?URL.
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized academic discipline found"
    end
	
	local subClasses = {}
    -- Get the number of specialized academic disciplines
	local totalSubClasses = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalSubClasses  do
    	local item = jsonResults.results.bindings[index]
    	if not item then
        		return ""
    	end
    	if not item.Label.value then
        	return ""
    	elseif not item.URL.value then
        	return ""
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Academic_discipline:" .. numericId
        	local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
        	table.insert(subClasses, "| " .. labelWithUrl)
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(subClasses, "\n|-\n") .. "\n|}"
	return wikitextTable
end	


-- Function to get subclass of items
function p.getDescribedBySource(frame)
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Constructing the SPARQL query with dynamic entity entityId
    -- P286: described by source property id
    local sparqlQuery = [[
PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label  
	WHERE {
		entityId: wdt:P286 ?URL.
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized academic discipline found"
    end
	
	local describedBySources = {}
    -- Get the number of specialized academic disciplines
	local totalSources = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totalSources  do
    	local item = jsonResults.results.bindings[index]
    	if not item then
        		return ""
    	end
    	if not item.Label.value then
        	return ""
    	elseif not item.URL.value then
        	return ""
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Academic_discipline:" .. numericId
        	local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
        	table.insert(describedBySources, "| " .. labelWithUrl)
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(describedBySources, "\n|-\n") .. "\n|}"
	return wikitextTable
end	

function p.getImageWithLegend(frame)
	-- Property ID for the image
    local pidImage  = "P356" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local imageClaims = entity.claims[pidImage]
    local imageStrings = {}
	if imageClaims and type(imageClaims) == "table" then
    	local numClaims = #imageClaims
    	-- Loop through the image claims
		for index = 1, numClaims  do
			-- Extract the image filename
    		local imageFilename = imageClaims[index].mainsnak.datavalue.value
			local imageLegend = imageClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
			-- Add this image line to the list
        	 table.insert(imageStrings, string.format("[[File:%s|thumb|380px|%s]]", imageFilename, imageLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(imageStrings, '&nbsp;')  
    end
		end    
		-- Combine all image cells into one row of a wikitable
		local imageTable = '{| class="wikitable"\n| ' .. table.concat(imageStrings, ' || ') .. '\n|}'
		return imageTable .. "\n\n"
	else
    	return " " 
	end

end


function p.getVideoWithLegend(frame)
	-- Property ID for the video
    local pidVideo  = "P797" 
    -- Property ID for the (qualifier) media legend
    local pidMediaLegend = "P401"   
    local defaultLegend = "No legend available."

   	
	local entityId = frame.args[1]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return "Error: No entity ID provided"
    end
    
    -- Attempt to retrieve entity data
    local entity = mw.wikibase.getEntity(entityId)
    local videoClaims = entity.claims[pidVideo]
    local videoStrings = {}
	if videoClaims and type(videoClaims) == "table" then
    	local numClaims = #videoClaims
    	-- Loop through the image claims
		for index = 1, numClaims  do
			-- Extract the image filename
    		local videoFilename = videoClaims[index].mainsnak.datavalue.value
			local videoLegend = videoClaims[index].qualifiers[pidMediaLegend][1].datavalue.value.text
			-- Add this image line to the list
        	 table.insert(videoStrings, string.format("[[File:%s|thumb|340px|%s]]", videoFilename, videoLegend))
        
        	if index < numClaims then
        		-- spacer column
        		table.insert(videoStrings, '&nbsp;')  
    end
		end    
		-- Combine all image cells into one row of a wikitable
		local videoTable = '{| class="wikitable"\n| ' .. table.concat(videoStrings, ' || ') .. '\n|}'
		return videoTable .. "\n\n"
	else
    	return " " 
	end

end

-- Function to get list with qualifier values only
function p.getListWithQualifierValues(frame)
	local entityId = frame.args[1]
    local propertyId = frame.args[2]
    
    -- Validate input parameter
    if not entityId or entityId == '' then
        return " "
    end
   
    -- Constructing the SPARQL query with dynamic entity entityId
    local sparqlQuery = [[
	PREFIX entityId: <https://portal.mardi4nfdi.de/entity/]] .. entityId .. [[>
	SELECT ?URL ?Label ?qualPropEntity ?qualPropLabel ?qualValue ?qualValueLabel
	WHERE {
		entityId: p:]] .. propertyId .. [[  ?statement .
		?statement ps:]] .. propertyId .. [[ ?URL .
		?URL rdfs:label ?Label
		FILTER(LANG(?Label) = "en")

		OPTIONAL {
    		?statement ?qualProp ?qualValue .
    		FILTER(STRSTARTS(STR(?qualProp), STR(pq:)))   # only qualifiers

	    	# Map qualifier property to its property entity
    		BIND(IRI(REPLACE(STR(?qualProp), "prop/qualifier", "entity")) AS ?qualPropEntity)

	    	OPTIONAL {
    			?qualPropEntity rdfs:label ?qualPropLabel .
    			FILTER(LANG(?qualPropLabel) = "en")
    		}

    		OPTIONAL {
    			?qualValue rdfs:label ?qualValueLabel .
    			FILTER(LANG(?qualValueLabel) = "en")
    		}
		}
	}
	ORDER BY ?Label
	]]

	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	-- Validate results
	if not jsonResults or not jsonResults.results or not jsonResults.results.bindings then
       	return "No specialized research fields found"
    end
	
	local listItems = {}
    -- Get the number of specialized research fields
	local totallistItems = #jsonResults.results.bindings
	-- Loop through the bindings
	for index = 0, totallistItems do
    	local item = jsonResults.results.bindings[index]
    	if not item.Label.value then
        	return "Error: Missing item.Label.value"
    	elseif not item.URL.value then
        	return "Error: Missing item.URL.value"
    	else
    		local label = item.Label.value
        	local url = item.URL.value
        	local numericId = url:match("Q(%d+)")
        	local urlRendered = "https://portal.mardi4nfdi.de/wiki/Model:" .. numericId
        	local qualValue = item.qualValueLabel or item.qualValue or ""
	        local labelWithUrl = string.format('[%s %s]', tostring(url), tostring(label))
	        if qualValue ~= "" then
	       		local qualValueWithUrl = string.format('[%s %s]', tostring(item.qualValue.value), tostring(item.qualValueLabel.value))
	        	local row = "| " .. labelWithUrl ..  " || (" ..  qualValueWithUrl .. ")"
    			table.insert(listItems, row)
	        else
        		table.insert(listItems, "| " .. labelWithUrl)
        	end
    	end
	end

	-- Construct the Wikitext table
	local wikitextTable = "{| class='wikitable'\n" .. table.concat(listItems, "\n|-\n") .. "\n|}"
	return wikitextTable
end

return p