Module:ExperimentalNavplate

Revision as of 00:50, 28 January 2023 by Royken (talk | contribs) (Created page with "--[[This Mess is an experimental module for seeing if we can create a navplate that populates itself as an ordered, nested list. It works by getting all of the pages from the category (in this example, Stanton) including their infobox data through a single DPLlua request. It then parses these into categories and assigns a sort order. These lists are then iterated through to find the objects that orbit the star, objects that orbit those objects, etc. Those lists are then...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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

--[[This Mess is an experimental module for seeing if we can create a navplate that populates itself
as an ordered, nested list.

It works by getting all of the pages from the category (in this example, Stanton) including their infobox data
through a single DPLlua request. It then parses these into categories and assigns a sort order.
These lists are then iterated through to find the objects that orbit the star, objects that orbit
those objects, etc. Those lists are then sorted by their orbit location (a new required field) and then
semantic precedence defined by a precedence score. As this sorting goes on, a table of strings is built up to generate the page contents.]]--

local p = {};    --Necessary Lua Object DO NOT remove!
local abbreviations = {{},{}} -- empty container for list of abbreviations (full length and cleaned up)
local abbreviationsCheckThreshold = 5;
local dpl = require( 'Module:DPLlua' )

--List of possible categories for each location ordered :
--Category Name, Clean version, Symbol, Important?, Precedence Score
--Mistakes/inadequacies arising from the code are likely to originate here!
--ORDER of the list defines priority for semantic collapsing: more specific classifications should go to the
--top, more general ones to the bottom (e.g. Gas Giant to top, Planet to bottom)
--PRECEDENCE SCORE is for sorting lists

local arrowdown = "[[File:arrowdown_blue.svg|thumb|link=|15x15px]]"
local arrowleft = "[[File:arrowleft_blue.svg|thumb|link=|15x15px]]"
local arrowright = "[[File:arrowright_blue.svg|thumb|link=|15x15px]]"
local arrowup = "[[File:arrowup_blue.svg|thumb|link=|15x15px]]"
local circle =  "[[File:circle_blue.svg|thumb|link=|20x20px]]"
local concentric =  "[[File:Concentric_blue.svg|thumb|link=|25x25px]]"
local hexagon =  "[[File:Hexagon_blue.svg|thumb|link=|20x20px]]"
local lz =  "[[File:LZ_blue.svg|thumb|link=|20x20px]]"
local moon =  "[[File:moon_blue.svg|thumb|link=|20x20px]]"
local rhombus =  "[[File:rhombus_blue.svg|thumb|link=|20x20px]]"
local square =  "[[File:Square_blue.svg|thumb|link=|20x20px]]"
local target =  "[[File:target_blue.svg|thumb|link=|20x20px]]"





local typelist = {

	--GAS PLANETS
	{"Ice Giant","gasgiant",circle,true,1},
	{"Gas Dwarf","gasgiant",circle,true,1}, 
	{"Puffy Planet","gasgiant",circle,true,1},
	{"Gas Giant","gasgiant",circle,true,1},
	{"Super-Jupiter","superjupiter",circle,true,1}, --This is more of a size class than an environment class so I am putting it below Gas Giant, and we won't see it.

	
	--TERRESTRIAL PLANETS (would prefer if each planet had an environmental description)
	{"Ice Planet","iceplanet",circle,true,1},
	{"Iron Planet","ironplanet",circle,true,1},
	{"Smog Planet","smogplanet",circle,true,1},
	{"Carbon Planet","carbonplanet",circle,true,1},
	{"Chthonian Planet","chthonianplanet",circle,true,1},
	{"Lava Planet","lavaplanet",circle,true,1},
	{"Ocean Planet","oceanplanet",circle,true,1},
	{"Artificial Planet","artificialplanet",circle,true,1},
	{"Protoplanet","protoplanet",circle,true,1},
	{"Terrestrial Rocky","terrestrialrocky",circle,true,1},
	
	--These are commonly featured, but don't fit with the others and are less informative
	{"Super-Earth","superearth",circle,true,1}, --This is more of a size class than an environment class
	{"Coreless Planet","corelessplanet",circle,true,1}, --A geological feature, not an environment.
	{"Dwarf Planet","dwarfplanet",circle,true,1}, --This is more of a size class than an environment class
	{"Mesoplanet","mesoplanet",circle,true,1}, --This is more of a size class than an environment class

	--IDEALLY THIS SHOULD NOT BE USED, AND A MORE DESCRIPTIVE CATEGORY ASSIGNED
	{"Planet","planet",circle,true,1.9},

	--OTHER CELESTIAL
	{"Jump Point","jumppoint",target, true,99},
	{"Moon","moon",moon,true,5},
	{"Nebula","nebula",rhombus,true,5.5}, --Probably major??
	


	--ASTEROIDS
	{"Planetoid","planetoid",circle,true,6},
	{"Asteroid Belt","asteroidbelt",rhombus,true,6},
	{"Asteroid Ring","asteroidring",rhombus,true,6},
	{"Asteroid Field","asteroidfield",rhombus,false,6},
	{"Large Asteroid","largeasteroid",rhombus,false,6},
	{"Asteroid Cluster","asteroidcluster",rhombus,false,7},
	{"Asteroid","asteroid",rhombus,false,7.9},



	--SPACE STATIONS
	{"Flotilla","flotilla",square,true,4},
	{"Trade Hub","tradehub",square,true,4},
	{"Rest Stop","reststop",square,false,5},
	{"Military","military",square,true,4}, -- Can use as a catch-all for military types, though will apply to both land and space (clear from context?)
	{"Outlaw","outlaw",square,true,4}, -- Can use as a catch-all for outlaw types, though will apply to both land and space (clear from context?)
	{"Comm Array","commarray",rhombus, false,6},
	{"Space Station","spacestation",rhombus, false,6.9},
	{"Satellite","satellite",rhombus, false,6.9},

		
	--MISC
	{"Contact","contact",rhombus,false,8},
	{"Navigation Point","navigationpoint",rhombus,false,8},
	{"Lagrange Point","lagrangepoint",rhombus,false,5.5},
	{"Cardinal Point","cardinal",rhombus,false,8},
	{"Quantum Trace Point","quantumtrace",rhombus,false,8},
	{"Anomaly","anomaly",rhombus,false,8},



	--SETTLEMENTS
	{"City","city",lz,true,0},
	{"Settlement","settlement",lz,true,0},
	{"Asteroid Base","asteroidbase",lz,true,4.5},
	{"Landing Zone","landingzone", lz,true,0},
	{"Space Port","spaceport",lz,true,1},	
	
	--CITY AMENETIES
	{"Apartment Habs","apartment",arrowleft,false,2},
	{"Bar","bar",arrowleft,false,2},
	{"Commercial Building","commercial",arrowleft, false,2},
	{"Convention Center","convention",arrowleft, false,2},
	{"Shop","shop",arrowleft, false,2},
	{"Hospital","hospital",arrowleft, false,2},

	--LANDMARKS
	{"Cave","cave",hexagon, false,2},
	{"Drug Lab","druglab",hexagon, false,2},
	{"Farming Outpost","farming",hexagon, false,2},
	{"Mining Outpost","mining",hexagon, false,2},
	{"Salvage Yard","salvage",hexagon, false,2},
	{"Shelter","shelter",hexagon, false,2},
	{"Stash","stash",hexagon, false,2},
	{"Underground Facility","underground",hexagon, false,2},
	{"Outpost","outpost",hexagon, false,3},
	
	{"Landmark","landmark",hexagon, false,3}
	
}

--Special cases for the star, black hole and unidentified.	
local starSymb = concentric
local blackholeSymb = concentric
local miscLandSymb = arrowleft
local miscSpaceSymb = arrowup
local orphanSymbol = arrowdown
--How small to make the minor location expandables.
local minorlocationfontsize = 60

condenseByType = function(lines,unimportant)
	local originalCount = #unimportant
	for i,typ in ipairs(typelist) do
		-- we only want to condense minor types
		if typ[4] == false then
			local rest = {}
			local found = {}
			local count = 0
			for j, loc in ipairs(unimportant) do
				if string.find(loc["typeClean"],typ[2])~=nil or string.find(loc["classClean"],typ[2])~=nil then
					count = count+1
					table.insert(found,loc)
				else 
					table.insert(rest,loc)
				end
					
			end
			if count == originalCount then break end
			if count>1 then
				unimportant = rest
				table.insert(lines,"\n<div class=\"locationnavplate-item-other mw-collapsible mw-collapsed\">\n<div class=\"locationnavplate-item-other-header\">"..typ[1].." ("..count..")</div>\n<div class=\"locationnavplate-item-group mw-collapsible-content\">")
				table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
			
				--Can still run a condenseByName here in addition
				local update = condenseByName(lines,found)
				lines=update[1]
				found=update[2]
				for k,item in ipairs(found) do
					table.insert(lines,"\n	"..item["ListEntry"])
					table.insert(lines,"\n</div>")
				end
				table.insert(lines,"\n</div>")
				table.insert(lines,"\n</div>")
				table.insert(lines,"\n</div>")
			end
		end
	end
	
   --If still over threshold, run the remaining list through name condensation 	
   if #unimportant>abbreviationsCheckThreshold then
		return condenseByName(lines,unimportant)
	else
	--Else just return what we have
		return{lines,unimportant}
	end
end

condenseByName = function(lines,unimportant)
	local originalCount = #unimportant
	for i,abb in ipairs(abbreviations[2]) do
		local rest = {}
		local found = {}
		local count = 0
		for j,loc in ipairs(unimportant) do
			if string.find(loc["CleanName"],abb)~=nil then
				count = count+1
				table.insert(found,loc)
			else 
				table.insert(rest,loc)
			end
		end
		if count == originalCount then break end

		if count>1 then
			unimportant = rest
			table.insert(lines,"\n<div class=\"locationnavplate-item-other mw-collapsible mw-collapsed\">\n<div class=\"locationnavplate-item-other-header\">"..abbreviations[1][i].."... ("..count..")</div>\n<div class=\"locationnavplate-item-group mw-collapsible-content\">")
			table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
			for k,item in ipairs(found) do
				table.insert(lines,"\n	"..item["ListEntry"])
				table.insert(lines,"\n</div>")
			end
			table.insert(lines,"\n</div>")
			table.insert(lines,"\n</div>")
			table.insert(lines,"\n</div>")
		end
	end
	
	return {lines,unimportant}
end

populateEntry = function(category,entry,loctype,name)
	local class = "item"
	if loctype == "star" then class = "heading" end
	entry["ListEntry"] = "<div class=\"locationnavplate-"..loctype.." locationnavplate-"..class.."\"><div class=\"locationnavplate-item-title\"><span class=\"locationnavplate-item-icon\">"..category[3].."</span><span class=\"locationnavplate-item-name\">[["..name.."]]</span></div>"
	entry["Important"] = category[4]
	entry["SortPower"] = category[5]
	return entry
end

iterateToPopulate = function(lines,children,rest,expand)
	

	for i=1 ,2, 1 do
		local obj = children[i]
		local important = obj[1]
		local unimportant = obj[2]
		
		
		if important ~= nil then 
			table.sort(important,compareObjects)
			for j,loc in ipairs(important) do
				table.insert(lines,"\n"..loc["ListEntry"])
				local locChl = findChildren(rest,loc["ProperName"])
				rest=locChl[3]
				if #locChl[1][1]+#locChl[1][2]+#locChl[2][1]+#locChl[2][2]>0 then
					table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
					local update = iterateToPopulate(lines,locChl,rest,expand)
					lines=update[1]
					rest=update[2]
					table.insert(lines,"\n</div>")
				end
				table.insert(lines,"\n</div>")
			end
		end
		
		if unimportant~= nil then
			if #unimportant>0 then
				if expand == false then
					table.insert(lines,"\n<div class=\"locationnavplate-item-other mw-collapsible mw-collapsed\">\n<div class=\"locationnavplate-item-other-header\">"..#unimportant.." minor locations</div>\n<div class=\"locationnavplate-item-group mw-collapsible-content\">")
				end
				
				if #unimportant>=abbreviationsCheckThreshold then
					local returned = condenseByType(lines,unimportant)
					lines = returned[1]
					unimportant = returned[2]
				end
				
				--This typesets the residual entries
				for j, loc in ipairs(unimportant) do 
					table.insert(lines,"\n	"..loc["ListEntry"])
					local locChl = findChildren(rest,loc["ProperName"])
					rest=locChl[3]
					if #locChl[1][1]+#locChl[1][2]+#locChl[2][1]+#locChl[2][2]>0 then
						table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
						local update = iterateToPopulate(lines,locChl,rest,true)
						lines=update[1]
						rest=update[2]
						table.insert(lines,"\n</div>")
					end
					table.insert(lines,"\n</div>")
				end
				if expand == false then
					table.insert(lines,"\n</div>")
				end
				table.insert(lines,"\n</div>")
			end
		end
	end
	
	local ret = {lines,rest}
	return ret
end

--Find all objects in list that mention string s in their location descriptor. Returns in format:
--{{important land, unimportant land},{important space},{unimportant space},{remainder]}
findChildren = function (list, s)
	local found={{{},{}},{{},{}},{}}
	s = string.lower(s)
	s = s:gsub("%s+", "")
	s = s:gsub("%p+", "")

	local brack = "[["..s.."]]"
	
	for x, item in ipairs(list) do
		loc = item["Location"]
		orb = item["Orbits"]
		if loc == nil then
			loc = "NOTFOUND"
		end
		
		if orb == nil then
			orb = "NOTFOUND"
		end
			
		
		loc = string.lower(loc)
		loc = loc:gsub("%s+", "")
		loc = loc:gsub("%p+", "")
		
		orb = string.lower(orb)
		orb = orb:gsub("%s+", "")
		orb = orb:gsub("%p+", "")
		if string.find(loc,s)~=nil or string.find(loc,brack)~=nil or string.find(orb,s)~=nil or string.find(orb,brack)~=nil then
			local important = 2
			local space = 2
			if item["inSpace"] == true then land = 1 end
			if item["Important"] == true then important = 1 end
			table.insert(found[land][important],item)
		else
			table.insert(found[3],item)
		end
		
	end
	return found
end

--Return a table that combines 	
mergeTab = function (table1, table2)
	local newtab = {}
	for x,item in ipairs(table1) do
		table.insert(newtab,table1[i])
	end
	for x,item in ipairs(table2) do
		include = true
		for y,item2 in ipairs(table1) do
			if item == item2 then
				include = false
				break
			end
		end
		if include then table.insert(newtab,table2[i]) end
	end
	return newtab
end


--Sort objects by Orbital Position, if not (absent data or idential position) 
-- then Precedence, if not then alphabetically.
compareObjects = function  (a,b)
	orbA = a["Orbital Position"]
	orbB = b["Orbital Position"]
	if orbA == nil or orbB == nil or orbA == orbB then
		if a["SortPower"] == b["SortPower"] then 
			return a["ProperName"]<b["ProperName"] 
		else 
			return a["SortPower"] < b["SortPower"] 
		end
	else 
		return orbA<orbB
	end
end

p.showTable = function (frame)
	local lines = {}
	table.insert(lines,"{|\n!Category\n!Icon\n!Important\n!Priority")
	for i,ln in ipairs(typelist) do
		table.insert(lines,"\n|-\n|"..ln[1].."\n|"..ln[3].."\n|"..tostring(ln[4]).."\n|"..ln[5])
	end
	table.insert(lines,"\n|}")
    return table.concat(lines)
end
	

--MAIN FUNCTION
--Takes the system from args (defaults to stanton if it cannot) 
p.fullplate= function(frame)    
	
	local abbrevs = {}
	local delist = {}

	local searchCategory = frame.args[1]
	if frame.args[2]~=nil then
		abbrevs = frame.args[2]
	end
	if frame.args[3]~=nil then
		for k, v in string.gmatch(frame.args[3], "([^,]+)") do
			delist[k] = v
		end
		if #delist == 0 then
			delist={frame.args[3]}
		end
	end
	
	if searchCategory == "" or searchCategory == nil then
		return "Looks like the search category was set up incorrectly"
	end
	
	
	if abbrevs ~= null then
		for abb in abbrevs:gmatch("([^,]+)") do
			table.insert(abbreviations[1],abb)
			abb = string.lower(abb)
			abb = abb:gsub("%s+", "")
			abb = abb:gsub("%p+", "")
			table.insert(abbreviations[2],abb)
		end
	end
	
	-- Get the pages from the category of Stanton System
	-- Potential to make this more elegant
   local list = dpl.ask({
		namespace = '',
		category = searchCategory,
		include='{Infobox Astronomical Object},{Infobox Location},{Infobox Space Station}'
	} )

   local stars={}
   local objects = {}
   local orphans = {} --pages without the infobox
   local infoboxtypes = {true,false,true}
   
   --Iterate through list, categorise and populate accordingly
	for i, line in ipairs(list) do
		local entry = {}
		
		local name = line["title"]
		
		local delisted = false
		--Check if this is on our delist array, meaning we skip it
		for j, dl in ipairs(delist) do
			if name == delist[j] then
				delisted = true
				break
			end
		end
		
		if delisted == false then
			local loctype = "orphan"
			local inspace = true
			
			for j, cat in ipairs(infoboxtypes) do
				entry = line["include"][j]
				if type(entry)== "table" then
					entry["ProperName"]= name
					local clean = string.lower(name)
					clean = clean:gsub("%s+", "")
					clean = clean:gsub("%p+", "")
					entry["CleanName"]=clean
					loctype = entry["Type"]
					classif = entry["Classification"]
	
					entry["inSpace"] = cat
					entry["SortPower"] = 9999
					break
				end
			end
			
			
			
			loctype = string.lower(loctype)
			loctype = loctype:gsub("%s+", "")
			loctype = loctype:gsub("%p+", "")
			entry["typeClean"] = loctype
			if classif ~= nil then 
				classif = string.lower(classif)
				classif = classif:gsub("%s+", "")
				classif = classif:gsub("%p+", "")
				entry["classClean"] = classif
			else
				entry["classClean"] = ""
			end
			
			
			if loctype =="star" then
				entry["ListEntry"] = "<div class=\"locationnavplate-star locationnavplate-item\"><span class=\"locationnavplate-item-icon\">"..starSymb.."</span><span class=\"locationnavplate-item-name\">[["..name.."]]</span>"
				table.insert(stars,entry)
				entry["ProperName"] = line["title"]
			elseif loctype =="star" then
				entry["ListEntry"] = "<div class=\"locationnavplate-star locationnavplate-item\"><span class=\"locationnavplate-item-icon\">"..starSymb.."</span><span class=\"locationnavplate-item-name\">[["..name.."]]</span>"
				table.insert(stars,entry)
				entry["ProperName"] = line["title"]
			elseif  loctype == "orphan" then
				table.insert(orphans,orphanSymbol..name)
			else 
				entry["ProperName"] = line["title"]
				local found = false
				--Check Classification first, as this is often more specific
				if classif ~= nil then
					for j, category in ipairs(typelist) do
						if string.find(classif,category[2])~=nil then
							entry = populateEntry(category,entry,classif,name)
							table.insert(objects,entry)
							found = true
							break;
						end
					end
				end
				--If classification doesn't give a match, then match type
				if found== false then
					for j, category in ipairs(typelist) do
						if string.find(loctype,category[2])~=nil then
							entry = populateEntry(category,entry,loctype,name)
							table.insert(objects,entry)
							found = true
							break;
						end
					end
				end
				--If all else fails, give it a generic land or space class	
				if found == false then
					
					if entry["inSpace"] == true then
						entry["ListEntry"] =  "<div class=\"locationnavplate-miscspace locationnavplate-item\"><span class=\"locationnavplate-item-icon\">"..miscSpaceSymb.."</span><span class=\"locationnavplate-item-name\">[["..name.."]]</span>"
					else
						entry["ListEntry"] = "<div class=\"locationnavplate-miscland locationnavplate-item\"><span class=\"locationnavplate-item-icon\">"..miscLandSymb.."</span><span class=\"locationnavplate-item-name\">[["..name.."]]</span>"
					end
					entry["Important"] = false
					entry["SortPower"] = 10
					table.insert(objects,entry)
				end
			end
		end
	end
	
	local lines = {}
	
	table.insert(lines,"<div class=\"locationnavplate floatnone\" role=\"navigation\">\n<div class=\"locationnavplate-header\">Locations in<div class=\"locationnavplate-source\">[["..searchCategory.."]]</div>\n</div>")
	if star == {} and searchCategory == "Min System" then --Edge case, starless system
		for i, loc in objects do
			if loc["ProperName"] == "Min I" then
				table.insert(star,loc)
				table.remove(objects,i)
				break
			end
		end
	end

	if star == {} then
		table.insert(lines,"Star Not Properly identified in infobox. You can help by fixing this.")
		table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
		update = iterateToPopulate(lines,objects,{},false)
		lines=update[1]
		objects=update[2]
	else 
		table.sort(stars,compareObjects)
		table.insert(lines,stars[1]["ListEntry"])
		local orbitSun = findChildren(objects,stars[1]["ProperName"])
		objects = orbitSun[3]
		for i=2,#stars,1 do
			table.insert(lines,"</div>\n"..stars[i]["ListEntry"])
			orbitSun = mergeTab(orbitSun,findChildren(objects,stars[i]["ProperName"]))
		end
		
		table.insert(lines,"\n<div class=\"locationnavplate-item-group\">")
		if #orbitSun > 0 then
			update = iterateToPopulate(lines,orbitSun,objects,false)
			lines=update[1]
			objects=update[2]
		else 
			table.insert(lines,"\nCannot identify Objects orbiting star")
			update = iterateToPopulate(lines,objects,{},false)
			lines=update[1]
			objects=update[2]
		end
		table.insert(lines,"\n</div>")
		table.insert(lines,"\n</div>")
	end
	
	if #objects>0 then
		table.insert(lines,"<div class=\"error\">\nThe following locations are not formatting correctly, either because their infoboxes need updating, or due to an error. You can help by editing the wiki.")
		table.sort(objects,compareObjects)
		for i, obj in ipairs(objects) do
			table.insert(lines,"\n".. obj["ListEntry"].."</div>")
		end
		table.insert(lines,"\n</div>")

	end
	
	if #orphans>0 then
		table.insert(lines,"<div class=\"error\">\nThe following locations are not formatting correctly because they do not have infoboxes. You can help by providing them with one.")
		for i, orph in ipairs(orphans) do
			table.insert(lines,"\n".. orph.."</div>")
		end
		table.insert(lines,"\n</div>")
	end


 		table.insert(lines,"\n</div>")
 	

    return table.concat(lines)
end


return p