Saltu al enhavo

Modulo:eht

El Vikivortaro
 MODULO
Memtesto disponeblas sur la paĝo Ŝablono:eht.

--[===[

MODULE "EHT" (enhavtabelo)

"eo.wiktionary.org/wiki/Modulo:eht" <!--2025-Aug-16-->

Purpose: displays TOC of a cat only if there are at least 300
         pages or at least 300 subcategories, and automatically picks
         correct alphabet based on langname read out from pagename

Utilo: montras enhavtabelon de kategorio nur se disponeblas almenaux 300
       pagxoj aux almenaux 300 subkategorioj, kaj auxtomate elektas
       gxustan alfabeton surbaze de lingvonomo ellegita el pagxonomo

Manfaat: menlihatkan daftar isi kategori ...

Syfte: visar ...

Used by templates / Uzata far sxablonoj /
Digunakan oleh templat / Anvaent av mallar:
* only "eht"

Required submodules / Bezonataj submoduloj /
Submodul yang diperlukan / Behoevda submoduler:
* "loaddata-tbllingvoj" T80 in turn requiring template "tbllingvoj" (EO)
* "loaddata-tblbahasa" T80 in turn requiring template "tblbahasa" (ID)
* "loaddata-tblalfabetoj"  !!!FIXME!!!
* "ind12dim"               !!!FIXME!!!

Required templates:
* "KategorioEHT-etiopia-skribo"  !!!FIXME!!!
* "eht-kat-alfa"                 !!!FIXME!!!

This module can accept parameters whether sent to itself (own frame) or
to the parent (parent's frame). If there is a parameter "parentframe=true"
on the own frame then that own frame is discarded in favor of the
parent's one.

Incoming: * no ordinary parameters
          * 4 hidden named and optional parameters
            * "pagenameoverridetestonly=" -- can directly cause #E01
            * "nsnumberoverridetestonly="
            * "tocforceoverridetestonly=" -- "on" or "off"
            * "detrc=" no error possible

Returned: * either a box or empty or error message, tracking cat:s
            only on error

------------------------------------------------------------------------

Supported and prohibited types of cats and resulting
langcodes and error codes, eval in this order:
* full types (catname is a valid langname, comparison or fetch must be
  case-insensitive on leftmost char):
  * "Sveda" -> #E16 (number of members too low, thus TOC never needed)
  * "Greka antikva" -> #E16 (number of members too low, thus TOC never needed)
* sandwiched types:
  * "Vorto -en- enhavanta morfemon U (-ist)" -> en
  * "Frazo -en- enhavanta vorton (nope)" -> en
  * "Vortgrupo -en- enhavanta (nope)" -> en
* suffixed types:
  * "Geografio laux lingvo" -> eo (langnames consist of Esperanto letters)
* full single exceptions (only needed if NOT "mul" or rule-out essential):
  * "Aldona pago pri unuopa lingvo" -> eo (langnames consist of Esperanto letters)
  * "Sablonaro pri specifa lingvo" -> en (langcodes consist of ASCII letters)
  * "Fundamenta elemento" -> Esperanto (consist of Esperanto letters)
* bracketed types:
  * "Verbo (sveda)" -- word class and lang -> sv
  * "Fiziko (indonezia)" -- field and lang -> id
  * "Tradukoj (franca)" -> fr
  * "Vortfarado (angla)" -> en
* dialect types:
  * "Oseta digora" -> os  !!!FIXME!!! not yet
* anything else:
  * "Verbo" -- word class -> mul
  * "Fiziko" -- field -> mul
This module does NOT bother about word classes or fields, it just
looks at the bracketed part.

Dialect types are difficult to parse, since:
* "Oseta digora" must give os (dialect)
* "Germana auxstra" must give de (dialect)
whereas
* "Greka antikva" must give #E16 (language)

Notes on structure (NOT provided by this module):
* "Kategorio:Oseta digora" is in "Kategorio:Dialekto (oseta)" with
  hint "digora"
* "Kategorio:Oseta irona" is in "Kategorio:Dialekto (oseta)" with
  hint "irona"
* "Kategorio:Germana auxstra" is in "Kategorio:Dialekto (germana)" with
  hint "auxstra"

Proposal A:
* use list of dialects
* check full catname (after check against possible langname) against
  possible dialect (comparison or fetch must be case-insensitive
  on leftmost char)
* if dialect found then fetch the language belonging to it
* can identify unknown dialect

Proposal B:
* check full catname against possible langname (needed to rule
  out "Kategorio:Greka antikva")
* drop word after word from right until either valid langname found
  or nothing left, ie take the longest substring from left that
  is a valid langname
* if langname found then take it
* cannot identify unknown dialect

------------------------------------------------------------------------

Possible errors:
* <<#E01 Internal error in module "eht">>
  Possible causes:
  * strings not uncommented
  * function "mw.title.getCurrentTitle().text" AKA "{{PAGENAME}}" failed
* <<#E02 Malica eraro en subprogramaro uzata far sxablono "eht">>
  Possible causes:
  * submodule "loaddata-tbllingvoj" not found
  * submodule "loaddata-tbllingvoj" caused unspecified failure
* <<#E03 Nombrigita eraro en subprogramaro uzata far sxablono "eht">>
  Possible causes:
  * submodule failed and returned valid error code
* <<#E10 Erara uzo de sxablono (eht), uzebla nur en nomspaco 14 (kategorio)>>
* <<#E11 Evidente nevalida lingvokodo en pagxonomo>>
* <<#E14 Ne esperanta majuskla komenclitero ...
* <<#E15 Erara uzo de sxablono (eht), nevalida aux nekonata
         lingvonomo en pagxonomo>>
* <<#E16 Uzo cxe malpermesita tipo de kategorio>>
* <<#E19 SXablono ne redonis validan rezulton>>  !!!FIXME!!!

Possible tracking cat:s:
* [[Kategorio:Evidente nevalida lingvokodo]]         #E11
* [[Kategorio:Nekonata lingvonomo]]                  #E15
* [[Kategorio:Erara uzo de sxablono]]                #E10 #E19
* [[Kategorio:Erara uzo de sxablono (eht)]]          #E10 #E19
* [[Kategorio:Erara uzo de sxablono (eht, E19)]]     #E10 #E19

]===]

local exporttable = {}

require('strict')

-- ***********************
-- *    CONSTANTS [O]    * ---------------------------------------------
-- ***********************

-- constant table -- ban list -- add obviously invalid access codes (2-letter or 3-letter) only

-- length of the list is NOT stored anywhere, the processing stops
-- when type "nil" is encountered, used by "lfivalidatelnkoadv" only

-- controversial codes (sh sr hr), (zh cmn)
-- "en.wiktionary.org/wiki/Wiktionary:Language_treatment" excluded languages
-- "en.wikipedia.org/wiki/Spurious_languages"
-- "iso639-3.sil.org/code/art" only valid in ISO 639-2
-- "iso639-3.sil.org/code/gem" only valid in ISO 639-2 and 639-5, "collective"
-- "iso639-3.sil.org/code/zxx" "No linguistic content"

local contabisbanned = {}
contabisbanned = {'by','dc','ll','jp','art','deu','eng','epo','fra','gem','ger','ido','lat','por','rus','spa','swe','tup','zxx'} -- 1...19

-- uncommentable (site-related features)

      local constringvoj = 'Modulo:loaddata-tbllingvoj'   -- EO
        -- local constringvoj = 'Modul:loaddata-tblbahasa'   -- ID
      local constrbetoj = 'Modulo:loaddata-tblalfabetoj'  -- EO
        -- local constrbetoj = 'Modul:loaddata-tblabjad"     -- ID

-- uncommentable constant table (error messages)

-- #E02...#E98, holes permitted
-- note that #E0 #E01 #E99 are NOT supposed to be included here
-- separate "strpikparent" needed for "%@"

local contaberaroj = {}
      contaberaroj[02] = 'Malica eraro en subprogramaro uzata far %@'               -- EO #E02
        -- contaberaroj[02] = 'Kesalahan jahat dalam subprogram digunakan oleh %@'     -- ID #E02
      contaberaroj[03] = 'Nombrigita eraro en subprogramaro uzata far %@'           -- EO #E03
        -- contaberaroj[03] = 'Kesalahan ternomor dalam subprogram digunakan oleh %@'  -- ID #E03
      contaberaroj[10] = 'Erara uzo de %@, uzebla nur en nomspaco 14 (kategorio)'                      -- EO #E10
        -- contaberaroj[10] = 'Penggunaan salah %@, hanya bisa digunakan dalam ruang nama 14 (kategori)'  -- ID #E10
      contaberaroj[11] = 'Evidente nevalida lingvokodo sendita al %@'                                  -- EO #E11
        -- contaberaroj[11] = 'Kode bahasa jelas-jelas salah dikirim ke %@'                               -- ID #E11
      contaberaroj[16] = 'Uzo de %@ cxe malpermesita tipo de kategorio'                                -- EO #E16
        -- contaberaroj[16] = 'Penggunaan %@ di tipe kategori yang tidak diperbolehkan'                   -- ID #E16

-- uncommentable constant table (tracking cat:s)

local contabtrako = {}
      contabtrako['E11'] = 'Evidente nevalida lingvokodo'
      contabtrako['E15'] = 'Nekonata lingvonomo'
      contabtrako.cetere = 'Erara uzo de sxablono'

-- uncommentable patterns for cat names for lfhgetlangfromcat

local contabfarado = {} -- 3 types -- there can follow space and script code
contabfarado['Vorto -'    ] = '- enhavanta morfemon ' -- "Vorto -en- enhavanta morfemon U (-ist)" -- BEWARE ends with space
contabfarado['Frazo -'    ] = '- enhavanta vorton ('  -- "Frazo -en- enhavanta vorton (nope)"
contabfarado['Vortgrupo -'] = '- enhavanta ('         -- "Vortgrupo -en- enhavanta (nope)"

local contabfullcat = {}
contabfullcat['Aldona pagxo pri unuopa lingvo'] = 'eo'
contabfullcat['Fundamenta elemento'           ] = 'eo'
contabfullcat['SXablonaro pri specifa lingvo' ] = 'en'

local contabsuffcat = {}
contabsuffcat[' laux lingvo'] = 'eo' -- BEWARE begins with space

-- surrogate transcoding table (only needed for EO)

local contabtransluteo = {}
contabtransluteo[ 67] = 0xC488 -- CX
contabtransluteo[ 99] = 0xC489 -- cx
contabtransluteo[ 71] = 0xC49C -- GX
contabtransluteo[103] = 0xC49D -- gx
contabtransluteo[ 74] = 0xC4B4 -- JX
contabtransluteo[106] = 0xC4B5 -- jx
contabtransluteo[ 83] = 0xC59C -- SX
contabtransluteo[115] = 0xC59D -- sx
contabtransluteo[ 85] = 0xC5AC -- UX breve
contabtransluteo[117] = 0xC5AD -- ux breve

-- constant strings (error circumfixes)

local contabfel = {}
contabfel.mibg = '<span class="error">'     -- mini whining begin
contabfel.labg = '<b>' .. contabfel.mibg    -- lagom whining begin
contabfel.hubg = '<big>' .. contabfel.labg  -- huge whining begin
contabfel.mien = '</span>'                  -- mini whining end
contabfel.laen = contabfel.mien .. '</b>'   -- lagom whining end
contabfel.huen = contabfel.laen .. '</big>' -- huge whining end

-- langcodes with fallbacks

local contabfall = {} -- left column for supported codes and possible targets, other columns fallback codes
contabfall [ 1] = {'sv', 'da', 'fi', 'fit', 'no', 'nb', 'nn'}
contabfall [ 2] = {'ru', 'be', 'bg', 'sr', 'uk'}
contabfall [ 3] = {'de', 'bar', 'lb', 'vo'}
contabfall [ 4] = {'etio', 'am', 'gez', 'om', 'ti', 'tig', 'har'}
contabfall [ 5] = {'en'}
contabfall [ 6] = {'cv'}
contabfall [ 7] = {'eo'}
contabfall [ 8] = {'hy'}
contabfall [ 9] = {'mul'}
contabfall [10] = {'os'}
contabfall [11] = {'rue'}
contabfall [12] = {'sud'}
contabfall [13] = {'tg'}

-- diverse tuning values in separate variables

local conbookodlng = false -- "true" to allow long codes like "zh-min-nan"
local conboomiddig = false -- "true" to allow middle digit like "s7a"

-- uncommentable (override)

-- * name of table MUST always be defined, OTOH elements are usually NOT
-- * for testing only, values automatically peeked otherwise

local contabovrd = {}
  -- contabovrd.sitelang = 'eo'                                    -- "en"
  -- contabovrd.sitelang = 'id'
  -- contabovrd.katprefi = 'Kategorio'                             -- "Category"
  -- contabovrd.katprefi = 'Kategori'
  -- contabovrd.parentfn = string.char(0xC5,0x9C) .. 'ablono:eht'  -- "Template:eht" (!!! no surr translation !!!)
  -- contabovrd.defortrc = true                                    -- force "detrc=true"

-- ****************************************
-- *    SPECIAL STUFF OUTSIDE MAIN [B]    * ----------------------------
-- ****************************************

  -- SPECIAL VAR:S

local qldingvoj = {}     -- type "table" but metaized with some subtables
local qldbetoj = {}      -- type "table" but metaized with some subtables
local qbooguard = false  -- only for the guard test, pass to other var ASAP
local qboodetrc = true   -- from "detrc=true" but default is "true" !!!
local qstrtrace = '<br>' -- for main & proc, debug report request by "detrc="

---- GUARD AGAINST INTERNAL ERROR AND IMPORT ONE VIA LOADDATA ----  !!!FIXME!!! betoj parsecatname

qbooguard = (type(constringvoj)~='string')
if (not qbooguard) then
  qldingvoj = mw.loadData(constringvoj) -- can crash here
  qbooguard = (type(qldingvoj)~='table') -- seems to be always false
end--if

-- ******************************
-- *    DEBUG PROCEDURES [D]    * --------------------------------------
-- ******************************

-- Enhance upvalue "qstrtrace" with fixed text.

-- for variables the other "lfdshowvar" is preferable but in exceptional
-- cases it can be justified to send text with values of variables to this

-- no size limit

-- upvalue "qstrtrace" must NOT be type "nil" on entry (is inited to "<br>")

-- uses upvalue "qboodetrc"

local function prdtracemsg (strshortline)
  if (qboodetrc and (type(strshortline)=='string')) then
    qstrtrace = qstrtrace .. strshortline .. '.<br>' -- dot added !!!
  end--if
end--function prdtracemsg

------------------------------------------------------------------------

---- MATH FUNCTIONS [E] ----

------------------------------------------------------------------------

local function mathisintrange (numzjinput, numzjmin, numzjmax)
  local booisclean = false -- preASSume guilt
  if (type(numzjinput)=='number') then -- no non-numbers, thanks
    if (numzjinput==math.floor(numzjinput)) then -- no transcendental
      booisclean = ((numzjinput>=numzjmin) and (numzjinput<=numzjmax)) -- rang
    end--if
  end--if
  return booisclean
end--function mathisintrange

local function mathdiv (xdividens, xdivisero)
  local resultdiv = 0 -- DIV operator lacks in LUA :-(
  resultdiv = math.floor (xdividens / xdivisero)
  return resultdiv
end--function mathdiv

local function mathmod (xdividendo, xdivisoro)
  local resultmod = 0 -- MOD operator is "%" and bitwise AND operator lack too
  resultmod = xdividendo % xdivisoro
  return resultmod
end--function mathmod

------------------------------------------------------------------------

---- NUMBER CONVERSION FUNCTIONS [N] ----

------------------------------------------------------------------------

local function prnnumto2digit (numzerotoninetynine)

-- Convert integer 0...99 to decimal ASCII string always 2 digits "00"..."99".

-- Depends on procedures :
-- [E] mathisintrange mathdiv mathmod

  local strtwodig = '??' -- always 2 digits
  if (mathisintrange(numzerotoninetynine,0,99)) then
    strtwodig = tostring(mathdiv(numzerotoninetynine,10)) .. tostring(mathmod(numzerotoninetynine,10))
  end--if
  return strtwodig
end--function prnnumto2digit

-- *****************************************
-- *    LOW LEVEL STRING PROCEDURES [G]    * ---------------------------
-- *****************************************

local function prgstringrange (varvictim, nummini, nummaxi)
  local nummylengthofstr = 0
  local booveryvalid = false -- preASSume guilt
  if (type(varvictim)=='string') then
    nummylengthofstr = string.len(varvictim)
    booveryvalid = ((nummylengthofstr>=nummini) and (nummylengthofstr<=nummaxi))
  end--if
  return booveryvalid
end--function prgstringrange

------------------------------------------------------------------------

-- Local function LFGPOKESTRING

-- Replace single octet in a string.

-- Input  : * strinpokeout -- empty legal
--          * numpokepoz   -- ZERO-based, out of range legal
--          * numpokeval   -- new value

-- This is inefficient by design of LUA. The caller is responsible to
-- minimize the number of invocations of this, in particular, not to
-- call if the new value is equal the existing one.

local function lfgpokestring (strinpokeout, numpokepoz, numpokeval)
  local numpokelen = 0
  numpokelen = string.len(strinpokeout)
  if ((numpokelen==1) and (numpokepoz==0)) then
    strinpokeout = string.char(numpokeval) -- totally replace
  end--if
  if (numpokelen>=2) then
    if (numpokepoz==0) then
      strinpokeout = string.char(numpokeval) .. string.sub (strinpokeout,2,numpokelen)
    end--if
    if ((numpokepoz>0) and (numpokepoz<(numpokelen-1))) then
      strinpokeout = string.sub (strinpokeout,1,numpokepoz) .. string.char(numpokeval) .. string.sub (strinpokeout,(numpokepoz+2),numpokelen)
    end--if
    if (numpokepoz==(numpokelen-1)) then
      strinpokeout = string.sub (strinpokeout,1,(numpokelen-1)) .. string.char(numpokeval)
    end--if
  end--if (numpokelen>=2) then
  return strinpokeout
end--function lfgpokestring

------------------------------------------------------------------------

local function lfgtestnum (numkaad)
  local boodigit = false
  boodigit = ((numkaad>=48) and (numkaad<=57))
  return boodigit
end--function lfgtestnum

local function lfgtestuc (numkode)
  local booupperc = false
  booupperc = ((numkode>=65) and (numkode<=90))
  return booupperc
end--function lfgtestuc

local function lfgtestlc (numcode)
  local boolowerc = false
  boolowerc = ((numcode>=97) and (numcode<=122))
  return boolowerc
end--function lfgtestlc

------------------------------------------------------------------------

-- Local function LFGCMPBEG

-- Check whether a given substring can be found at begin of a longer
-- string (equality optionally valid).

-- Input  : * strbig, strtiny
--          * booalso6ekv -- allow equality

local function lfgcmpbeg (strbig, strtiny, booalso6ekv)
  local numlen6big = 0
  local numlen6tiny = 0
  local boof6ound = false
  numlen6big = string.len (strbig)
  numlen6tiny = string.len (strtiny)
  if (numlen6tiny>0) then
    if ((numlen6tiny<numlen6big) or (booalso6ekv and (numlen6tiny==numlen6big))) then
      boof6ound = ( strtiny == (string.sub (strbig,1,numlen6tiny)) )
    end--if
  end--if
  return boof6ound
end--function lfgcmpbeg

------------------------------------------------------------------------

-- Local function LFGCMPEND

-- Check whether a given substring can be found at end of a longer
-- string (equality optionally valid).

-- Input  : * strbig, strtiny
--          * booalso7ekv -- allow equality

local function lfgcmpend (strbig, strtiny, booalso7ekv)
  local numlen7big = 0
  local numlen7tiny = 0
  local boof7ound = false
  numlen7big = string.len (strbig)
  numlen7tiny = string.len (strtiny)
  if (numlen7tiny>0) then
    if ((numlen7tiny<numlen7big) or (booalso7ekv and (numlen7tiny==numlen7big))) then
      boof7ound = ( strtiny == (string.sub (strbig,(numlen7big-numlen7tiny+1),numlen7big)) )
    end--if
  end--if
  return boof7ound
end--function lfgcmpend

------------------------------------------------------------------------

---- UTF8 FUNCTIONS [U] ----

------------------------------------------------------------------------

-- Local function LFULNUTF8CHAR

-- Evaluate length of a single UTF8 char in octet:s.

-- Input  : * numbgoctet  -- beginning octet of a UTF8 char

-- Output : * numlen1234x -- unit octet, number 1...4, or ZERO if invalid

-- Does NOT thoroughly check the validity, looks at ONE octet only.

local function lfulnutf8char (numbgoctet)
  local numlen1234x = 0
    if (numbgoctet<128) then
      numlen1234x = 1 -- $00...$7F -- ANSI/ASCII
    end--if
    if ((numbgoctet>=194) and (numbgoctet<=223)) then
      numlen1234x = 2 -- $C2 to $DF
    end--if
    if ((numbgoctet>=224) and (numbgoctet<=239)) then
      numlen1234x = 3 -- $E0 to $EF
    end--if
    if ((numbgoctet>=240) and (numbgoctet<=244)) then
      numlen1234x = 4 -- $F0 to $F4
    end--if
  return numlen1234x
end--function lfulnutf8char

------------------------------------------------------------------------

local function lfusearchcasepair (strmyset, num16valin)

-- Search pair of undecoded UTF8 values (UPPER, lower) based on ONE
-- value (unrolled loop).

-- Input  : * strmyset -- "eo" or "sv" or (no "GENE" no "ASCII" here)
--          * num16valin -- undecoded UINT16BE value, $C200... $DEFF

-- Output : * num16upper, num16lower -- ZERO if nothing found

-- Called from lfucompareutf8wci lfutristletr lfucaseallinone
-- lfucomparecibegin lficaseadvjaem.

  local num16upper = 0
  local num16lower = 0

  local function xxxdicompare (xxxupper, xxxlower) -- only 3 upvalues used
    local xxxfound = false -- preASSume no hit
    if ((num16valin==xxxupper) or (num16valin==xxxlower)) then
      num16upper = xxxupper
      num16lower = xxxlower
      xxxfound = true
    end--if
  end--function xxxdicompare

  while true do -- fake loop
    if (strmyset~='eo') then
      break -- do NOT check
    end--if
    if (xxxdicompare(0xC488,0xC489)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC49C,0xC49D)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC4A4,0xC4A5)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC4B4,0xC4B5)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC59C,0xC59D)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC5AC,0xC5AD)) then
      break -- found it
    end--if
    break -- finally
  end--while -- fake loop

  while true do -- fake loop
    if (strmyset~='sv') then
      break -- do NOT check
    end--if
    if (xxxdicompare(0xC384,0xC3A4)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC385,0xC3A5)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC389,0xC3A9)) then
      break -- found it
    end--if
    if (xxxdicompare(0xC396,0xC3B6)) then
      break -- found it
    end--if
    break -- finally
  end--while -- fake loop

  return num16upper, num16lower

end--function lfusearchcasepair

------------------------------------------------------------------------

local function lfutristletr (strsel4set, strin4trist)

-- Evaluate char to tristate result (no letter vs uppercase letter
-- vs lowercase letter) within defined charset (ASCII + selectable
-- extra subset of UTF8).

-- Input  : * strsel4set  : "ASCII" (default, empty string or type "nil"
--                          will do too) "eo" "sv" (value "GENE" NOT here)      !!!FIXME!!! is GENE supposed to be supp or not ??
--          * strin4trist : single unicode char (1 or 2 octet:s) or
--                          longer string

-- Output : * numtype4x : 0 no letter or invalid UTF8 -- 1 upper -- 2 lower

-- Depends on procedures : (this is LFUTRISTLETR)
-- [U] lfulnutf8char lfusearchcasepair
-- [G] lfgtestuc lfgtestlc

-- Possible further char:s or fragments of such are disregarded, the
-- question answered is "Is there one uppercase or lowercase letter
-- available at begin?".

local numlong4den = 0 -- actual length of input string

local numcxa4unde = 0 -- undecoded UINT16BE of incoming
local numw4upper = 0 -- undecoded UINT16BE of found lower
local numw4lower = 0 -- undecoded UINT16BE of found UPPER

local numlong4bor = 0 -- expected length of single char
local numcha4r = 0 -- UINT8 beginning char
local numcha4s = 0 -- UINT8 later char (BIG ENDIAN, lower value here above)

local numtype4x = 0 -- final result to be returned, preASSume invalid

  while true do -- fake loop -- this is LFUTRISTLETR

    numlong4den = string.len (strin4trist)
    if (numlong4den==0) then
      break -- bad string length
    end--if
    numcha4r = string.byte (strin4trist,1,1)
    numlong4bor = lfulnutf8char(numcha4r)
    if ((numlong4bor==0) or (numlong4den<numlong4bor)) then
      break -- truncated char or invalid
    end--if

    if (numlong4bor==1) then
      if (lfgtestuc(numcha4r)) then
        numtype4x = 1
      end--if
      if (lfgtestlc(numcha4r)) then
        numtype4x = 2
      end--if
      break -- success ASCII
    end--if

    if (numlong4bor==2) then
      numcha4s = string.byte (strin4trist,2,2) -- only $80 to $BF cannot ovrfl
      numcxa4unde = numcha4r * 256 + numcha4s -- UINT16BE
      numw4upper, numw4lower = lfusearchcasepair (strsel4set,numcxa4unde)
      if (numcxa4unde==numw4upper) then
        numtype4x = 1
      end--if
      if (numcxa4unde==numw4lower) then
        numtype4x = 2
      end--if
    end--if

    break -- finally
  end--while -- fake loop -- join mark

return numtype4x

end--function lfutristletr

------------------------------------------------------------------------

local function lfucaserest (strsel6set, strinco6cs, boowup6cas, boodo6all)

-- Adjust (restricted) case of a single letter or longer string within
-- defined charset (ASCII + selectable minimal subset of UTF8). (this is REST)

-- Input  : * strsel6set : "ASCII" (default, empty string or type "nil"
--                         will do too) "eo" "sv" (value "GENE" NOT here)
--          * strinco6cs : single unicode letter (1 or 2 octet:s) or
--                         longer string
--          * boowup6cas : for desired output uppercase "true" and for
--                         lowercase "false"
--          * boodo6all  : "true" to adjust all letters, "false"
--                         only beginning letter

-- Output : * strinco6cs

-- Depends on procedures : (this is REST)
-- [U] lfulnutf8char lfusearchcasepair
-- [G] lfgpokestring lfgtestuc lfgtestlc

-- This process never changes the length of a string in octet:s. Empty string
-- on input is legal and results in an empty string returned. When case is
-- adjusted, a 1-octet or 2-octet letter is replaced by another letter of same
-- length. Unknown seemingly valid char:s (1-octet ... 4-octet) are copied.
-- Broken UTF8 stream results in remaining part of the output string (from
-- one char to complete length of the incoming string) filled by "Z".

-- We peek max 2 values per iteration, and change the string in-place, doing
-- so strictly only if there indeed is a change. This is important for LUA
-- where the in-place write access must be emulated by means of a less
-- efficient procedure.

local numlong6den = 0 -- actual length of input string
local numokt6index = 0

local numcxa6unde = 0 -- undecoded UINT16BE of incoming
local numw6upper = 0 -- undecoded UINT16BE of found lower
local numw6lower = 0 -- undecoded UINT16BE of found UPPER

local numlong6bor = 0 -- expected length of single char

local numdel6ta = 0 -- quasi-signed ZERO or +1 or -1 or +32 or -32 or other

local numcha6r = 0 -- UINT8 beginning char
local numcha6s = 0 -- UINT8 later char (BIG ENDIAN, lower value here above)

local boowan6tlowr = false

local boodo6adj = true -- preASSume innocence -- continue changing
local boobotch6d = false -- preASSume innocence -- NOT yet botched

  boowup6cas = (boowup6cas==true)
  boodo6all = (boodo6all==true)
  boowan6tlowr = (not boowup6cas)
  numlong6den = string.len (strinco6cs)

  while true do -- outer genuine loop over incoming string (this is REST)

    if (numokt6index>=numlong6den) then
      break -- done complete string
    end--if
    if ((not boodo6all) and (numokt6index~=0)) then -- loop can skip index ONE
      boodo6adj = false
    end--if
    numdel6ta   = 0 -- preASSume on every iteration
    numlong6bor = 1 -- preASSume on every iteration

    while true do -- inner fake loop (this is REST)

      numcha6r = string.byte (strinco6cs,(numokt6index+1),(numokt6index+1))
      if (boobotch6d) then
        numdel6ta = 90 - numcha6r -- "Z" -- delta must be non-ZERO to write
        break -- fill with "Z" char:s
      end--if
      if (not boodo6adj) then
        break -- copy octet after octet
      end--if
      numlong6bor = lfulnutf8char(numcha6r) -- now 1 ... 4 or ZERO
      if ((numlong6bor==0) or ((numokt6index+numlong6bor)>numlong6den)) then
        numlong6bor = 1 -- reassign to ONE !!!
        numdel6ta = 90 - numcha6r -- "Z" -- delta must be non-ZERO to write
        boobotch6d = true
        break -- truncated char or broken stream
      end--if
      if (numlong6bor>=3) then
        break -- copy UTF8 char, no chance for adjustment
      end--if

      if (numlong6bor==1) then
        if (lfgtestuc(numcha6r) and boowan6tlowr) then
          numdel6ta = 32 -- ASCII UPPER->lower
        end--if
        if (lfgtestlc(numcha6r) and boowup6cas) then
          numdel6ta = -32 -- ASCII lower->UPPER
        end--if
        break -- success with ASCII and one char almost done
      end--if (numlong6bor==1) then

      if (numlong6bor==2) then
        numcha6s = string.byte (strinco6cs,(numokt6index+2),(numokt6index+2)) -- only $80 to $BF
        numcxa6unde = numcha6r * 256 + numcha6s -- UINT16BE
        numw6upper, numw6lower = lfusearchcasepair (strsel6set,numcxa6unde)
        if ((numcxa6unde==numw6upper) and boowan6tlowr) then
          numdel6ta = numw6lower-numw6upper -- UPPER->lower
        end--if
        if ((numcxa6unde==numw6lower) and boowup6cas) then
          numdel6ta = numw6upper-numw6lower -- lower->UPPER
        end--if
      end--if (numlong6bor==2) then

      break -- finally -- unknown non-ASCII char is a fact :-(
    end--while -- inner fake loop -- join mark (this is REST)

    if ((numlong6bor==1) and (numdel6ta~=0)) then -- no risk of carry here
      strinco6cs = lfgpokestring (strinco6cs,numokt6index,(numcha6r+numdel6ta))
    end--if
    if ((numlong6bor==2) and (numdel6ta~=0)) then -- no risk of carry here
      strinco6cs = lfgpokestring (strinco6cs,(numokt6index+1),(numcha6s+numdel6ta))
    end--if
    numokt6index = numokt6index + numlong6bor -- advance in incoming string

  end--while -- outer genuine loop over incoming string (this is REST)

return strinco6cs

end--function lfucaserest

------------------------------------------------------------------------

---- HIGH LEVEL STRING FUNCTIONS [I] ----

------------------------------------------------------------------------

-- Local function LFIKATALDIGU  !!!FIXME!!! use LFIKATPALDIGU

local function lfikataldigu (strprefixx, strkataldnomo, strhintvisi)
  local strrbkma = ''
  if (type(strhintvisi)=='string') then
    strrbkma = '[[' .. strprefixx .. ':' .. strkataldnomo .. '|' .. strhintvisi .. ']]'
  else
    strrbkma = '[[' .. strprefixx .. ':' .. strkataldnomo .. ']]'
  end--if
  return strrbkma
end--function lfikataldigu

------------------------------------------------------------------------

-- Local function LFISEPBRACKET

-- Separate bracketed part of a string and return the inner and outer
-- part, the outer one with the brackets. There must be exactly ONE "("
-- and exactly ONE ")" in correct order.

-- Input  : * strsep33br
--          * numxmin33len -- minimal length of inner part, must be >= 1

-- Output : * boosaxes, strinner, strouter

-- Note that for length of hit ZERO ie "()" we would have "begg" + 1 = "endd"
-- and for length of hit ONE ie "(x)" we have "begg" + 2 = "endd".

-- Example: "crap (NO)" -> len = 9
--           123456789
-- "begg" = 6 and "endd" = 9
-- Expected result: "NO" and "crap ()"

-- Example: "(XX) YES" -> len = 8
--           12345678
-- "begg" = 1 and "endd" = 4
-- Expected result: "XX" and "() YES"

local function lfisepbracket (strsep33br, numxmin33len)

  local strinner = ''
  local strouter = ''
  local num33idx = 1 -- ONE-based
  local numdlong = 0
  local num33wesel = 0
  local numbegg = 0 -- ONE-based, ZERO invalid
  local numendd = 0 -- ONE-based, ZERO invalid
  local boosaxes = false -- preASSume guilt

  numdlong = string.len (strsep33br)
  while true do
    if (num33idx>numdlong) then
      break -- ONE-based -- if both "numbegg" "numendd" non-ZERO then maybe
    end--if
    num33wesel = string.byte(strsep33br,num33idx,num33idx)
    if (num33wesel==40) then -- "("
      if (numbegg==0) then
        numbegg = num33idx -- pos of "("
      else
        numbegg = 0
        break -- damn: more than 1 "(" present
      end--if
    end--if
    if (num33wesel==41) then -- ")"
      if ((numendd==0) and (numbegg~=0) and ((numbegg+numxmin33len)<num33idx)) then
        numendd = num33idx -- pos of ")"
      else
        numendd = 0
        break -- damn: more than 1 ")" present or ")" precedes "("
      end--if
    end--if
    num33idx = num33idx + 1
  end--while

  if ((numbegg~=0) and (numendd~=0)) then
    boosaxes = true
    strouter = string.sub(strsep33br,1,numbegg) .. string.sub(strsep33br,numendd,-1)
    strinner = string.sub(strsep33br,(numbegg+1),(numendd-1))
  end--if

  return boosaxes, strinner, strouter

end--function lfisepbracket

------------------------------------------------------------------------

-- Local function LFIVALIDATELNKOADV

-- Advanced test whether a string (intended to be a langcode) is valid
-- containing only 2 or 3 lowercase letters, or 2...10 char:s and with some
-- dashes, or maybe a digit in middle position or maybe instead equals to "-"
-- or "??" and maybe additionally is not included on the ban list.

-- Input  : * strqooq -- string (empty is useless and returns
--                       "true" ie "bad" but cannot cause any major harm)
--          * booyesdsh -- "true" to allow special code dash "-"
--          * booyesqst -- "true" to allow special code doublequest "??"
--          * booloonkg -- "true" to allow long codes such as "zh-min-nan"
--          * boodigit -- "true" to allow digit in middle position
--          * boonoban -- (inverted) "true" to skip test against ban table

-- Output : * booisvaladv -- true if string is valid

-- Depends on procedures :
-- [G] lfgtestnum lfgtestlc

-- Depends on constants :
-- * table "contabisbanned"

-- Incoming empty string is safe but type "nil" is NOT.

-- Digit is tolerable only ("and" applies):
-- * if boodigit is "true"
-- * if length is 3 char:s
-- * in middle position

-- Dashes are tolerable (except in special code "-") only ("and" applies):
-- * if length is at least 4 char:s (if this is permitted at all)
-- * in inner positions
-- * NOT adjacent
-- * maximally TWO totally
-- There may be maximally 3 adjacent letters, this makes at least ONE dash
-- obligatory for length 4...7, and TWO dashes for length 8...10.

local function lfivalidatelnkoadv (strqooq, booyesdsh, booyesqst, booloonkg, boodigit, boonoban)

  local varomongkosong = 0 -- for check against the ban list
  local numchiiar = 0
  local numukurran = 0
  local numindeex = 0 -- ZERO-based -- two loops
  local numadjlet = 0 -- number of adjacent letters (max 3)
  local numadjdsh = 0 -- number of adjacent dashes (max 1)
  local numtotdsh = 0 -- total number of dashes (max 2)
  local booislclc = false
  local booisdigi = false
  local booisdash = false
  local booisvaladv = true -- preASSume innocence -- later final verdict here

  while true do -- fake (outer) loop

    if (strqooq=='-') then
      booisvaladv = booyesdsh
      break -- to join mark -- good or bad
    end--if
    if (strqooq=='??') then
      booisvaladv = booyesqst
      break -- to join mark -- good or bad
    end--if
    numukurran = string.len (strqooq)
    if ((numukurran<2) or (numukurran>10)) then
      booisvaladv = false
      break -- to join mark -- evil
    end--if
    if (not booloonkg and (numukurran>3)) then
      booisvaladv = false
      break -- to join mark -- evil
    end--if

    numindeex = 0
    while true do -- inner genuine loop over char:s
      if (numindeex>=numukurran) then
        break -- done -- good
      end--if
      numchiiar = string.byte (strqooq,(numindeex+1),(numindeex+1))
      booisdash = (numchiiar==45)
      booisdigi = lfgtestnum(numchiiar)
      booislclc = lfgtestlc(numchiiar)
      if (not (booislclc or booisdigi or booisdash)) then
        booisvaladv = false
        break -- to join mark -- inherently bad char
      end--if
      if (booislclc) then
        numadjlet = numadjlet + 1
      else
        numadjlet = 0
      end--if
      if (booisdigi and ((numukurran~=3) or (numindeex~=1) or (not boodigit))) then
        booisvaladv = false
        break -- to join mark -- illegal digit
      end--if
      if (booisdash) then
        if ((numukurran<4) or (numindeex==0) or ((numindeex+1)==numukurran)) then
          booisvaladv = false
          break -- to join mark -- illegal dash
        end--if
        numadjdsh = numadjdsh + 1
        numtotdsh = numtotdsh + 1 -- total
      else
        numadjdsh = 0 -- do NOT zeroize the total !!!
      end--if
      if ((numadjlet>3) or (numadjdsh>1) or (numtotdsh>2)) then
        booisvaladv = false
        break -- to join mark -- evil
      end--if
      numindeex = numindeex + 1 -- ZERO-based
    end--while -- inner genuine loop over char:s

    if (not boonoban) then -- if "yesban" then
      numindeex = 0
      while true do -- lower inner genuine loop
        varomongkosong = contabisbanned[numindeex+1] -- number of elem unknown
        if (type(varomongkosong)~='string') then
          break -- abort inner loop (then outer fake loop) due to end of table
        end--if
        numukurran = string.len (varomongkosong)
        if ((numukurran<2) or (numukurran>3)) then
          break -- abort inner loop (then outer fake loop) due to faulty table
        end--if
        if (strqooq==varomongkosong) then
          booisvaladv = false
          break -- abort inner loop (then outer fake loop) due to violation
        end--if
        numindeex = numindeex + 1 -- ZERO-based
      end--while -- lower inner genuine loop
    end--if (not boonoban) then

    break -- finally to join mark
  end--while -- fake loop -- join mark

  return booisvaladv

end--function lfivalidatelnkoadv

-- *******************************************
-- *    HIGH LEVEL STRING PROCEDURES [I7]    * -- placeholderism
-- *******************************************

local function pripl2altwre (strbeforfill, numaskikodo, varsupstitu)

-- Process all anon and fixed placeholders "%@" or "%~".

-- Input  : * strbeforfill -- request string with placeholders to be filled
--                            in, no placeholders or empty input is useless
--                            but cannot cause major harm
--          * numaskikodo  -- ASCII code of placeholder type, 64 for "%@" or
--                            126 for "%~"
--          * varsupstitu  -- substitute, either string (same content reused
--                            if multiple placeholders), or ZERO-based table
--                            (with one element per placeholder such as
--                            {[0]="none","neniu"}), length 1...80, further
--                            sanitization must be done elsewhere

-- Output : * strafterfill

-- Depends on procedures :
-- [G] prgstringrange

local varpfiller    = 0  -- risky picking
local strufiller    = '' -- final validated filler
local strafterfill  = ''
local numlenbigtext = 0  -- len of strbeforfill
local numsfrcindex  = 0  -- char index ZERO-based
local numinsrtinde  = 0  -- index in table ZERO-based
local numtecken0d   = 0
local numtecken1d   = 0

  numlenbigtext = string.len (strbeforfill)

  while true do
    if (numsfrcindex>=numlenbigtext) then
      break -- empty input is useless but cannot cause major harm
    end--if
    numtecken0d = string.byte(strbeforfill,(numsfrcindex+1),(numsfrcindex+1))
    numsfrcindex = numsfrcindex + 1 -- INC here
    numtecken1d = 0 -- preASSume none
    if (numsfrcindex<numlenbigtext) then -- pick but do NOT INC
      numtecken1d = string.byte(strbeforfill,(numsfrcindex+1),(numsfrcindex+1))
    end--if
    if ((numtecken0d==37) and (numtecken1d==numaskikodo)) then -- "%@" "%~"
      numsfrcindex = numsfrcindex + 1 -- INC more, now totally + 2
      varpfiller = 0 -- preASSume nothing available
      strufiller = '??' -- preASSume nothing available
      if (type(varsupstitu)=='string') then
        varpfiller = varsupstitu -- take it as-is (length check below)
      end--if
      if (type(varsupstitu)=='table') then
        varpfiller = varsupstitu [numinsrtinde] -- risk of type "nil"
        numinsrtinde = numinsrtinde + 1 -- INC tab index on every placeholder
      end--if
      if (prgstringrange(varpfiller,1,80)) then -- restrict
        strufiller = varpfiller -- now the substitute is finally accepted
      end--if
    else
      strufiller = string.char (numtecken0d) -- no placeholder -> copy octet
    end--if
    strafterfill = strafterfill .. strufiller -- add one of 4 possible cases
  end--while

return strafterfill

end--function pripl2altwre

------------------------------------------------------------------------

-- Transcode eo X-surrogates to cxapeloj in a single string (eo only).

-- Input  : * streosurr -- ANSI string (empty is useless but cannot
--                                      cause major harm)

-- Output : * strutf8eo -- UTF8 string

-- Depends on procedures :
-- [E] mathdiv mathmod

-- Depends on constants :
-- * table "contabtransluteo" inherently holy

-- To be called ONLY from "prhrecusurrstrtab".

-- * the "x" in a surr pair is case insensitive,
--   for example both "kacxo" and "kacXo" give same result
-- * avoid "\", thus for example "ka\cxo" would get converted but the "\" kept
-- * double "x" (both case insensitive) prevents conversion and becomes
--   reduced to single "x", for example "kacxxo" becomes "kacxo"

local function prikodeosg (streosurr)

  local vareopeek = 0
  local strutf8eo = ''
  local numeoinplen = 0
  local numinpinx = 0 -- ZERO-based source index
  local numknar0k = 0 -- current char
  local numknaf1x = 0 -- next char (ZERO is NOT valid)
  local numknaf2x = 0 -- post next char (ZERO is NOT valid)
  local boonext1x = false
  local boonext2x = false
  local boosudahdone = false

  numeoinplen = string.len(streosurr)

  while true do

    if (numinpinx>=numeoinplen) then
      break
    end--if

    numknar0k = string.byte(streosurr,(numinpinx+1),(numinpinx+1))
    numknaf1x = 0 -- preASSume no char
    numknaf2x = 0 -- preASSume no char
    if ((numinpinx+1)<numeoinplen) then
      numknaf1x = string.byte(streosurr,(numinpinx+2),(numinpinx+2))
    end--if
    if ((numinpinx+2)<numeoinplen) then
      numknaf2x = string.byte(streosurr,(numinpinx+3),(numinpinx+3))
    end--if

    boonext1x = ((numknaf1x==88) or (numknaf1x==120)) -- case insensitive
    boonext2x = ((numknaf2x==88) or (numknaf2x==120)) -- case insensitive
    boosudahdone = false
    if (boonext1x and boonext2x) then -- got "xx"
      strutf8eo = strutf8eo .. string.char(numknar0k,numknaf1x) -- keep one "x" only
      numinpinx = numinpinx + 3 -- eaten 3 written 2
      boosudahdone = true
    end--if
    if (boonext1x and (not boonext2x)) then -- got yes-"x" and no-"x"
      vareopeek = contabtransluteo[numknar0k] -- UINT16 or type "nil"
      if (type(vareopeek)=='number') then
        strutf8eo = strutf8eo .. string.char(mathdiv(vareopeek,256),mathmod(vareopeek,256)) -- add UTF8 char
        numinpinx = numinpinx + 2 -- eaten 2 written 2
        boosudahdone = true
      end--if
    end--if
    if (not boosudahdone) then
      strutf8eo = strutf8eo .. string.char(numknar0k) -- copy char
      numinpinx = numinpinx + 1 -- eaten 1 written 1
    end--if

  end--while

  return strutf8eo

end--function prikodeosg

-- ***********************************
-- *    HIGH LEVEL PROCEDURES [H]    * ---------------------------------
-- ***********************************

-- Depends on procedures :
-- [E] mathisintrange

local function lfhvali1status98code (varvalue)
  local boovalid = false -- preASSume guilt
  while true do -- fake loop
    if (varvalue==0) then
      break -- success thus keep false since no valid ;-)
    end--if
    if (mathisintrange(varvalue,1,98)) then
      boovalid = true -- got an error and valid error code returned
    else
      varvalue = 255 -- failed to return valid status code
    end--if
    break -- finally to join mark
  end--while -- fake loop -- join mark
  return varvalue, boovalid
end--function lfhvali1status98code

------------------------------------------------------------------------

local function prhrecusurrstrtab (varinkommen, strlingkod, bookeys)

-- Process (transcode) either a single string, or all string items in a
-- table (even nested) using any type of keys/indexes (such as a holey
-- number sequence and non-numeric ones). Items with a non-string non-table
-- value are kept unchanged. Optional transcoding of eo and NOPE sv surrogates
-- (via 2 separate procedures). Optionally string keys/indexes are transcoded
-- as well.

-- Input  : * varinkommen -- type "string" or "table"
--          * strlingkod -- langcode "eo"
--          * bookeys -- transcode keys too (preferably either "true"
--                       or type "nil")

-- Depends on procedures :
-- [I5] prikodeosg (only if trans of eo X-surrogates desired)
-- [I5] NOPE prikodsvsg
-- [E] mathdiv mathmod (via "prikodeosg" and NOPE "prikodsvsg")

-- Depends on constants :
-- * table "contabtransluteo" inherently holey (via "prikodeosg")
-- * NOPE table "contabtranslutsv"

-- We always fully rebrew tables from scratch, thus do NOT replace
-- single elements (doing so would break "in pairs").

local varnky = 0 -- variable without type
local varutmatning = 0
local boodone = false

  if (type(varinkommen)=='string') then
    if (strlingkod=='eo') then
      varutmatning = prikodeosg (varinkommen) -- surr
      boodone = true
    end--if
    -- if (strlingkod=='sv') then
      -- varutmatning = prikodsvsg (varinkommen) -- surr
      -- boodone = true
    -- end--if
  end--if

  if (type(varinkommen)=='table') then
    varutmatning = {} -- brew new table from scratch
    for k4k,v4v in pairs(varinkommen) do -- nothing done if table empty
      if ((bookeys==true) and (type(k4k)=='string')) then
        varnky = prhrecusurrstrtab (k4k, strlingkod, nil) -- RECURSION
      else
        varnky = k4k
      end--if
      if ((type(v4v)=='string') or (type(v4v)=='table')) then
        v4v = prhrecusurrstrtab (v4v, strlingkod, bookeys) -- RECURSION
      end--if
      varutmatning[varnky] = v4v -- write same or diff place in dest table
    end--for
    boodone = true
  end--if

  if (not boodone) then
    varutmatning = varinkommen -- copy as-is whatever it is, useless
  end--if

return varutmatning

end--function prhrecusurrstrtab

-- ************************************                   error handling
-- *    HIGH LEVEL PROCEDURES [H4]    * --------------------------------
-- ************************************

local function prhconstructerar (numerar3code, boopeek3it)

-- Construct partial error message maybe peeking description.

-- Input  : * numerar3code -- 1 ... 98 or 2 ... 98 (resistant against
--                            invalid data type, giving "??" on such)
--          * boopeek3it   -- do peek description #E02...#E98 from table

-- Depends on procedures :
-- [N] prnnumto2digit
-- [E] mathisintrange mathdiv mathmod

-- Depends on constants :
-- * maybe table contaberaroj TWO-based (holes permitted)

-- To be called ONLY from PRHBREWERR4HUNP PRHBREWERR5HUPA
-- PRHBREWERR6SLNP PRHBREWERR7SLPA PRHBREWERR8SUBM PRHBREWERR9DETA.

local vardes3krip = 0
local numbottom3limit = 1
local stryt3sux = '#E'

  if (boopeek3it) then
    numbottom3limit = 2 -- #E01 is a valid code for submodule only
  end--if
  if (mathisintrange(numerar3code,numbottom3limit,98)) then
    stryt3sux = stryt3sux .. prnnumto2digit(numerar3code)
    if (boopeek3it) then
      vardes3krip = contaberaroj[numerar3code] -- risk of type "nil"
      if (type(vardes3krip)=='string') then
        stryt3sux = stryt3sux .. ' ' .. vardes3krip
      else
        stryt3sux = stryt3sux .. ' ??' -- no text found
      end--if
    end--if (boopeek3it) then
  else
    stryt3sux = stryt3sux .. '??' -- no valid error code
  end--if

return stryt3sux

end--function prhconstructerar

------------------------------------------------------------------------

local function prhbrewerr5hupa (numeror5code, strparent5nm)

-- Brew error sev huge, one line, insertable parent.

-- Input  : * numeror5code -- TWO-based error code 2 ... 98 (resistant
--                            against invalid data type, giving "??" on such)
--          * strparent5nm -- name of parent (not used and should be type
--                            "nil" if message does NOT contain "%@")

-- Output : * stryt5sux -- message with HTML

-- Depends on procedures :
-- [H4] prhconstructerar
-- [I7] pripl2altwre
-- [G] prgstringrange
-- [N] prnnumto2digit
-- [E] mathisintrange mathdiv mathmod

-- Depends on constants :
-- * table contabfel with 2 elements .hubg .huen
-- * table contaberaroj TWO-based (holes permitted)

-- #E02...#E98, note that #E00 #E01 #E99 are NOT supposed to be included here.

local stryt5sux = ''

  stryt5sux = contabfel.hubg .. pripl2altwre(prhconstructerar(numeror5code,true),64,strparent5nm) .. contabfel.huen

return stryt5sux

end--function prhbrewerr5hupa

-- ************************************
-- *    HIGH LEVEL PROCEDURES [H6]    * --------------------------------
-- ************************************

local function lfhgetlangfromcat(strcatname)

-- Input  : * strcatname -- without ns prefix

-- Output : * strlingvo -- nothing or langcode or langname
--          * numtristato -- ZERO nothing | ONE langcode (2...10) |
--                           TWO langname (>=3)

-- Depends on procedures :
-- [I] lfisepbracket
-- [G] lfgcmpend
-- [E] mathisintrange

-- Depends on constants :
-- * contabfarado contabfullcat contabsuffcat

-- Chain of rules applied here (part of the main parsing work):
-- * two fragments of catname found in contabfarado -> get langcode
--   (len 2...10) sandwiched beween them
-- * full catname is found in contabfullcat -> get langcode from there
-- * catname is suffixed by item found contabsuffcat -> get langcode
--   from there
-- * there is bracketed part (len >= 3 char:s) -> take it as langname
-- * else nothing found

  local vartpm = 0
  local str7tomp = ''
  local str7tump = ''
  local strlingvo = ''
  local num7lencleft = 0
  local num7posirajt = 0
  local num7sandwitch = 0
  local numtristato = 0 -- preASSume nothing found
  local boopmt = false

  while true do -- fake loop

    for k7k,v7v in pairs(contabfarado) do -- nothing done if table empty
      if ((type(k7k)~='string') or (type(v7v)~='string')) then
        break -- protect from terrorism
      end--if
      if (lfgcmpbeg(strcatname,k7k,false)) then -- equality NOT appreci
        num7lencleft = string.len(k7k)
        vartpm = string.find(strcatname,v7v,1,true)
        if (type(vartpm)=='number') then
          num7posirajt = vartpm
          num7sandwitch = num7posirajt - num7lencleft - 1
          if (mathisintrange(num7sandwitch,2,10)) then
            strlingvo = string.sub(strcatname,(num7lencleft+1),(num7posirajt-1))
            numtristato = 1
            break -- got langcode (validity NOT certified yet)
          end--if
        end--if
      end--if (lfgcmpbeg(strcatname,k7k,false)) then
    end--for
    if (numtristato==1) then
      break -- got langcode, exit fake loop too
    end--if

    vartpm = contabfullcat[strcatname] -- single query gives the verdict
    if (type(vartpm)=='string') then
      strlingvo = vartpm
      numtristato = 1
      break -- got langcode
    end--if

    for k7k,v7v in pairs(contabsuffcat) do -- nothing done if table empty
      if ((type(k7k)~='string') or (type(v7v)~='string')) then
        break -- protect from terrorism
      end--if
      if (lfgcmpend(strcatname,k7k,false)) then -- equality NOT appreci
        strlingvo = v7v
        numtristato = 1
        break -- got langcode
      end--if
    end--for
    if (numtristato==1) then
      break -- got langcode, exit fake loop too
    end--if

    boopmt, str7tomp, str7tump = lfisepbracket(strcatname,3) -- succ innr outr
    if (boopmt) then -- langname found (validity NOT certified yet)
      strlingvo = str7tomp
      numtristato = 2 -- got langname
    end--if

    break -- finally to join mark
  end--while -- fake loop -- join mark

  return strlingvo, numtristato

end--function lfhgetlangfromcat

------------------------------------------------------------------------

-- Local function LFHFALLBACK

-- Output : * str55rezult -- empty string if nothing found

-- Depends on constants :
-- * table contabfall

local function lfhfallback (str55incom)

  local var55target = 0
  local var55nonleft = 0
  local varone55line = 0
  local str55rezult = ''
  local num55outer = 1 -- ONE-based
  local num55inner = 0 -- ONE-based but starts from TWO

  while true do
    varone55line = contabfall [num55outer]
    if (type(varone55line)~='table') then
      break -- we have run out of outer table, nothing found, abort outer loop
    end--if
    var55target = varone55line[1]
    if (type(var55target)~='string') then
      break -- should be impossible, abort outer loop too
    end--if
    if (str55incom==var55target) then
      str55rezult = str55incom -- supported as-is, abort outer loop
      break
    end--if
    num55inner = 2
    while true do
      var55nonleft = varone55line[num55inner]
      if (type(var55nonleft)~='string') then
        break -- no fallback in this line, abort inner loop only
      end--if
      if (str55incom==var55nonleft) then
        str55rezult = var55target -- fallback success
        break -- abort inner loop only
      end--if
      num55inner = num55inner + 1
    end--while
    num55outer = num55outer + 1
  end--while

  return str55rezult

end--function lfhfallback

------------------------------------------------------------------------

---- MEDIAWIKI INTERACTION FUNCTIONS [W] ----

------------------------------------------------------------------------

-- Local function LFWKATROLSIM

-- Peek summary of a category.

-- Input  : * strcat8name

-- Output : * numpages, numsubcats -- 2 values, value -1 for unknown

-- Always expensive, the caller must use "pcall".

local function lfwkatrolsim (strcat8name)
  local metab = 0
  local numpages = -1 -- preASSume unknown
  local numsubcats = -1 -- preASSume unknown
  metab = mw.site.stats.pagesInCategory ( strcat8name, '*' ) -- expensive here
  if (type(metab)=='table') then -- risk of type "number" or "nil"
    numpages = metab.pages
    numsubcats = metab.subcats
    if (numpages<0) then
      numpages = -1 -- YES MediaWiki is stupid
    end--if
    if (numsubcats<0) then
      numsubcats = -1 -- YES MediaWiki is stupid
    end--if
  end--if
  return numpages, numsubcats
end--function lfwkatrolsim

-- ***********************
-- *    VARIABLES [R]    * ---------------------------------------------
-- ***********************

function exporttable.ek (arxframent)

  -- general unknown type

  local vartymp = 0    -- variable without type multipurpose

  -- special type "args" AKA "arx"

  local arxsomons = 0  -- metaized "args"

  -- general tab

  local tablg80lefty = {}

  -- peeked stuff

  local strpiklangcode = '' -- "en" privileged site language
  local strpikkatns    = '' -- "Category"
  local strpikparent   = '' -- "Template:attack" FULLPAGENAME
  local strpikpareuf   = '' -- "attack" PAGENAME unfull

  -- override

  local strtocoverride = '' -- from "tocforceoverridetestonly="

  -- general str

  local strpagenam = '' -- {{PAGENAME}} or "pagenameoverridetestonly="
  local strruangna = '' -- {{NAMESPACENUMBER}} or "nsnumberoverridetestonly="
  local strkodbah  = '' -- langcode

  local strtump    = '' -- temp

  local strvisgud  = ''  -- visible output on success
  local strviserr  = ''  -- visible error message on error
  local strtrakat  = ''  -- invisible tracking categories on error
  local strret     = ''  -- final result string

  -- general num

  local numerr       = 0   -- 0 OK | 1 inter | 2 sub fail | 3 sub statuscode
  local num2statcode = 0
  local numpages     = 0
  local numsubkatt   = 0

  -- general boo

  local boobigenough = false
  local bootimp      = false

------------------------------------------------------------------------

---- MAIN [Z] ----

------------------------------------------------------------------------

  ---- GUARD AGAINST INTERNAL ERROR AGAIN ----

  -- later reporting of #E01 must NOT depend on uncommentable stuff

  prdtracemsg ('This is "Module:eht", requested "detrc" report')

  if (qbooguard) then
    numerr = 1 -- #E01 internal
  end--if

  ---- PEEK STUFF THAT IS NOT OVERRIDDEN GENEROUS ----

  -- this depends on "arxframent" (only if parent requested) but NOT on "arx"

  -- "strpikkatns" and "strpikindns" and "strpikapxns" do NOT
  -- include a trailing ":" colon, and are for "lfykattlaenk"
  -- and "lfyapxindlaenk" and "lfikatpaldigu"

  -- full "strpikparent" is used for error messages
  -- unfull "strpikpareuf" is used for tracking cat:s

  if (numerr==0) then
    strpiklangcode = contabovrd.sitelang or mw.getContentLanguage():getCode() or 'en'              -- privileged site language
    strpikkatns    = contabovrd.katprefi or (mw.site.namespaces[ 14] or {})['name'] or 'Category'  -- standard namespace
    strpikparent   = contabovrd.parentfn or arxframent:getParent():getTitle() or 'Template:eht'    -- fullpagename
    if ((type(strpiklangcode)~='string') or (type(strpikkatns)~='string') or (type(strpikparent)~='string')) then
      numerr = 1 -- #E01 internal (unlikely)
    end--if
    vartymp = string.find(strpikparent,':',1,true)
    if (mathisintrange(vartymp,2,(string.len(strpikparent)-1))) then
      strpikpareuf = string.sub(strpikparent,(vartymp+1),-1) -- make unfull
    else
      strpikpareuf = strpikparent -- dubi call from ns ZERO or misplaced ":"
    end--if
  end--if (numerr==0) then

  ---- GET THE ARX (ONE OF TWO) ----

  -- must be seized independently on "numerr" even if we
  -- already suck due to possible "detrc=true"

  -- give a f**k in possible params other than "parentframe=true"

  arxsomons = arxframent.args -- "args" from our own "frame"
  if (type(arxsomons)~='table') then
    arxsomons = {} -- guard against indexing error from our own
    numerr = 1 -- #E01 internal
  end--if
  if (arxsomons['parentframe']=='true') then
    arxsomons = arxframent:getParent().args -- "args" from parent's "frame"
  end--if
  if (type(arxsomons)~='table') then
    arxsomons = {} -- guard against indexing error again
    numerr = 1 -- #E01 internal
  end--if

  ---- PROCESS MESSAGES ----

  if (numerr==0) then
    contabfullcat = prhrecusurrstrtab (contabfullcat, strpiklangcode, true) -- also keys
    contabsuffcat = prhrecusurrstrtab (contabsuffcat, strpiklangcode, true) -- also keys
    contaberaroj  = prhrecusurrstrtab (contaberaroj, strpiklangcode, nil)
    contabtrako   = prhrecusurrstrtab (contabtrako, strpiklangcode, nil)
  end--if

  prdtracemsg ('Messages processed')

  ---- PICK SUBTABLE T80 FROM ONE IMPORT ----

  -- here risk of #E02 #E03

  -- on error we assign "numerr" and maybe "num2statcode" both used far below

  while true do -- fake loop

    if (numerr~=0) then -- #E01 possible
      break -- to join mark
    end--if

    num2statcode, bootimp = lfhvali1status98code (qldingvoj[2])
    if (num2statcode~=0) then
      if (bootimp) then
        numerr = 3 -- #E03 nombrigita
      else
        numerr = 2 -- #E02 malica
      end--if
      break -- to join mark
    end--if

    vartymp = qldingvoj['T80'] -- from "loaddata-tbllingvoj"
    if (type(vartymp)~='table') then -- important check
      numerr = 2 -- #E02 malica
      break -- to join mark
    end--if
    tablg80lefty = vartymp -- leftmost column -> y-index (langname to langcode)

    break -- finally to join mark
  end--while -- fake loop -- join mark

  ---- PROCESS 3 HIDDEN NAMED PARAMS INTO 3 STRING:S ----

  -- this may override "mw.title.getCurrentTitle().text" and
  -- stipulate content in "strpagenam", empty is NOT valid

  -- bad "pagenameoverridetestonly=" can give #E01
  -- no error is possible from other hidden parameters

  strpagenam = '' -- using vartymp here
  if (numerr==0) then -- get pagename (error if bad, silent if absent)
    vartymp = arxsomons['pagenameoverridetestonly']
    if (type(vartymp)=='string') then
      if (prgstringrange(vartymp,1,200)) then -- empty or too long NOT legal
        strpagenam = vartymp
      else
        numerr = 1 -- #E01 internal
      end--if
    end--if
  end--if

  strruangna = '' -- using vartymp here
  if (numerr==0) then -- get namespace (silent if bad, silent if absent)
    vartymp = arxsomons['nsnumberoverridetestonly']
    if (prgstringrange(vartymp,1,4)) then -- empty or too long NOT legal
      strruangna = vartymp
    end--if
  end--if

  if (numerr==0) then
    vartymp = arxsomons['tocforceoverridetestonly']
    if ((vartymp=='on') or (vartymp=='off')) then
      strtocoverride = vartymp
    end--if
  end--if

  if (contabovrd.defortrc) then
    prdtracemsg ('Force "detrc=true"')
  else
    if (arxsomons['detrc']=='true') then
      prdtracemsg ('Param "detrc=true" seized')
    else
      qboodetrc = false -- was preassigned to "true"
      qstrtrace = '' -- shut up now
    end--if
  end--if

  ---- SEIZE PAGENAME AND NS FROM MW ----

  -- "pagenameoverridetestonly=" "nsnumberoverridetestonly=" processed above

  -- later reporting of #E01 must NOT depend on uncommentable
  -- or peekable stuff

  -- pagename must be 1...200 octet:s keep consistent
  -- with "pagenameoverridetestonly="

  if ((numerr==0) and (strpagenam=='')) then -- get pagename (error if bad)
    vartymp = mw.title.getCurrentTitle().text -- without namespace prefix
    if (prgstringrange(vartymp,1,200)) then -- empty or too long NOT legal
      strpagenam = vartymp -- cannot be left empty
    else
      numerr = 1 -- #E01 internal
    end--if
  end--if

  if ((numerr==0) and (strruangna=='')) then -- get namespace (silent if bad)
    vartymp = mw.title.getCurrentTitle().namespace -- type is "number"
    if (mathisintrange(vartymp,0,9999)) then -- negative NOT legal but silent
      strruangna = tostring(vartymp) -- can be left empty, check below required
    end--if
  end--if

  ---- CHECK NS ----

  if ((numerr==0) and (strruangna~='14')) then -- must be "Category:"
    numerr = 10 -- #E10 called from wrong ns
  end--if

  prdtracemsg ('Seized pagename and ns, numerr=' .. tostring(numerr))

  ---- WHINE IF YOU MUST #E01 ----

  if (numerr==1) then
    strtump = '#E01 Internal error in "Module:eht".'
    strviserr = contabfel.hubg .. strtump .. contabfel.huen
  end--if

  ---- CHECK WHETHER NON-UPPPER #E14 OR FULL LANGNAME #E16 ----

  -- from above strpagenam

  if ((numerr==0) and (lfutristletr('eo',strpagenam)~=1)) then
    numerr = 14 -- #E14 catname does not begin with eo uppercase
  end--if

  prdtracemsg ('Checked required uppercase, numerr=' .. tostring(numerr))

  if (numerr==0) then
    vartymp = tablg80lefty[strpagenam] -- try reverse query as-is
    if (type(vartymp)~='string') then
      vartymp = tablg80lefty[lfucaserest('eo',strpagenam,false,false)] -- try reverse query lowered
    end--if
    if (type(vartymp)=='string') then
      numerr = 16 -- #E16 sorry this type of cat is prohibited
    end--if
  end--if

  prdtracemsg ('Checked for prohibited type, numerr=' .. tostring(numerr))

  ---- CHECK WHETHER SPECIAL OR LANGNAME AVAILABLE OR TRANSLINGUAL CAT ----

  -- from above strpagenam, "sveda" or "Greka antikva" ruled out

  -- strkodbah is empty string so far, assign it based on a chain of rules:
  -- * two fragments of catname found in contabfarado -> get langcode
  --   sandwiched beween them, BEWARE needs strict validation
  -- * full catname is found in contabfullcat -> get langcode from there
  -- * catname is suffixed by item found contabsuffcat -> get langcode
  --   from there (for example "Kategorio:Biologio laux lingvo" look at
  --   last 12 char:s and from them get "eo" via that table)
  -- * there is bracketed part >= 3 char:s -> take it as langname and
  --   do reverse query
  -- * else translingual cat -> langcode is "mul"

  -- here call [H] lfhgetlangfromcat and maybe peek tablg80lefty

  -- numerr, strpagenam -> numerr, strkodbah

  if (numerr==0) then
    do -- scope
      local varmpt = 0
      local strmylang = ''
      local numwhat = 0 -- ZERO nothing | ONE langcode | TWO langname
      strmylang, numwhat = lfhgetlangfromcat(strpagenam)
      if (numwhat==0) then
        strkodbah = 'mul' -- nothing found -> assume translingual cat
      end--if
      if (numwhat==1) then -- must validate  !!!FIXME!!! forward query needed too
        if (lfivalidatelnkoadv(strmylang,false,false,conbookodlng,conboomiddig,false)) then
          strkodbah = strmylang
        else
          numerr = 11 -- #E11 obviously invalid langcode
        end--if
      end--if
      if (numwhat==2) then -- get langcode by reverse query
        varmpt = tablg80lefty[strmylang] -- reverse query
        if (type(varmpt)=='string') then
          strkodbah = varmpt -- got ordinary langcode from (...)
        else
          numerr = 15 -- #E15 invalid or unknown langname
        end--if
      end--if (numwhat==2) then
    end--do scope
  end--if (numerr==0) then

  prdtracemsg ('After "lfhgetlangfromcat" strkodbah="' .. strkodbah .. '", numerr=' .. tostring(numerr))

  ---- CHECK WHETHER IT IS BIG ENOUGH ----

  -- if it's too big to fail then it's too big but here we
  -- require at least 300 pages or at least 300 subcategories

  if (numerr==0) then
    boobigenough = (strtocoverride=='on')
    if ((not boobigenough) and (strtocoverride~='off')) then
      numpages, numsubkatt = lfwkatrolsim(strpagenam) -- without namespace prefix
      boobigenough = ((numpages>=300) or (numsubkatt>=300))
    end--if
  end--if

  prdtracemsg ('Size check, boobigenough=' .. tostring(boobigenough))

  ---- PERFORM POSSIBLE FALLBACK ----

-- everything not listed fallbacks here to "en", this is good for "io"
-- and "id" and possibly a few more, but desperate for many others

  if ((numerr==0) and boobigenough) then
    strkodbah = lfhfallback(strkodbah) -- risk of empty "" or special "etio"
    if (strkodbah=='') then
      strkodbah = 'en'
    end--if
  end--if

  ---- FIRE THE TEMPLATE ----  !!!FIXME!!!

  if ((numerr==0) and boobigenough) then
    do -- scope
      local strforwardajxo = ''
      local boo3crap = false

      strforwardajxo = 'pagenameoverridetestonly=' .. strpagenam .. '|fullpanaoverridetestonly=' .. strpikkatns .. ':' .. strpagenam .. '|nsnumberoverridetestonly=14'
      if (strkodbah=='etio') then
        strvisgud = arxframent:preprocess ('{{KategorioEHT-etiopia-skribo|'..strforwardajxo..'}}')
      else
        strvisgud = arxframent:preprocess ('{{eht-kat-alfa|ling='..strkodbah..'|'..strforwardajxo..'}}')
      end--if
      if (string.len(strvisgud)<50) then
        boo3crap = true
      else
        boo3crap = (string.byte(strvisgud,1,1)==91) or (string.sub(strvisgud,1,5)=='&#91;')
      end--if
      if (boo3crap) then
        numerr = 19 -- #E19 template failed
      end--if

    end--do scope
  end--if

------------------------------------------------------------------------

  ---- WHINE IF YOU MUST #E02...#E98 SIMPLE THERE IS A PARENT ----

  if ((numerr>=2) and (numerr<=98)) then
    strviserr = prhbrewerr5hupa(numerr,('"'..strpikparent..'"'))
  end--if

------------------------------------------------------------------------

  ---- TRACKING CAT:S ON #E10 #E11 #E15 #E19 ----

  -- here we use "strpikpareuf" ie the UNFULL name of the parent

  while true do -- fake loop
    if (numerr<10) then -- #E10
      break
    end--if
    if (numerr==11) then -- #E11
      strtrakat = lfikataldigu(strpikkatns,contabtrako['E11'])
      break
    end--if
    if (numerr==15) then -- #E15
      strtrakat = lfikataldigu(strpikkatns,contabtrako['E15'])
      break
    end--if
    strtrakat =              lfikataldigu(strpikkatns,contabtrako['cetere'])
    strtrakat = strtrakat .. lfikataldigu(strpikkatns,contabtrako['cetere'] .. ' (' .. strpikpareuf .. ')')
    strtrakat = strtrakat .. lfikataldigu(strpikkatns,contabtrako['cetere'] .. ' (' .. strpikpareuf .. ', E' .. prnnumto2digit(numerr) .. ')')
    break -- finally
  end--while -- fake loop -- join mark

------------------------------------------------------------------------

  ---- RETURN THE JUNK STRING ----

  -- on #E02 and higher we risk partial results in "strvisgud"

  if (numerr==0) then
    strret = strvisgud
  else
    strret = strviserr .. strtrakat
  end--if
  if (qboodetrc) then -- "qstrtrace" declared separately outside main function
    strret = '<br>' .. qstrtrace .. '<br><br>' .. strret
  end--if
  return strret

------------------------------------------------------------------------

end--function

  ---- RETURN THE JUNK LUA TABLE ----

return exporttable