Module:HelperMethods: Difference between revisions

From MaRDI portal
No edit summary
No edit summary
Line 345: Line 345:
if not jsonResults then
if not jsonResults then
         return "Could not fetch data."
         return "Could not fetch data."
end
if not jsonResults.results then
        return "Could not fetch any data."
end
end

Revision as of 14:41, 15 April 2025

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

------------------------------------------------------------------------------------
--                                 HelperMethods                                  --
--                                                                                --
-- This module includes a number of helper functions for dealing with lists,      --
-- e.g. in the person template. It is a meta-module, meant to be called from      --
-- other Lua modules, and should not be called directly from #invoke.             --
------------------------------------------------------------------------------------


local M = {}

-- Required modules for SPARQL queries and HTML table generation
local sparql = require('SPARQL')
local mwHtml = require('mw.html')

-- Utility function to trim and lowercase a string
function M.trimAndLower(str)
	if str == nil then return nil end
	str = str:gsub("^%s*(.-)%s*$", "%1")
	return str:lower()
end

-- Utility function to count number of results in JSON answer of the SPQARL query
function M.countElementsInBindings(bindings)
	if not bindings then return 0 end
	local count = 0
	while bindings[count] do
	    count = count + 1
	end
	return count
end


-- This function will replace spaces with + and encode other non-alphanumeric 
-- characters into their percent-encoded representations, making the string 
-- safe to use in a URL.
function M.urlencode(str)
    if str then
        str = string.gsub(str, "\n", "\r\n")
        str = string.gsub(str, "([^%w %-%_%.%~])",
            function (c) return string.format("%%%02X", string.byte(c)) end)
        str = string.gsub(str, " ", "+")
    end
    return str
end


-- Function to convert JSON results into a Lua table
function M.convertJsonToTable(jsonResults)
    local resultsTable = {}
    if jsonResults and jsonResults.results and jsonResults.results.bindings then
        local bindings = jsonResults.results.bindings
        for j = 0, #bindings do
            local row = {}
            for key, value in pairs(bindings[j]) do
                table.insert(row, value.value)
            end
            table.insert(resultsTable, row)
        end
    end
    return resultsTable
end

-- Function to convert JSON results into a Lua table with specified field order
function M.convertJsonToTableOrdered(jsonResults, fieldOrder)
    local resultsTable = {}

    if jsonResults and jsonResults.results and jsonResults.results.bindings then
        local bindings = jsonResults.results.bindings
        -- Iterate through each result row in the bindings
        for j = 0, #bindings do
            local row = {}
            for _, fieldName in ipairs(fieldOrder) do
                -- Extract the value from the current binding
                local value = bindings[j][fieldName]
                if value and value.value then
                    table.insert(row, value.value)
                else
                    -- Use " " for missing or empty fields
                    table.insert(row, ' ')
                end
            end
            table.insert(resultsTable, row)
        end
    end

    return resultsTable
end

-- Function to convert JSON results into a comma-separated string
-- E.g. from a SPQRQL query: local jsonResults = sparql.runQuery(sparqlQuery)
-- Expected: "valueLabel" (name or description) and "value" (URL) 
function M.convertJsonToCommaSeparatedList(jsonResults)
	local resultsString = ""
	
	if jsonResults and jsonResults.results and jsonResults.results.bindings then
        local bindings = jsonResults.results.bindings
        for i = 0, #bindings do
            local binding = bindings[i]
            if binding.valueLabel and binding.valueLabel.value then
                if resultsString ~= "" then
                    resultsString = resultsString .. ", "
                end
                
                -- get value
                local value = binding.value.value

                -- get label
                local label = binding.valueLabel.value
                if string.find(label, "https://") then
                	label = "N/A"
                end
                
                local nameAndLink = "[" .. value .. " " .. label .. "]"

                resultsString = resultsString .. nameAndLink
            end
        end
    end

    return resultsString
end


-- Additional function to generate the histogram data
-- colWithYear: Contains the index of the column that contains the year information. Expected format "2023-12-30"
function M.generateHistogramChartFromTable(dataTable, colWithYear)
    local yearCounts = {}

    for _, row in ipairs(dataTable) do
        -- Extract the year from the fourth column (publication date)
        local date = row[colWithYear]
        if date then  -- Check if the date exists
            local year = date:sub(1, 4)  -- Extract the year

            if year ~= "" then
                yearCounts[year] = (yearCounts[year] or 0) + 1
            end
        end
    end

    local years = {}
    local counts = {}
    for year, count in pairs(yearCounts) do
        table.insert(years, year)
        table.insert(counts, count)
    end

    -- Sort the years to maintain chronological order
    table.sort(years)
    local sortedCounts = {}
    for _, year in ipairs(years) do
        table.insert(sortedCounts, yearCounts[year])
    end

    local chartData = {
        type = 'bar',
        data = {
            labels = years,  -- x-axis labels (years)
            datasets = {{
                label = 'Number of Publications',
                data = sortedCounts,  -- y-axis data (counts)
                backgroundColor = 'rgba(54, 162, 235, 0.2)',
                borderColor = 'rgba(54, 162, 235, 1)',
                hoverBackgroundColor = 'red',
                borderWidth = 1
            }}
        },
        options = {
            scales = {
                y = {
                    beginAtZero = true
                }
            }
        }
    }

    return chartData
end


-- Function to create a HTML table from a Lua table where columns are merged
-- mergeColumns: contains a list of which columns should be merged, e.g. {{1, 2}, {4}} or {{2, 4}, {1, 3}}
function M.createHtmlTableWithMergedCols(dataTable, headers, mergeColumns, itemprop)
    local htmlTable = mwHtml.create('table')
    htmlTable:addClass('wikitable'):attr('border', '1')
    
    htmlTable:addClass('sortable') -- This line ensures your table has the 'sortable' class

    local headerRow = htmlTable:tag('tr')
    
    -- Use the provided headers
    for _, header in ipairs(headers) do
        headerRow:tag('th'):wikitext(header)
    end

    for _, row in ipairs(dataTable) do
	    if not string.find(row[1], "/entity/statement/") then
	        local dataRow = htmlTable:tag('tr')
	        
	        for _, cols in ipairs(mergeColumns) do
	            local combinedData
	            if #cols == 1 then
	                -- If only one column index is provided, use it as is, default to "N/A" string if nil
	                combinedData = row[cols[1]] or "N/A"
	            else
	                -- If two column indices are provided, merge them, defaulting to "N/A" string if nil
	                local col1Data = row[cols[1]] or "N/A"
	                local col2Data = row[cols[2]] or "N/A"
	                
					-- Check if col1Data is " "
					if col1Data == " " then
					    combinedData = col2Data
					-- Check if both col1Data and col2Data are " "
					elseif col1Data == " " and col2Data == " " then
					    combinedData = " "
					else
					    combinedData = '[' .. col1Data .. ' ' .. col2Data .. ']'
					end
					if itemprop then
						combinedData = combinedData .. ' <link itemprop="' .. itemprop .. '" href="' .. col1Data .. '"/> '
					end
	            end
	            dataRow:tag('td'):wikitext(combinedData)
	        end
	    end
    end
    
    -- mw.log(htmlTable)
    
	return tostring(htmlTable)
end

-- Helper function to convert table contents to a single string
function tableToString(t)
    local stringParts = {}
    for key, value in pairs(t) do
        if type(value) == 'string' then
            table.insert(stringParts, value)
        elseif type(value) == 'table' then
            -- Recursively convert nested tables to string
            table.insert(stringParts, tableToString(value))
        elseif type(value) == 'number' or type(value) == 'boolean' then
            -- Convert numbers and booleans to string directly
            table.insert(stringParts, tostring(value))
        end
    end
    return table.concat(stringParts, " ") -- Combine with a space as separator
end

-- Function to convert text to JSON valid
function M.validJSON(text)
	    -- Ensure text is always a string (handles nil cases)
    text = tostring(text or "")

    -- Use mw.text.jsonEncode to escape special characters
    return mw.text.jsonEncode(text)
end


-- Function to convert Markdown text to Mediawiki format
function M.markdownToMediawiki(mdText)

    -- Check if the input is a table and convert it
    if type(mdText) == 'table' then
        mdText = tableToString(mdText)
    elseif type(mdText) ~= 'string' then
        error("Expected a string or a table, got " .. type(mdText))
    end

    -- Process the string to convert Markdown to MediaWiki formatting
    mdText = string.gsub(mdText, "%*%*(.-)%*%*", "'''%1'''")
    mdText = string.gsub(mdText, "%[(.-)%]%((.-)%)", "[%2 %1]")
    mdText = string.gsub(mdText, "\\N", "\n")
    mdText = string.gsub(mdText, "\\T", "\t")
    mdText = string.gsub(mdText, "\n### (.-)\n", "\n=== %1 ===\n")
    mdText = string.gsub(mdText, "\n## (.-)\n", "\n== %1 ==\n")
    mdText = string.gsub(mdText, "\n# (.-)\n", "\n= %1 =\n")

    return mdText
end

-- Created a string containing all values for a given property of a given item
function M.getValuesForProperty(frame)
	local target1 = frame.args[1]
	local PID = frame.args[2]
	
	-- Ensure text is always a string (handles nil cases)
    PID = tostring(PID or "")
    target1 = tostring(target1 or "")

-- Constructing the SPARQL query with dynamic entity target1
    local sparqlQuery = [[
PREFIX target1: <https://portal.mardi4nfdi.de/entity/]] .. target1 .. [[>
PREFIX wdt: <https://portal.mardi4nfdi.de/prop/direct/>
PREFIX wd: <https://portal.mardi4nfdi.de/entity/>

SELECT ?property ?propertyLabel ?value ?valueLabel
WHERE {
    target1: wdt:]] .. PID .. [[ ?value .
    OPTIONAL {
        ?value rdfs:label ?valueLabel .
        FILTER(LANG(?valueLabel) = "en")
    }
    BIND(wdt:]] .. PID .. [[ AS ?property)
    SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
    ]]
    
    -- mw.log( sparqlQuery )
    
	-- Executing the SPARQL query and retrieving results in JSON format
	local jsonResults = sparql.runQuery(sparqlQuery)
	
	-- mw.logObject(jsonResults)

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

function M.getValuesForPropertyList(frame)
	
	local target1 = frame.args[1]
	local PID = frame.args[2]
	
	target1 = tostring(target1 or "")
    if not target1 or target1 == '' then
        return "No records found"
    end    
    
    local fakeFrame = {
		args = {
			[1] = target1,
			[2] = PID
		}
    }
	jsonResults = M.getValuesForProperty( fakeFrame )

	if not jsonResults then
        return "Could not fetch data."
	end
	
	if not jsonResults.results then
        return "Could not fetch any data."
	end
	
	if M.countElementsInBindings(jsonResults.results.bindings) == 0 then
        return "No records found."
	end

	local list = M.convertJsonToCommaSeparatedList(jsonResults)
	
	-- mw.log(licenseList) 
	
    return list

end

return M