Module:GHS phrases

-- Module:GHS phrases -- -- main: reads GHS parameters (arguments like "H301", "P401") --		and returns for each (listtype='abbr'): --		phraseID visible; formal phrase text as  -- setID	= "H" or "P" -- phraseID = e.g. "H201", "P231+P234" -- phrase text read from array tables in Module:GHS phrases/data -- -- Implements: Template:GHS phrases -- Helppage: Template:GHS phrases -- Error category:, (mainspace pages only) -- -- Also: -- listAll, numberOfPhrases, listOmitRules, -- listtype, omit

-- require('strict') local r = {}	-- "r" for return, so no confusion with setID P local GHSdata	= mw.loadData('Module:GHS phrases/data') local getArgs	= require('Module:Arguments').getArgs local tTools	= require('Module:TableTools') local yesno		= require('Module:Yesno') local tArgName	= {} -- named parameters (setid, omit, listtype) local tMessagesToShow = {} -- the tail: Preview, Categories

-- wlHelpPage -- -- Formats page as Label -- by default, sLabel == sSection

local function wlHelpPage(sSection, sLabel) local sHelpPage = 'Template:GHS phrases' if sLabel == nil then sLabel = sSection end if (sLabel or ) ==  then sLabel = '' else sLabel = '|' .. sLabel end if (sSection or ) ==  then sSection = '' else sSection = '#' .. sSection end return  .. sHelpPage .. sSection .. sLabel ..  end

-- addErrorCategory -- -- Formats as -- or '' when in other namespace. -- sCatsort option using: H, P, _

local function addErrorCategory(sCatsort) local pagetype = require('Module:Pagetype').main local wlErrCat = '' if pagetype == 'article' then -- mainspace only if sCatsort == nil then sCatsort = tArgName['setID'] end if sCatsort == '' then wlErrCat = '' else wlErrCat = '' end else return '' end

table.insert(tMessagesToShow, wlErrCat) return end

-- addWarningCategory -- -- Formats as -- mainspace only, or '' when in other namespace. -- sCatsort option using: H, P, U, ?, D, O

local function addWarningCategory(sCatsort) local pagetype = require('Module:Pagetype').main if sCatsort == nil then sCatsort = tArgName['setID'] end local wlWarnCat = '' if pagetype == 'article' then -- mainspace only if sCatsort == '' then wlWarnCat = '' else wlWarnCat = '' end else return end

table.insert(tMessagesToShow, wlWarnCat) return end

-- addPreviewMsg

local function addPreviewMsg(sMsg) local previewWarn = require('Module:If preview')._warning table.insert(tMessagesToShow, previewWarn({sMsg})) return end

-- showPreviewMsg -- -- show table tMessagesToShow -- preview-messages and errorcat -- all namespaces

local function showPreviewMsg if tTools.size(tMessagesToShow) > 0 then return table.concat(tMessagesToShow, '') else return '' end end

-- applyRemoveDuplicates -- -- returns edited table, with double Codes removed -- adds warning with codes. -- base table tArgs is walked through by a iwalker that reads a singel code, -- then a ikiller checks the upward part of the same table to delete all copies -- ikiller starts at end of table, walks towards iwalker; then tArgs is compressed -- iwalker steps 1 up in the freshly compressed table -- Used: iArgs is sorted, and order stays same. compress does not change that.

local function applyRemoveDuplicates(tArgs) local iR, iK -- iR = reader, iK = killer local hit = false

iR = 1 while iR < #tArgs do		iK = #tArgs -- will be counting downwards while iK > iR do			if tArgs[iK] == tArgs[iR] then hit = true addPreviewMsg('Duplicate removed: ' .. tArgs[iR]) table.remove(tArgs, iK) tTools.compressSparseArray(tArgs) end iK = iK - 1 end tTools.compressSparseArray(tArgs) iR = iR + 1 end

if hit then addWarningCategory('D') end return tArgs end

-- applyOmitRules -- -- returns edited table, with Omit phraseID's removed -- Omit rule is per GHS_Rev9E_0.pdf (2021)

local function applyOmitRules(tArgs) local tRules = GHSdata['tOmitRules'] local hit = false for keep, omit in pairs(tRules) do		if tTools.inArray(tArgs, omit) then if tTools.inArray(tArgs, keep) then hit = true for i, k in pairs(tArgs) do					if k == omit then table.remove(tArgs, i) end end addPreviewMsg(wlHelpPage('Omit Rules') .. ': keep ' .. keep .. ', omit ' .. omit) end end end if hit then tTools.compressSparseArray(tArgs) addWarningCategory('O') end return tArgs end

-- label H-phrases or P-phrases

local function PHlabel if tArgName['setID'] == 'GHS' then return 'GHS phrases' else return tArgName['setID'] .. '-phrases' end end

-- inMono -- -- Use mono font-family (from: Template:Mono)

local function inMono(s) if s == nil then s = '' end return ' ' .. s .. ' ' end

-- wlInlineTag -- -- Returns [?] with wikilink to errormessage

local function wlInlineTag(phraseID) local sMsg sMsg = ' &#91;' .. wlHelpPage(PHlabel, '? ') .. '&#93; ' return sMsg end

-- errorPhraseIDnotFound -- -- Returns single value when error (not found in list): -- plain value + inline warning [?] (linked) + error cat (mainsp) + preview warning

local function errorPhraseIDnotFound(phraseID) if phraseID == nil then phraseID = '' end local inlineTag = wlInlineTag(phraseID) local previewMsg = wlHelpPage(PHlabel) .. ': \"' .. phraseID .. '\" not found' addPreviewMsg(previewMsg) addErrorCategory return phraseID .. inlineTag end

-- errorHPsetIDnotFound -- -- setID H or P could not be found

local function errorHPsetIDnotFound local sMsg sMsg = wlHelpPage('', PHlabel) .. ': "H" or "P" set id not found' .. ' (please use form like "|H200" or "|P300+P301")' addPreviewMsg(sMsg) addErrorCategory('?') return showPreviewMsg end

-- errorHPsetIDmissing -- -- parameter |setid= to be used

local function errorHPsetIDmissing local sMsg sMsg = wlHelpPage( '', PHlabel) .. ': "H" or "P" set id not found,' .. ' please use |setid=... (H or P)' addPreviewMsg(sMsg) return end

-- formatPhraseAbbr -- -- format phraseID and text, for abbr-form (infobox list form)

local function formatPhraseAbbr(phraseID, sPhrase) return '' .. phraseID .. ' ' end

-- formatPhraseInline -- -- format phraseID and text, for inline form (in sentence) -- adds "quotes"

local function formatPhraseInline(phraseID, sPhrase) return inMono(phraseID) .. ': \"' .. sPhrase .. '\"' end

-- formatPhraseList -- -- as inline, but no "quotes" added.

local function formatPhraseList(phraseID, sPhrase) return inMono(phraseID) .. ': ' .. sPhrase end

-- getSetID -- -- Determines setID (expected either 'H' or 'P') -- First route is: read |setid= -- When |setid= is not set, --		it looks for a first parameter that has an H of P prefix (in |P201|P202|...) --		when not found, 'GHS' is retured -- In one call, P and H numbers can *not* be mixed --		so "|H201|P202|" will cause error "P202 not found" (... in H-list)

local function getSetID(tArgs) local setIDfound = 'GHS' local paramsetID = tArgs['setid'] or nil if (paramsetID ~= nil) and (paramsetID == 'P' or paramsetID == 'H') then setIDfound = paramsetID else local initial = nil for i, v in ipairs(tArgs) do			initial = mw.ustring.match(v, '^[PH]') if initial ~=nil then setIDfound = initial break end end end return setIDfound end

-- getListType -- -- Checks list format, including those from Module:List

local function getListType(tArgs) local listTypes = { ['abbr'] = true, ['bulleted'] = true, ['unbulleted'] = true, ['horizontal'] = true, ['ordered'] = true, ['horizontal_ordered'] = true, ['horizontal ordered'] = true, ['inline'] = true }	local sListType = tArgs['listtype'] or 'abbr'

if sListType == '' or sListType == 'abbr' then return 'abbr' elseif listTypes[sListType] == true then if sListType == 'horizontal ordered' then sListType = 'horizontal_ordered' end return sListType else sListType = 'abbr' end return sListType end

-- getDoOmitRules

local function getDoOmitRules(tArgs) local b = yesno(tArgs['omit'], true) if b == nil then b = true end

return yesno(b, true) end

-- prepareArgs -- -- First: determine setID (from |setID= OR from prefixes in parameters) -- Then: clean up & format phrase IDs (=unnamed parameters) --		remove bad characters, create H/P pattern "H201", "P310+P302" -- straight array, no nil's, sorted

local function prepareArgs(tArgs)

tArgName['setID'] = getSetID(tArgs) tArgName['listtype'] = getListType(tArgs) tArgName['omit'] = getDoOmitRules(tArgs)

tArgs = tTools.compressSparseArray(tArgs) -- removes all named args if string.len(tArgName['setID']) == 1 and #tArgs > 0 then for i, v in ipairs(tArgs) do			v = mw.text.decode(v) v = mw.ustring.gsub(v, '[^%d%+A-Za-z]', '') v = mw.ustring.gsub(v, '^(%d)', tArgName['setID'] .. '%1') v = mw.ustring.gsub(v, '%+(%d)', '+' .. tArgName['setID'] .. '%1') tArgs[i] = v		end table.sort(tArgs) end return tArgs end

-- listAll -- -- Returns wikitable rows for each phrase id. -- requires |setID=P/H -- returns full list, all phrases, for a setID -- 2-columns wikitable, sorted, sortable, anchor like "H201" for each

function r.listAll(frame) local newArgs = getArgs(frame) local tL = {}

prepareArgs(newArgs) local tRead if tArgName['setID'] == 'H' then tRead = GHSdata['Hphrases'] elseif tArgName['setID'] == 'P' then tRead = GHSdata['Pphrases'] else errorHPsetIDmissing return showPreviewMsg end

-- Intermediate table t2 to maintain order; read from original table (/data) local t2 = {} local iPh for s, v in pairs(tRead) do		iPh = tonumber(mw.ustring.match(s, '[PH](%d%d%d)')) if string.len(s) > 4 then iPh = tTools.size(t2) + 1 end table.insert(t2, iPh, s)	end t2 = tTools.compressSparseArray(t2) table.sort(t2)

local sTR, v, sAnchor -- i = array index, s = phraseID, v = phrase text for i, s in ipairs(t2) do		v = tRead[s] sAnchor = ' ' sTR = '|- ' .. sAnchor .. '\n| datasortvalue="' .. i .. '" | ' .. s .. ' || ' .. v		table.insert(tL, sTR) end

return table.concat(tL, '\n') end

-- numberOfPhrases -- -- Documentation -- requires |setID=H/P -- Returns number of phrases, in format --	"GHS H-phrases (123)"

function r.numberOfPhrases(frame) local newArgs = getArgs(frame)

prepareArgs(newArgs)

local iT	if tArgName['setID'] == 'H' then iT = tTools.size(GHSdata['Hphrases']) elseif tArgName['setID'] == 'P' then iT = tTools.size(GHSdata['Pphrases']) else errorHPsetIDmissing return showPreviewMsg end

return 'GHS ' .. PHlabel .. ' (' .. tostring(iT) .. ') ' end

-- listOmitRules -- -- self-documentation

function r.listOmitRules local tRules = GHSdata['tOmitRules'] local tL = {} local s

s = wlHelpPage('Omit Rules') .. ': when the keep ID is present, do not show the omit ID phrase' table.insert(tL, s)	for keep, omit in pairs (tRules) do s = '&bull; keep ' .. inMono(keep) .. ', omit ' .. inMono(omit) table.insert(tL, s)	end return table.concat(tL, ' ') end

-- _main -- -- processes setID (H, P) and phrase codes --		error:	setID not P, H --				code not found -- cannot mix H and P phrases -- reads phrases from /data H or P phrases tables -- formats phrase (abbreviation, abbr-title, phraseID)

function r._main(tArgs)

tArgs = prepareArgs(tArgs) if #tArgs == 0 then return showPreviewMsg -- no content elseif tArgName['setID'] == 'GHS' then return errorHPsetIDnotFound end

tArgs = applyRemoveDuplicates(tArgs) if tArgName['omit'] then tArgs = applyOmitRules(tArgs) end

local formatterF if tArgName['listtype'] == 'abbr' then formatterF = formatPhraseAbbr elseif tArgName['listtype'] == 'inline' then formatterF = formatPhraseInline else --- Module:List options formatterF = formatPhraseList end local tReadD = {} if tArgName['setID'] == 'H' then tReadD = GHSdata['Hphrases'] elseif tArgName['setID'] == 'P' then tReadD = GHSdata['Pphrases'] else return showPreviewMsg end

local sPhrase local tR = {} for i, v in ipairs(tArgs) do		sPhrase = tReadD[v] if sPhrase == nil then table.insert(tR, errorPhraseIDnotFound(tostring(v))) else table.insert(tR, formatterF(v, sPhrase)) end end

if tArgName['listtype'] == 'abbr' then return table.concat(tR, ', ') .. showPreviewMsg elseif tArgName['listtype'] == 'inline' then return table.concat(tR, ', ') .. showPreviewMsg else local mList = require('Module:List') return mList[tArgName['listtype']](tR) .. showPreviewMsg end end

-- main -- -- handles template input frame, then calls generic _main function -- To be invoked from

function r.main(frame) local newArgs = getArgs(frame) return r._main(newArgs) end

return r