Module:HelperMethods: Difference between revisions

From MaRDI portal
No edit summary
No edit summary
Tag: Manual revert
 
(48 intermediate revisions by 2 users not shown)
Line 13: Line 13:
local sparql = require('SPARQL')
local sparql = require('SPARQL')
local mwHtml = require('mw.html')
local mwHtml = require('mw.html')
-- Return a string if an item's title is not available
local titleNotAvailablePageName = "TitleNotAvailable"
function M.titleNotAvailableStr(arg)
local entity
if type(arg) == "table" and arg.args then
-- Called from template (e.g. Template:Publication)
entity = arg.args[1]
elseif type(arg) == "string" then
-- Called from another module
entity = arg
else
-- No parameter given
entity = "Title"
end
return entity .. " not available ([[" .. titleNotAvailablePageName .. "|Why is that?]])"
end


-- Utility function to trim and lowercase a string
-- Utility function to trim and lowercase a string
Line 87: Line 105:
     return resultsTable
     return resultsTable
end
end
function M.makeWikiLinkOLD(link, name)
if string.sub(link, 1, 34) == "https://portal.mardi4nfdi.de/wiki/" then
  return "[[" .. string.sub(link, 35) .. "|" .. name .. "]]"
end
return "[" .. link .. " " .. name .. "]"
end
function M.makeWikiLink(link, name)
    local baseWiki = "https://portal.mardi4nfdi.de/wiki/"
    local baseEntity = "https://portal.mardi4nfdi.de/entity/"
-- Case 1: Workaround for the "not available" info links -> return external link
    if string.find(name, titleNotAvailablePageName) then
    return "[" .. link .. " " .. name .. "]"
    end   
    -- Case 2: full wiki URL -> strip prefix
    if string.sub(link, 1, #baseWiki) == baseWiki then
        local page = string.sub(link, #baseWiki + 1):match("^[^%?]+")
        return "[[" .. page .. "|" .. name .. "]]"
    end
    -- Case 3: entity URL -> convert to wiki/Item:Qxxx
    if string.sub(link, 1, #baseEntity) == baseEntity then
        local qid = string.sub(link, #baseEntity + 1)
        return "[[Item:" .. qid .. "|" .. name .. "]]"
    end
   
    -- fallback
    return "[" .. link .. " " .. name .. "]"
end


-- Function to convert JSON results into a comma-separated string
-- Function to convert JSON results into a comma-separated string
Line 108: Line 159:
                 -- get label
                 -- get label
                 local label = binding.valueLabel.value
                 local label = binding.valueLabel.value
                if string.find(label, "https://") then
--                if string.find(label, "https://") then
                label = "N/A"
--                label = "N/A"
                end
--                end
                  
                  
                 local nameAndLink = "[" .. value .. " " .. label .. "]"
                 local nameAndLink = "[" .. value .. " " .. label .. "]"


                 resultsString = resultsString .. nameAndLink
                 resultsString = resultsString .. M.makeWikiLink(value, label)
             end
             end
         end
         end
Line 216: Line 267:
    combinedData = " "
    combinedData = " "
else
else
    combinedData = '[' .. col1Data .. ' ' .. col2Data .. ']'
--    combinedData = '[' .. col1Data .. ' ' .. col2Data .. ']'
combinedData = M.makeWikiLink(col1Data, col2Data)
end
end
if itemprop then
if itemprop then
Line 270: Line 322:


     -- Process the string to convert Markdown to MediaWiki formatting
     -- Process the string to convert Markdown to MediaWiki formatting
    -- Replace newline and tab placeholders
    mdText = string.gsub(mdText, "\\N", "\n")
    mdText = string.gsub(mdText, "\\T", "\t")
    -- Convert bold markdown (**text**) to MediaWiki bold ('''text''')
     mdText = string.gsub(mdText, "%*%*(.-)%*%*", "'''%1'''")
     mdText = string.gsub(mdText, "%*%*(.-)%*%*", "'''%1'''")
    -- Convert markdown links [text](url) to MediaWiki [url text]
     mdText = string.gsub(mdText, "%[(.-)%]%((.-)%)", "[%2 %1]")
     mdText = string.gsub(mdText, "%[(.-)%]%((.-)%)", "[%2 %1]")
    mdText = string.gsub(mdText, "\\N", "\n")
 
     mdText = string.gsub(mdText, "\\T", "\t")
     -- Convert headings: match at start of line and beginning of string
     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")
     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")
    mdText = string.gsub(mdText, "^### (.-)\n", "=== %1 ===\n")
    mdText = string.gsub(mdText, "^## (.-)\n", "== %1 ==\n")
    mdText = string.gsub(mdText, "^# (.-)\n", "= %1 =\n")


     return mdText
     return mdText
Line 282: Line 346:


-- Created a string containing all values for a given property of a given item
-- Created a string containing all values for a given property of a given item
function M.getValuesForProperty(target1, PID)
function M.getValuesForProperty(frame)
-- Ensure text is always a string (handles nil cases)
-- Ensure text is always a string (handles nil cases)
    PID = tostring(PID or "")
local target1 = tostring(frame.args[1] or "")
    target1 = tostring(target1 or "")
local PID = tostring(frame.args[2] or "")
 
-- Constructing the SPARQL query with dynamic entity target1
-- Constructing the SPARQL query with dynamic entity target1
     local sparqlQuery = [[
     local sparqlQuery = [[
Line 300: Line 364:
         FILTER(LANG(?valueLabel) = "en")
         FILTER(LANG(?valueLabel) = "en")
     }
     }
     BIND(wdt:]] .. PID .. [[ AS ?property)
     BIND(wd:]] .. PID .. [[ AS ?property)
     SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
     SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
}
Line 318: Line 382:
end
end
return sparqlQuery
return jsonResults
-- return jsonResults
end
end


function M.getValuesForPropertyList(target1, PID)
-- Returns a list of the values for this property.
-- To test:
--    local HM = require("Module:HelperMethods")
--    local fakeFrame = {args = {[1] = "Q6767920", [2] = "P56"}}
--    result = HM.getValuesForPropertyList(fakeFrame)
--    mw.log(result)
function M.getValuesForPropertyList(frame)
local target1 = frame.args[1]
local PID = frame.args[2]
target1 = tostring(target1 or "")
target1 = tostring(target1 or "")
     if not target1 or target1 == '' then
     if not target1 or target1 == '' then
Line 328: Line 401:
     end     
     end     
      
      
jsonResults = M.getValuesForProperty( target1, PID )
    local fakeFrame = { args = {[1] = target1, [2] = PID } }
jsonResults = M.getValuesForProperty( fakeFrame )


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
Line 345: Line 423:


end
end


return M
return M

Latest revision as of 13:48, 25 November 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')

-- Return a string if an item's title is not available
local titleNotAvailablePageName = "TitleNotAvailable"
function M.titleNotAvailableStr(arg)
	local entity
	if type(arg) == "table" and arg.args then
		-- Called from template (e.g. Template:Publication)
		entity = arg.args[1]
	elseif type(arg) == "string" then
		-- Called from another module
		entity = arg
	else
		-- No parameter given
		entity = "Title"
	end

	return entity .. " not available ([[" .. titleNotAvailablePageName .. "|Why is that?]])"
end

-- 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 M.makeWikiLinkOLD(link, name)
	if string.sub(link, 1, 34) == "https://portal.mardi4nfdi.de/wiki/" then
	   return "[[" .. string.sub(link, 35) .. "|" .. name .. "]]"
	end
	return "[" .. link .. " " .. name .. "]"
end

function M.makeWikiLink(link, name)
    local baseWiki = "https://portal.mardi4nfdi.de/wiki/"
    local baseEntity = "https://portal.mardi4nfdi.de/entity/"

	-- Case 1: Workaround for the "not available" info links -> return external link
    if string.find(name, titleNotAvailablePageName) then
	    return "[" .. link .. " " .. name .. "]"
    end    

    -- Case 2: full wiki URL -> strip prefix
    if string.sub(link, 1, #baseWiki) == baseWiki then
        local page = string.sub(link, #baseWiki + 1):match("^[^%?]+")
        return "[[" .. page .. "|" .. name .. "]]"
    end

    -- Case 3: entity URL -> convert to wiki/Item:Qxxx
    if string.sub(link, 1, #baseEntity) == baseEntity then
        local qid = string.sub(link, #baseEntity + 1)
        return "[[Item:" .. qid .. "|" .. name .. "]]"
    end
    
    -- fallback
    return "[" .. link .. " " .. name .. "]"
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 .. M.makeWikiLink(value, label)
            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 .. ']'
						combinedData = M.makeWikiLink(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

    -- Replace newline and tab placeholders
    mdText = string.gsub(mdText, "\\N", "\n")
    mdText = string.gsub(mdText, "\\T", "\t")

    -- Convert bold markdown (**text**) to MediaWiki bold ('''text''')
    mdText = string.gsub(mdText, "%*%*(.-)%*%*", "'''%1'''")

    -- Convert markdown links [text](url) to MediaWiki [url text]
    mdText = string.gsub(mdText, "%[(.-)%]%((.-)%)", "[%2 %1]")

    -- Convert headings: match at start of line and beginning of string
    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")

    mdText = string.gsub(mdText, "^### (.-)\n", "=== %1 ===\n")
    mdText = string.gsub(mdText, "^## (.-)\n", "== %1 ==\n")
    mdText = string.gsub(mdText, "^# (.-)\n", "= %1 =\n")

    return mdText
end

-- Created a string containing all values for a given property of a given item
function M.getValuesForProperty(frame)
	-- Ensure text is always a string (handles nil cases)
	local target1 = tostring(frame.args[1] or "")
	local PID = tostring(frame.args[2] 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(wd:]] .. 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 jsonResults
end

-- Returns a list of the values for this property.
-- To test:
--    local HM = require("Module:HelperMethods")
--    local fakeFrame = {args = {[1] = "Q6767920", [2] = "P56"}}
--    result = HM.getValuesForPropertyList(fakeFrame)
--    mw.log(result)
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