--[===[

MODULE "EHT" (enhavtabelo)

"eo.wiktionary.org/wiki/Modulo:eht" <!--2024-Jul-14-->

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 kategrio ...

Manfaat: menlihatkan daftar isi kategori ...

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)

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

Incoming: * no ordinary parameters
          * 3 hidden parameters
            * pagenameoverridetestonly
            * nsnumberoverridetestonly
            * tocforceoverridetestonly -- "on" or "off"

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

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)>>
* <<#E15 <<Erara uzo de sxablono (eht), nekonata lingvonomo>>
* <<#E16 <<SXablono ne redonis validan rezulton>>

Possible tracking cat:s:
* [[Kategorio:Nekonata lingvonomo]]                  #E15
* [[Kategorio:Erara uzo de sxablono]]                #E10 #E16
* [[Kategorio:Erara uzo de sxablono (eht)]]          #E10 #E16
* [[Kategorio:Erara uzo de sxablono (eht, E18)]]     #E10 #E16

]===]

local exporttable = {}

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

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

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

-- uncommentable (site-related features)

      local constringvoj = 'Modulo:loaddata-tbllingvoj'    -- EO
        -- local constringvoj = 'Modul:loaddata-tblbahasa'    -- ID

-- uncommentable constant table (error messages)

-- #E02...#E99, holes permitted
-- note that #E00 and #E01 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

-- uncommentable constant table (tracking cat:s)

      local contabtrack = {[1]='Nekonata lingvonomo',[5] = 'Erara uzo de sxablono'}

-- uncommentable patterns

      local constrpatti = ' laux lingvo' -- BEWARE: begins with space

-- 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

-- 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 constrelabg = '<span class="error"><b>'  -- lagom whining begin
local constrelaen = '</b></span>'              -- lagom whining end
local constrlaxhu = '&nbsp;&#42;&#42;&nbsp;'   -- lagom -> huge circumfix " ** "

-- langcodes with fallbacks

local contabfall = {} -- left column for supported codes and possible targets, other columns fallback codes
contabfall [ 1] = {'sv', 'da', 'fi', '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'}

-- uncommentable override constant strings (for
-- testing only, automatically peeked otherwise)

  local constrpriv = nil
  local constrkatq = nil
  local constrkoll = nil
  -- local constrpriv = 'eo'                                    -- "en"
  -- local constrkatq = 'Kategorio'                             -- "Category"
  -- local constrkoll = string.char(0xC5,0x9C) .. 'ablono:eht'  -- "Template:eht" (!!! no surr translation !!!)

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

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

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

require('strict')

  -- SPECIAL VAR:S

local qldingvoj = {}     -- type "table" but metaized with some subtables
local qbooguard = false  -- only for the guard test, pass to other var ASAP

---- GUARD AGAINST INTERNAL ERROR AND IMPORT ONE VIA LOADDATA ----

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

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

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))
    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 LFNUMTO2DIGIT

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

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

local function lfnumto2digit (numzerotoninetynine)
  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 lfnumto2digit

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

---- 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 LFIFILLNAME

-- Replace placeholder "\@" "\\@" by augmented name of the caller.

-- To be called ONLY from "lfhfillsurrstrtab".

-- The name of the caller is submitted to us as a parameter thus we
-- do NOT access any constants and do NOT have to peek it either.

local function lfifillname (strmessage, strcaller)

  local strhasill = ''
  local numstrloen = 0
  local numindfx = 1 -- ONE-based
  local numcjar = 0
  local numcjnext = 0

  numstrloen = string.len (strmessage)

  while true do
    if (numindfx>numstrloen) then
      break -- empty input is useless but cannot cause major harm
    end--if
    numcjar = string.byte (strmessage,numindfx,numindfx)
    numindfx = numindfx + 1
    numcjnext = 0 -- preASSume no char
    if (numindfx<=numstrloen) then
      numcjnext = string.byte (strmessage,numindfx,numindfx)
    end--if
    if ((numcjar==92) and (numcjnext==64)) then
      strhasill = strhasill .. strcaller -- invalid input is caller's risk
      numindfx = numindfx + 1 -- skip 2 octet:s of the placeholder
    else
      strhasill = strhasill .. string.char (numcjar)
    end--if
  end--while

  return strhasill

end--function lfifillname

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

-- Local function LFIKODEOSG

-- 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 functions :
-- [E] mathdiv mathmod

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

-- To be called ONLY from "lfhfillsurrstrtab".

-- * 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 lfikodeosg (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 lfikodeosg

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

---- HIGH LEVEL FUNCTIONS [H] ----

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

-- Local function LFHVALI1STATUS99CODE

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

local function lfhvali1status99code (varvalue)
  local boovalid = false -- preASSume guilt
  while true do -- fake loop
    if (varvalue==0) then
      break -- success thus keep false since no valid error code ;-)
    end--if
    if (mathisintrange(varvalue,1,99)) 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 lfhvali1status99code

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

-- Local function LFHCONSTRUCTERAR

-- Input  : * numerar6code -- 1 ... 99 or 2 ... 99 (invalid data type ignored)
--          * boopeek6it -- do peek description #E02...#E99 from table

-- Depends on functions :
-- [N] lfnumto2digit
-- [E] mathisintrange mathdiv mathmod

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

-- To be called ONLY from lfhbrewerror, lfhbrewerrsm,
-- lfhbrewerrsvr, lfhbrewerrinsi.

local function lfhconstructerar (numerar6code, boopeek6it)
  local vardes6krip = 0
  local numbottom6limit = 1
  local stryt6sux = '#E'
  if (type(numerar6code)~='number') then
    numerar6code = 0 -- invalid
  end--if
  if (boopeek6it) then
    numbottom6limit = 2 -- #E01 is a valid code for submodule only
  end--if
  if (mathisintrange(numerar6code,numbottom6limit,99)) then
    stryt6sux = stryt6sux .. lfnumto2digit(numerar6code)
    if (boopeek6it) then
      vardes6krip = contaberaroj[numerar6code] -- risk of type "nil"
      if (type(vardes6krip)=='string') then
        stryt6sux = stryt6sux .. ' ' .. vardes6krip
      else
        stryt6sux = stryt6sux .. ' ??' -- no text found
      end--if
    end--if (boopeek6it) then
  else
    stryt6sux = stryt6sux .. '??' -- no valid error code
  end--if
  return stryt6sux

end--function lfhconstructerar

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

-- Local function LFHBREWERROR

-- Input  : * numerar7code -- 2 ... 99

-- Depends on functions :
-- [H] lfhconstructerar
-- [N] lfnumto2digit
-- [E] mathisintrange mathdiv mathmod

-- Depends on constants :
-- * 3 strings constrelabg constrelaen constrlaxhu
-- * table contaberaroj TWO-based (holes permitted)

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

local function lfhbrewerror (numerar7code)
  local stryt7sux = ''
  stryt7sux = constrlaxhu .. constrelabg .. lfhconstructerar (numerar7code,true) .. constrelaen .. constrlaxhu
  return stryt7sux
end--function lfhbrewerror

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

-- Local function LFHFILLSURRSTRTAB

-- Process (fill in, transcode surr) either a single string, or all string
-- items in a table (even nested) using any type of keys/indexes (such as
-- a holy number sequence and non-numeric ones). Items with a non-string
-- value are kept as-is. For filling in own name, and converting eo and
-- NOPE sv surrogates (via 3 separate sub:s).

-- Input  : * varinkommen -- type "string" or "table"
--          * varfyllo -- string, or type "nil" if no filling-in desired
--          * strlingkod -- "eo" or NOPE "sv" to convert surrogates, anything
--                          else (preferably type "nil") to skip this

-- Depends on functions :
-- [I] lfifillname (only if filling-in desired)
-- [I] lfikodeosg (only if converting of eo X-surrogates desired)
-- NOPE [I] lfikodsvsg
-- [E] mathdiv mathmod (via "lfikodeosg" and NOPE "lfikodsvsg")

-- Depends on constants :
-- * table "contabtransluteo" inherently holy (via "lfikodeosg")
-- NOPE * table "contabtranslutsv"

local function lfhfillsurrstrtab (varinkommen, varfyllo, strlingkod)

  local varkey = 0 -- variable without type
  local varele = 0 -- variable without type
  local varutmatning = 0
  local boodone = false

  if (type(varinkommen)=='string') then
    if (type(varfyllo)=='string') then
      varinkommen = lfifillname (varinkommen,varfyllo) -- fill-in
    end--if
    if (strlingkod=='eo') then
      varinkommen = lfikodeosg (varinkommen) -- surr
    end--if
    -- if (strlingkod=='sv') then
      -- varinkommen = lfikodsvsg (varinkommen) -- surr
    -- end--if
    varutmatning = varinkommen -- copy, risk for no change
    boodone = true
  end--if

  if (type(varinkommen)=='table') then
    varutmatning = {} -- brew new table
    varkey = next (varinkommen) -- try to pick 0:th (in no order) key/index
    while true do
      if (varkey==nil) then
        break -- empty table or end reached
      end--if
      varele = varinkommen[varkey] -- pick element of unknown type
      if ((type(varele)=='string') or (type(varele)=='table')) then
        varele = lfhfillsurrstrtab (varele, varfyllo, strlingkod) -- RECURSION
      end--if
      varutmatning[varkey] = varele -- write at same place in dest table
      varkey = next (varinkommen, varkey) -- try to pick next key/index
    end--while
    boodone = true
  end--if

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

  return varutmatning

end--function lfhfillsurrstrtab

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

-- 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 LFWKATROL

-- Peek summary of a category.

-- Input  : * strcatname           -- without prefix

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

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

local function lfwkatrol (strcatname)
  local metab = 0
  local numpages = -1 -- preASSume unknown
  local numsubcats = -1 -- preASSume unknown
  metab = mw.site.stats.pagesInCategory ( strcatname, "*" ) -- 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 lfwkatrol

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

---- 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" from our own or caller's "frame"

  -- 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 strtomp    = '' -- temp
  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
  local numtamp      = 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

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

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

  -- this depends on "arxframent" but NOT on "arx"

  if (numerr==0) then
    strpiklangcode = constrpriv or mw.getContentLanguage():getCode() -- "en" privileged site language
    strpikkatns    = constrkatq or mw.site.namespaces[ 14].name      -- "Category"  !!!FIXME!!! this is weak
    strpikparent   = constrkoll or arxframent:getParent():getTitle() -- "Template:attack"
    if ((type(strpiklangcode)~='string') or (type(strpikkatns)~='string') or (type(strpikparent)~='string')) then
      numerr = 1 -- #E01 internal
    end--if
    vartymp = string.find(strpikparent,':',1,true)
    if (type(vartymp)=='number') then
      strpikpareuf = string.sub(strpikparent,(vartymp+1),-1) -- make unfull
    else
      numerr = 1 -- #E01 internal
    end--if
  end--if

  ---- 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 "caller=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['caller']=='true') then
    arxsomons = arxframent:getParent().args -- "args" from caller's "frame"
  end--if
  if (type(arxsomons)~='table') then
    arxsomons = {} -- guard against indexing error again
    numerr = 1 -- #E01 internal
  end--if

  ---- PROCESS MESSAGES, FILL IN ALWAYS, SURR ONLY IF NEEDED ----

  -- only for some languages the surr-transcoding is subsequently performed

  if (numerr==0) then
    constrpatti = lfhfillsurrstrtab (constrpatti, nil, strpiklangcode)
    contaberaroj = lfhfillsurrstrtab (contaberaroj, ('"' .. strpikparent .. '"'), strpiklangcode)
    contabtrack = lfhfillsurrstrtab (contabtrack, nil, strpiklangcode)
  end--if

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

  -- here risk of #E02 #E03

  while true do -- fake loop

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

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

    tablg80lefty = qldingvoj['T80']
    if (type(tablg80lefty)~='table') then -- important check
      numerr = 2 -- #E02 malica
      break -- to join mark
    end--if

    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 = ''
  if (numerr==0) then
    vartymp = arxsomons['pagenameoverridetestonly']
    if (type(vartymp)=='string') then
      numtamp = string.len(vartymp)
      if ((numtamp>=1) and (numtamp<=200)) then
        strpagenam = vartymp -- empty is not legal, whine if bad
      else
        numerr = 1 -- #E01 internal
      end--if
    end--if
  end--if

  strruangna = ''
  if (numerr==0) then
    vartymp = arxsomons['nsnumberoverridetestonly']
    if (type(vartymp)=='string') then
      numtamp = string.len(vartymp)
      if ((numtamp>=1) and (numtamp<=4)) then
        strruangna = vartymp -- empty is not legal, skip if bad
      end--if
    end--if
  end--if

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

  ---- SEIZE THE PAGENAME FROM MW ----

  -- "pagenameoverridetestonly=" is processed above

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

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

  if ((numerr==0) and (strpagenam=='')) then
    vartymp = mw.title.getCurrentTitle().text -- without namespace prefix
    if (type(vartymp)=='string') then -- this can leave bhd "strpagenam" empty
      numtamp = string.len(vartymp)
      if ((numtamp>=1) and (numtamp<=200)) then
        strpagenam = vartymp -- empty is not legal, whine if bad
      else
        numerr = 1 -- #E01 internal
      end--if
    end--if
  end--if

  if (numerr==0) and (strpagenam=='') then
    numerr = 1 -- #E01 internal
  end--if

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

  -- reporting of this error #E01 must NOT depend on uncommentable
  -- stuff such as "constrkoll" / "strpikparent" and "contaberaroj"

  if (numerr==1) then
    strtump = '#E01 Internal error in "Module:eht".'
    strviserr = constrlaxhu .. constrelabg .. strtump .. constrelaen .. constrlaxhu
  end--if

  ---- SEIZE THE NAMESPACE FROM MW AND CHECK IT ----

  -- here risk of #E10

  -- "nsnumberoverridetestonly=" is processed above

  if ((numerr==0) and (strruangna=='')) then
    vartymp = mw.title.getCurrentTitle().namespace -- type is "number"
    if (type(vartymp)=='number') then -- this can leave bhd "strruangna" empty
      if (mathisintrange(vartymp,0,9999)) then
        strruangna = tostring(vartymp) -- negative not legal, skip if bad
      end--if
    end--if
  end--if

  if ((numerr==0) and (strruangna~='14')) then
    numerr = 10 -- #E10 wrong ns
  end--if

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

  -- strkodbah is empty string so far, check whether it can become
  -- "eo" by special rule or "mul" by absence of ()

  -- for example "Kategorio:Biologio laux lingvo" look at last 12 char:s

  if (numerr==0) then
    numtamp = string.len(constrpatti)
    if ((string.len(strpagenam)>numtamp) and (string.sub(strpagenam,-numtamp,-1)==constrpatti)) then
      strkodbah = 'eo' -- special case
    else
      bootimp, strtomp, strtump = lfisepbracket(strpagenam,3) -- succ innr outr
      if (not bootimp) then -- langname NOT found
        strkodbah = 'mul' -- translingual cat
      end--if
    end--if ((string.len(strpagenam) ... else
  end--if

  ---- GET LANGCODE BY REVERSE QUERY ----

  -- maybe strtomp comes from above

  if ((numerr==0) and (strkodbah=='')) then -- NOT special case and NOT translingual cat
    vartymp = tablg80lefty[strtomp] -- reverse query
    if (type(vartymp)=='string') then
      strkodbah = vartymp -- got ordinary langcode from (...)
    else
      numerr = 15 -- #E15 invalid langname
    end--if
  end--if

  ---- 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 = lfwkatrol(strpagenam) -- without namespace prefix
      boobigenough = ((numpages>=300) or (numsubkatt>=300))
    end--if
  end--if

  ---- 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 ----

  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 = 16 -- #E16 template failed
      end--if

    end--do scope
  end--if

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

  ---- WHINE IF YOU MUST ON #E02...#E99 ----

  -- reporting of errors #E02...#E99 depends on uncommentable stuff
  -- and name of the caller filled in from "constrkoll" / "strpikparent"

  if (numerr>=2) then
    strviserr = lfhbrewerror(numerr)
  end--if

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

  ---- TRACKING CAT:S ON #E10 #E15 #E16 ----

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

  if (numerr>=10) then -- #E10
    if (numerr==15) then -- #E15
      strtrakat =              lfikataldigu(strpikkatns,contabtrack[1])
    else
      strtrakat =              lfikataldigu(strpikkatns,contabtrack[5])
      strtrakat = strtrakat .. lfikataldigu(strpikkatns,contabtrack[5] .. ' (' .. strpikpareuf .. ')')
      strtrakat = strtrakat .. lfikataldigu(strpikkatns,contabtrack[5] .. ' (' .. strpikpareuf .. ', E' .. lfnumto2digit(numerr) .. ')')
    end--if

  end--if

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

  ---- 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
  return strret

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

end--function

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

return exporttable