Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:U.S. Army Cemeteries Explorer/sandbox

From Wikipedia, the free encyclopedia
-- U.S. Army Cemeteries Explorer.lua
-- License: CC-0. Public domain

-------------------
-- HELPER FUNCTIONS
-------------------

local function empty(val)
	return not val or "" == val
end

local function serialize_table(val, name, skipnewlines, depth)
    skipnewlines = skipnewlines or false
    depth = depth or 0

    local tmp = string.rep(" ", depth)

    if name then tmp = tmp .. name .. " = " end

    if type(val) == "table" then
        tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")

        for k, v in pairs(val) do
            tmp =  tmp .. serialize_table(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
        end

        tmp = tmp .. string.rep(" ", depth) .. "}"
    elseif type(val) == "number" then
        tmp = tmp .. tostring(val)
    elseif type(val) == "string" then
        tmp = tmp .. string.format("%q", val)
    elseif type(val) == "boolean" then
        tmp = tmp .. (val and "true" or "false")
    else
        tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
    end

    return tmp
end

local function has_value(tab, val)
    for index, value in ipairs(tab) do
        if value == val then
            return true
        end
    end
    return false
end

local function table_invert(t)
   local s={}
   for k,v in pairs(t) do
     s[v]=k
   end
   return s
end

local function length_and_string(str)
	local s = tostring(str)
	local size = #s	-- # returns byte length for Lua strings
	if size > 255 then
		error(("string too long (%d bytes); max 255"):format(size))
	end
	return string.char(size) .. s
end

local function parent_frame_if_args_empty(frame)
	local args_empty = true
	for key, val in pairs(frame.args) do args_empty = false; break end
	if args_empty and frame.getParent then
		local parent = frame:getParent()
		if parent then frame = parent end
	end
	return frame
end

------------
-- CONSTANTS
------------

local p = {}

p.passthrough_citation_parameters = {
	"title", "url", "url-status", "archive-url", "archive-date", "access-date" }

local FIELD_CODES = {
	last   = "\n",
	first  = string.char(0x12),
	middle = string.char(0x1A),
}

local FIELD_ORDER = {"last", "first", "middle"}

-- This is very incomplete.
local CEMETERY_NAMES_TO_SLUGS = {
	["Arlington National Cemetery"] = "arlington-national",
	["US Soldiers' and Airmen's Home"] = "soldiers-airmens-home",
	["Fort Sill Post Cemetery"] = "ft-sill",
	["Fort Bragg"] = "ft-bragg",
	["Fort Benning Georgia Main Post Cemetery"] = "ft-benning"
}

local CEMETERY_SLUGS_TO_NAMES = table_invert(CEMETERY_NAMES_TO_SLUGS)

local SEARCH_PARAMETERS = {
	'interment-date', 'birth-date', 'death-date',
	'last', 'first', 'middle', 'cemeteryid'
}

local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

-------------------
-- MODULE FUNCTIONS
-------------------

-- See another implementation at [[Module:Cite taxon]].
function p.base64_encode(data)
	if not data or #data == 0 then return "" end
	local out = {}
	local len = #data
	for i = 1, len, 3 do
		local a = data:byte(i) or 0
		local b = data:byte(i + 1)
		local c = data:byte(i + 2)
		local n = a * 65536 + ( (b or 0) * 256 ) + (c or 0)

		local i1 = math.floor(n / 262144) % 64 + 1
		local i2 = math.floor(n / 4096) % 64 + 1
		local i3 = math.floor(n / 64) % 64 + 1
		local i4 = n % 64 + 1

		if not b then
			-- only 1 input byte -> two base64 chars + "=="
			out[#out + 1] = b64chars:sub(i1, i1)
			out[#out + 1] = b64chars:sub(i2, i2)
			out[#out + 1] = "=="
		elseif not c then
			-- 2 input bytes -> three base64 chars + "="
			out[#out + 1] = b64chars:sub(i1, i1)
			out[#out + 1] = b64chars:sub(i2, i2)
			out[#out + 1] = b64chars:sub(i3, i3)
			out[#out + 1] = "="
		else
			-- 3 input bytes -> four base64 chars
			out[#out + 1] = b64chars:sub(i1, i1)
			out[#out + 1] = b64chars:sub(i2, i2)
			out[#out + 1] = b64chars:sub(i3, i3)
			out[#out + 1] = b64chars:sub(i4, i4)
		end
	end
	return table.concat(out)
end

-- +search_fields+ should be a table whose keys are at least some of the values
-- from SEARCH_PARAMETERS, or a frame whose args are such a table.
-- Returns strict Base64 string or nil when no known search parameters are
-- provided.
-- +unknown_action+ is what to do when an unknown parameter is provided.
-- Accepted values are ignore, warn, and error.
function p.encode_search_query(search_fields, unknown_action)
	if not unknown_action then unknown_action="warn" end
	if not empty(search_fields.args) then
		search_fields = search_fields.args
	end
	if not search_fields then return nil end

	local res_parts = {}
	for index, key in pairs(FIELD_ORDER) do
		local value = search_fields[key]
		if value then
			local code = FIELD_CODES[key]
			if code then
				res_parts[#res_parts + 1] = code
				res_parts[#res_parts + 1] = length_and_string(value)
			else
				if unknown_action == "warn" then
					mw.log(("Module:U.S. Army Cemeteries Explorer#encode_search_query: unknown field code: %q"):format(tostring(key)))
				elseif unknown_action == "error" then
					error(("unknown field code: %q"):format(tostring(key)))
				elseif unknown_action ~= "ignore" then
					error(("unknown 2nd parameter value %q. Accepted values are warn, error, and ignore."):format(tostring(unknown_action)))
				end
			end
		end
	end

	local raw = table.concat(res_parts)
	if "" == raw then
		mw.log("Module:U.S. Army Cemeteries Explorer#encode_search_query failed to generate a search query for " .. serialize_table(search_fields))
		return nil
	end
	mw.log("Raw query: " .. tostring(raw))
	local res = string.gsub(p.base64_encode(raw),'=','-')
	mw.log(res)
	return res
end

-- Can inherit parameters from parent frame
function p.print_url(frame)
	frame = parent_frame_if_args_empty(frame)
	local iss_id = frame.args['iss-id']
	if empty(iss_id) then return nil end
	local cemetery_slug = p.cemetery_slug(frame)
	if not cemetery_slug then return nil end	
	local url = "https://ancexplorer.army.mil/publicwmv/print.html#/" .. cemetery_slug .. "/burial/" .. iss_id .. "/"
	return url
end

-- frame must contain parameters directly.
function p.cemetery_name(frame)
	local args = frame
	if frame.args then args = frame.args end
	local res = args["cemetery-name"]
	if res then return res end
	res = CEMETERY_SLUGS_TO_NAMES[args["cemetery-slug"]] 
	if res then return res end
	return nil
end

-- frame must contain parameters directly.
function p.cemetery_slug(frame)
	local args = frame
	if frame.args then args = frame.args end
	local res = args["cemetery-slug"]
	if res then return res end
	res = CEMETERY_NAMES_TO_SLUGS[args["cemetery-name"]] 
	if res then return res end
	return nil
end

-- Can inherit parameters from parent frame
function p.search_url(frame)
	
	local res = "https://ancexplorer.army.mil/publicwmv/#/search-all/results/1/"

	frame = parent_frame_if_args_empty(frame)
	
	local query_slug = frame.args["encoded-query"]
	if empty(query_slug) then
		query_slug = p.encode_search_query(frame, "ignore")
		if empty(query_slug) then
			return nil
		end
	end
	res = res .. query_slug .. "/"

	return res
end

-- Can inherit parameters from parent frame
function p.make_citation(frame)

	frame = parent_frame_if_args_empty(frame)
	
	local module_args = frame.args
	local print_url = p.print_url(frame)
	local search_url = p.search_url(frame)
	local output_params = {}
	output_params.publisher = "United States Army"
	if search_url then
		output_params.url = search_url
	elseif print_url then
		output_params.url = print_url
	else
		output_params.url = "https://ancexplorer.army.mil/"
	end
	if not empty(module_args.middle) and empty(module_args.first) then
		error("ERROR: Middle name given but not first name.")
	end
	if not empty(module_args.last) then
		if not empty(module_args.first) then
			output_params.title = module_args.last .. ", " .. module_args.first
		else
			output_params.title = module_args.last
		end
	elseif not empty(module_args.first) then
		output_params.title = module_args.first
	end
	if not empty(module_args.middle) then
		output_params.title = output_params.title .. " " .. module_args.middle
	end
	local iss_id = module_args["iss-id"]
	if not output_params.title and not empty(iss_id) then
		output_params.title = iss_id
	end
	local cemetery_name = p.cemetery_name(frame)
	if output_params.title then
		if cemetery_name then
			output_params.title = output_params.title .. ": "
		end
	else
		output_params.title = ""
	end
	if cemetery_name then
		output_params.title = output_params.title .. cemetery_name
	end
	
	for key, value in pairs(p.passthrough_citation_parameters) do
		local val = module_args[value]
		if val then
			output_params[value] = val
		end
	end
	
	if empty(output_params.title) then
		output_params.title = "Army Cemeteries Explorer"
	else
		output_params.website = "Army Cemeteries Explorer"
	end
	
	local res = mw.getCurrentFrame():expandTemplate{ title = 'Cite web', args = output_params }

	if print_url and search_url then
		res = res .. " [" .. print_url .. " Alternate view]."
	end

	return res
end

return p