Saltu al enhavo

Modulo:mlili

El Vikivortaro
 MODULO
Memtesto disponeblas sur la dokumentaĵa subpaĝo.
Ĉi tiu modulo estas multfoje bindita.
Se vi konas la eblajn sekvojn, tiam vi povas zorgeme ekredakti.
Se vi ne kuraĝas redakti tiam vi povas proponi la deziratan ŝanĝon en la diskutejo.
  • Ĉi tiu modulo efektivigas laboron de ŝablono {{lili}} kaj estas vokata sole de tie.
  • Dependas de:



--[===[

MODULE "MLILI" (lingvokodo ligilo)

"eo.wiktionary.org/wiki/Modulo:mlili" <!--2025-Feb-15-->

Purpose: converts langcode to langname (with some bonus features) and
         optionally brews a link according to one of many available types

Utilo: konvertas lingvokodon al lingvomono (kun kelkaj bonusaj funkcioj)
       kaj opcie kreas ligilon laux unu inter multaj disponeblaj tipoj

Manfaat: mengonversi kode bahasa ke nama bahasa (dengan beberapa fungsi
         tambah) dan ...

Syfte: konverterar spraakkod till spraaknamn (med naagra extrafunktioner)
       och ...

Used by templates / Uzata far sxablonoj /
Digunakan oleh templat / Anvaend av mallar:
* only "lili" (not to be called from any other place)

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

This module can accept parameters whether sent to itself (own frame) or
to the caller (caller's frame). If there is a parameter "caller=true"
on the own frame then that own frame is discarded in favor of the
caller's one. Empty parameters and parameters longer than 160 octet:s
(except "noc=") are inherently invalid (#E09), further checks follow.

Parameters: * 1 anonymous obligatory parameter
              * langcode ("??" permitted)                                       !!!FIXME!!!
            * 0...9 named optional parameters
              - "tip=" - base type of output or link, 3 possible values: "LA",
                         "LK" (visible link to category), "KE" (cat insertion,
                         adding page into category), default is "raw" plain
                         text no link
              - "nem=" - one possible value: "1", avoid wall in the link,
                         show it plain and honest, default is wall with type
                         "LA" or "LK" but not with "KE", only legal with
                         base type "LA" or "LK" (otherwise error), not
                         together with "mon=" or "maj=1" (otherwise error)
              - "mon=" - type of langname, 5 possible values: "la" (add
                         "la" if appropriate), "aj" (brew adjective), "sf"
                         ("senfinajxa" ie without ending for example "kore",
                         does not work with all languages), "av" (brew adverb,
                         if one word then this is "mon=" type "sf" plus "e",
                         else this is string "en" plus space + "mon=" type
                         "la"), "pl" (brew plural adjective, works for sane
                         multiword langnames too), default is native ie
                         SB or AJ and no "la", only legal with base type
                         "LA" or "LK" or "raw" (otherwise error), not together
                         with "nem=1" or "prf=" (otherwise error)
              - "maj=" - one possible value: "1", force begin uppercase,
                         default false ie native case for "mon=" types native
                         and "la", and lowercase for "mon=" types "aj" "sf"
                         "av", only legal with base type "LA" or "LK" or
                         "raw" (otherwise error), not together
                         with "nem=1" (otherwise error)
              - "hin=" - string (2...80 octet:s), name of category, language
                         name will become the sorting hint/key all lowercase
                         and without spaces and dashes, name of category
                         is fixed, default no hint, only legal with base
                         type "KE" (otherwise error), not together with
                         "prf=" (otherwise error)
              - "prf=" - string (2...80 octet:s), add prefix before language
                         name and brackets, default no prefix, only legal with
                         base type "LA" or "LK" or "KE" (otherwise error), not
                         together with "mon=" or "hin=" (otherwise error)
              - "noc=" - one possible value: "true" (any other value ignored),    !!!FIXME!!! obsolete
                         "true" causes empty result with "KE" ie suppresses
                         categorization, only useful with base type "KE"
                         (otherwise ignored), forward "nocat=" to this
                         one if desired
              - "per=" - one possible value: "1", keep link or category if
                         langcode is bad (obviously invalid or unknown),
                         default is off and means to switch base type
                         of task "LA" or "LK" to type "raw", base
                         type "KE" to empty result, only legal with
                         base type "LA" or "LK" or "KE" (otherwise error)
              - "err=" - string (2...80 octet:s), common placeholder, by
                         default the errors are "evidente nevalida lingvokodo"
                         and "nekonata lingvokodo"

* parameter "noc=" cannot cause an error
* parameters "mon=" "maj=1" and on the other side "hin=" indirectly exclude
  each other absolutely via the base type
* for base type "KE" the language can be in the cat insertion target or
  in the hint, but not in both
* for types "LA" and "LK" the language can be in the single common field
  (with "nem=1"), or in both, but separately processed ("mon=" and "maj=1
  affect only the visible part)
* wikilink target or category name for "LA" "LK" "KE" always gets
  uppercase beginning letter
* sorting hint/key is always converted to lowercase (all letters) and junk
  char:s (particularly spaces and dashes) are removed
* if langcode is bad (obviously invalid or unknown) then by default
  both "tip=" and "mon=" are force-switched to default values "raw" and
  "native", yet with "per=1" the result is constructed with "tip="
  unaffected but "mon=" still force-switched to "native"

Returned: * one moderately long string with raw name or link, can be empty
            in some cases, or red error message

This module is unbreakable (when called with correct module name
and function name). Every imaginable input from the caller and
from the imported modules will output either a useful result or
at least a helpful error string.

Cxi tiu modulo estas nerompebla (kiam vokita kun gxustaj nomo de modulo
kaj nomo de funkcio). Cxiu imagebla enigo de la vokanto kaj
de la importataj moduloj eldonos aux utilan rezulton aux
almenaux helpeman eraranoncan signocxenon.

Following errors are possible:
* <<#E01 Internal error in module "mlili">>
  Possible causes:
  * strings not uncommented
* <<#E02 Malica eraro en subprogramaro uzata far sxablono "lili">>
  Possible causes:
  * submodule not found
  * submodule caused unspecified failure
* <<#E03 Nombrigita eraro en subprogramaro uzata far sxablono "lili">>
  Possible causes:
  * submodule failed and returned valid error code
* <<#E08 Erara uzo de sxablono "lili", legu gxian dokumentajxon>>
  Possible causes (early detected obvious problems with parameters):
  * one obligatory anonymous parameter missing
  * more than one anonymous parameter supplied
* <<#E09>>
  * empty parameters or parameters longer than 160 octet:s (except for "noc=")
* <<#E14 Nevalida baza tipo "tip=" en sxablono "lili">>
* <<#E15 Nevalida signocxeno "hin=" "prf=" "err=" en sxablono "lili">>
  Possible causes:
  * string length out of range (2...80 octet:s)
* <<#E16 Erara uzo de sxablono "lili" pro parametroj>>
  Possible causes (later detected more clandestine problems with parameters):
  * bad value of a single parameter not caught by #E09 #E14 #E15
  * conflicting parameters
Note that obviously invalid or unknown langcode is NOT an error here.

{{hr3}} <!-------------------------------->

* #T00 (no params, bad)
* parameters: ""
* expected result: #E08
* actual result: "{{#invoke:mlili|ek}}"

::* #T01 (two anon parameters, too many)
::* parameters: "|eo|io"
::* expected result: #E08
::* actual result: "{{#invoke:mlili|ek|eo|io}}"

* #T02 (simplest example, native SB)
* parameters: "|vo"
* expected result: "Volapuko"
* actual result: "{{#invoke:mlili|ek|vo}}"

::* #T03 (simplest example, native AJ)
::* parameters: "|ko"
::* expected result: "korea"
::* actual result: "{{#invoke:mlili|ek|ko}}"

* #T04 (raw, add "la" if needed from native SB)
* parameters: "|io|mon=la"
* expected result: "Ido"
* actual result: "{{#invoke:mlili|ek|io|mon=la}}"

::* #T05 (raw, add "la" if needed from native AJ)
::* parameters: "|sv|mon=la"
::* expected result: "la sveda"
::* actual result: "{{#invoke:mlili|ek|sv|mon=la}}"

* #T06 (raw, add "la" if needed from native AJ multiword)
* parameters: "|sma|mon=la"
* expected result: "la samea suda"
* actual result: "{{#invoke:mlili|ek|sma|mon=la}}"

{{hr3}} <!-------------------------------->

* #T10 ("io|mon=aj", raw, force AJ from native SB)
* expected result: "ida"
* actual result: "{{#invoke:mlili|ek|io|mon=aj}}"

::* #T11 ("sv|mon=aj", raw, force AJ from native AJ)
::* expected result: "sveda"
::* actual result: "{{#invoke:mlili|ek|sv|mon=aj}}"

* #T12 ("sma|mon=aj", raw, force AJ from native AJ multiword)
* expected result: "samea suda"
* actual result: "{{#invoke:mlili|ek|sma|mon=aj}}"

::* #T13 ("io|mon=sf", raw, force root from native SB)
::* expected result: "id" (note that this is name "Ido", not code "id")
::* actual result: "{{#invoke:mlili|ek|io|mon=sf}}"

* #T14 ("sv|mon=sf", raw, force root from native AJ)
* expected result: "sved"
* actual result: "{{#invoke:mlili|ek|sv|mon=sf}}"

::* #T15 ("sma|mon=sf", raw, force root from native AJ multiword)
::* expected result: "samea sud" (useless)
::* actual result: "{{#invoke:mlili|ek|sma|mon=sf}}"

* #T16 ("sv|mon=pl", raw, force AJ and plural from native AJ)
* expected result: "svedaj"
* actual result: "{{#invoke:mlili|ek|sv|mon=pl}}"

::* #T17 ("sma|mon=pl", raw, force AJ and plural from native AJ multiword)
::* expected result: "sameaj sudaj"
::* actual result: "{{#invoke:mlili|ek|sma|mon=pl}}"

* #T18 ("sa|mon=pl", raw, force AJ and plural from native SB)
* expected result: "sanskritaj"
* actual result: "{{#invoke:mlili|ek|sa|mon=pl}}"

{{hr3}} <!-------------------------------->

* #T20 ("io|mon=av", raw, force AV from native SB)
* expected result: "ide"
* actual result: "{{#invoke:mlili|ek|io|mon=av}}"

::* #T21 ("sv|mon=av", raw, force AV from native AJ)
::* expected result: "svede"
::* actual result: "{{#invoke:mlili|ek|sv|mon=av}}"

* #T22 ("sma|mon=av", raw, force AV from native AJ multiword)
* expected result: "en la samea suda" (fake AV by PP "en", is useful again)
* actual result: "{{#invoke:mlili|ek|sma|mon=av}}"

::* #T23 ("io|mon=av|maj=1", raw, force AV from native SB and uppercase)
::* expected result: "Ide"
::* actual result: "{{#invoke:mlili|ek|io|mon=av|maj=1}}"

* #T24 ("sv|mon=av|maj=1", raw, force AV from native AJ and uppercase)
* expected result: "Svede"
* actual result: "{{#invoke:mlili|ek|sv|mon=av|maj=1}}"

::* #T25 ("sma|mon=av|maj=1", raw, force AV from native AJ multiword and uppercase)
::* expected result: "En la samea suda" (fake AV by PP "en", is useful again)
::* actual result: "{{#invoke:mlili|ek|sma|mon=av|maj=1}}"

{{hr3}} <!-------------------------------->

* #T30 ("eo|tip=LA", simple example, native SB)
* expected result: link to "Aldono:Esperanto" with text "Esperanto"
* actual result: "{{#invoke:mlili|ek|eo|tip=LA}}"

::* #T31 ("ko|tip=LA", simple example, native AJ)
::* expected result: link to "Aldono:Korea" with text "korea" (note the letter case)
::* actual result: "{{#invoke:mlili|ek|ko|tip=LA}}"

* #T32 ("eo|tip=LK", simple example, native SB)
* expected result: link to "Kategorio:Esperanto" with hidden colon ":"
* actual result: "{{#invoke:mlili|ek|eo|tip=LK}}"
* actual result via debu: "{{debu|{{#invoke:mlili|ek|eo|tip=LK}}|outctl=nw}}"

::* #T33 ("ko|tip=LK", simple example, native AJ)
::* expected result: link to "Kategorio:Korea" with hidden colon ":"
::* actual result: "{{#invoke:mlili|ek|ko|tip=LK}}"
::* actual result via debu: "{{debu|{{#invoke:mlili|ek|ko|tip=LK}}|outctl=nw}}"

* #T34 ("sma|tip=LK|mon=pl", force AJ and plural from native AJ multiword)
* expected result: link to "Kategorio:Samea suda" with hidden colon ":" and text "sameaj sudaj"
* actual result: "{{#invoke:mlili|ek|sma|tip=LK|mon=pl}}"
* actual result via debu: "{{debu|{{#invoke:mlili|ek|sma|tip=LK|mon=pl}}|outctl=nw}}"

{{hr3}} <!-------------------------------->

* #T40 ("vo|tip=KE", simple example, native SB)
* expected result: below cat insertion "Kategorio:Volapuko"
* actual result: "{ {#invoke:mlili|ek|vo|tip=KE} }" (blocked)
* actual result via debu: "{{debu|{{#invoke:mlili|ek|vo|tip=KE}}|outctl=nw}}"

::* #T41 ("ko|tip=KE", simple example, native AJ)
::* expected result: below cat insertion "Kategorio:Korea"
::* actual result: "{ {#invoke:mlili|ek|ko|tip=KE} }" (blocked)
::* actual result via debu: "{{debu|{{#invoke:mlili|ek|ko|tip=KE}}|outctl=nw}}"

* #T42 ("ia|tip=KE|hin=Fivortaro", native SB, supply cat name and use hint)
* expected result: below cat insertion "Kategorio:Fivortaro" with hint "interlingvao"
* actual result: "{ {#invoke:mlili|ek|ia|tip=KE|hin=Fivortaro} }" (blocked)
* actual result via debu: "{{debu|{{#invoke:mlili|ek|ia|tip=KE|hin=Fivortaro}}|outctl=nw}}"

::* #T43 ("ko|tip=KE|hin=Fivortaro", native AJ, supply cat name and use hint)
::* expected result: below cat insertion "Kategorio:Fivortaro" with hint "korea"
::* actual result: "{ {#invoke:mlili|ek|ko|tip=KE|hin=Fivortaro} }" (blocked)
::* actual result via debu: "{{debu|{{#invoke:mlili|ek|ko|tip=KE|hin=Fivortaro}}|outctl=nw}}"

{{hr3}} <!-------------------------------->

* note that tests #T32 ... #T34 and #T40 ... #T43 depend on "debu"
* note that tests #T40 ... #T43 cannot be reasonably executed on the docs subpage without help of "pate" or "debu"

{{hr3}} <!-------------------------------->

]===]

local exporttable = {}

require('strict')

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

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

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

-- uncommentable EO vs ID constant strings (core site-related features)

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

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

-- surrogate transcoding table

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

-- uncommentable EO vs ID constant table

-- note that #E00 and #E01 are NOT supposed to be included here

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[08] = 'Erara uzo de %@, kvanto da anonimaj parametroj, legu gxian dokumentajxon'  -- EO #E08
        -- contaberaroj[08] = 'Penggunaan salah %@, jumlah parameter anonim, bacalah dokumentasinya'    -- ID #E08
      contaberaroj[09] = 'Erara uzo de %@, longo de parametro'                                       -- EO #E09
        -- contaberaroj[09] = 'Penggunaan salah %@, panjang parameter'                                  -- ID #E09
      contaberaroj[14] = 'Nevalida baza tipo "tip=" en %@'                          -- EO #E14
        -- contaberaroj[14] = 'Tipe dasar salah "tip=" di %@'                          -- ID #E14
      contaberaroj[15] = 'Nevalida signocxeno "hin=" "prf=" "err=" en %@'           -- EO #E15
        -- contaberaroj[15] = 'String salah "hin=" "prf=" "err=" di %@'                -- ID #E15
      contaberaroj[16] = 'Erara uzo de %@ pro parametroj'                           -- EO #E16
        -- contaberaroj[16] = 'Penggunaan salah %@ oleh karena parameter'              -- ID #E16

-- uncommentable EO vs ID constant strings (misc)

      local constrevid = "evidente nevalida lingvokodo"     -- EO replacement
        -- local constrevid = "kode bahasa jelas-jelas salah"  -- ID replacement
      local constrneli = "nekonata lingvokodo"              -- EO replacement
        -- local constrneli = "kode bahasa tidak dikenal"      -- ID replacement

  -- constant table (3 integers for preliminary parameter check)

  local contabparam = {}
  contabparam[0] = 1   -- minimal number of anon parameters
  contabparam[1] = 1   -- maximal number of anon parameters
  contabparam[2] = 160 -- maximal length of single para (min is hardcoded as ONE)

-- constants to control behaviour from source AKA semi-hardcoded parameters

  local conbookodlng  = false  -- "true" to allow long codes like "zh-min-nan"
  local conboomiddig  = false  -- "true" to allow middle digit "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.indprefi = 'Indekso'                                -- "Index"
  -- contabovrd.indprefi = 'Indeks'
  -- contabovrd.apxprefi = 'Aldono'                                 -- "Appendix"
  -- contabovrd.apxprefi = 'Lampiran'
  -- contabovrd.parentfn = string.char(0xC5,0x9C) .. 'ablono:nope'  -- "Template:nope" (!!! no surr translation !!!)

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

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

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

---- SPECIAL VAR:S ----

local qldingvoj = {}     -- type "table" and nested
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

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

---- MATH PROCEDURES [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

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

-- Local function MATHBITWRIT  !!!FIXME!!! soon can be removed

local function mathbitwrit (numinkoming, numbityndex, boowrite)
  local numpatched = 0
  local numcountup = 0
  local numweight = 1 -- single bit value 1 -> 2 -> 4 -> 8 ...
  local boosinglebit = false
  while true do
    if ((numinkoming==0) and (numcountup>numbityndex)) then
      break -- we have run out of bits on BOTH possible sources
    end--if
    if (numcountup==numbityndex) then
      boosinglebit = boowrite -- overwrite bit
    else
      boosinglebit = (mathmod(numinkoming,2)==1) -- pick bit
    end--if
    numinkoming = mathdiv(numinkoming,2) -- shift right
    if (boosinglebit) then
      numpatched = numpatched + numweight -- add one bit rtl only if true
    end--if
    numcountup = numcountup + 1 -- count up here until we run out of bits
    numweight = numweight * 2
  end--while
  return numpatched
end--function mathbitwrit

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

---- NUMBER CONVERSION PROCEDURES [N] ----

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

-- Local function LFNNUMTO2DIGIT

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

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

local function lfnnumto2digit (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 lfnnumto2digit

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

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

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

-- Local function LFGSTRINGRANGE

local function lfgstringrange (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 lfgstringrange

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

-- Local function LFGPOKESTRING

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

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

-- test whether char is an ASCII digit "0"..."9", return boolean

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

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

-- test whether char is an ASCII uppercase letter, return boolean

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

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

-- test whether char is an ASCII lowercase letter, return boolean

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

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

-- Local function LFGTESTVOWEL

-- Test whether char is a vowel, return tristate, ZERO no
-- vowel, ONE (e i u), TWO (a o).

local function lfgtestvowel (numonechaar)
  local numtrivowel = 0
  if (numonechaar>=96) then
    numonechaar = numonechaar - 32
  end--if
  if ((numonechaar==69) or (numonechaar==73) or (numonechaar==85)) then
    numtrivowel = 1
  end--if
  if ((numonechaar==65) or (numonechaar==79)) then
    numtrivowel = 2
  end--if
  return numtrivowel
end--function lfgtestvowel

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

---- UTF8 PROCEDURES [U] ----

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

-- Local function LFULNUTF8CHAR

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

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

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

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

-- Input  : * strmyset -- "eo" or "sv" or "GENE"
--          * num16valin -- undecoded UINT16BE value, $C200... $DEFF

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

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

local function lfusearchcasepair (strmyset, num16valin)

  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

-- 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 function lfutristletr (strsel4set, strin4trist)

  local numtype4x = 0 -- final result to be returned, preASSume invalid
  local numlong4den = 0 -- actual length of input string
  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 numcxa4unde = 0
  local numw4upper = 0
  local numw4lower = 0

  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  !!!FIXME!!! obsolete approach

-- 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
--          * booup6cas  : 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
-- [G] lfgpokestring lfgtestuc lfgtestlc
-- [E] mathdiv mathmod mathbitwrit  !!!FIXME!!! soon can be removed

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

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

  local numlong6den = 0 -- actual length of input string
  local numokt6index = 0
  local numlong6bor = 0 -- expected length of single char

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

  local numcha6r = 0 -- UINT8 beginning char
  local numcha6s = 0 -- UINT8 later char (BIG ENDIAN, lower value here above)
  local numcxa6rel = 0 -- UINT8 code relative to beginning of block $00...$FF

  local numtem6p = 0

  local boowan6tlowr = false
  local boois6uppr = false
  local boois6lowr = false

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

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

  while true do -- 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
    boois6uppr  = false -- preASSume on every iteration
    boois6lowr  = false -- preASSume on every iteration
    numdel6ta   = 0 -- preASSume on every iteration
    numlong6bor = 1 -- preASSume on every iteration

    while true do -- 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)
      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
        boois6uppr = lfgtestuc(numcha6r)
        boois6lowr = lfgtestlc(numcha6r)
        if (boois6uppr and boowan6tlowr) then
          numdel6ta = 32 -- ASCII UPPER->lower
        end--if
        if (boois6lowr and booup6cas) then
          numdel6ta = -32 -- ASCII lower->UPPER
        end--if
        break -- success with ASCII and one char almost done
      end--if

      numcha6s = string.byte (strinco6cs,(numokt6index+2),(numokt6index+2)) -- only $80 to $BF
      numcxa6rel = (mathmod(numcha6r,4)*64) + (numcha6s-128) -- 4 times 64

    if ((strsel6set=='eo') and ((numcha6r==196) or (numcha6r==197))) then
      numtem6p = mathbitwrit (numcxa6rel,0,false) -- bad way to do AND $FE
      if ((numtem6p==8) or (numtem6p==28) or (numtem6p==36) or (numtem6p==52) or (numtem6p==92) or (numtem6p==108)) then
        boois6uppr = (numtem6p==numcxa6rel) -- UC below, block of 1
        boois6lowr = not boois6uppr
        if (boois6uppr and boowan6tlowr) then
          numdel6ta = 1 -- UPPER->lower
        end--if
        if (boois6lowr and booup6cas) then
          numdel6ta = -1 -- lower->UPPER
        end--if
        break -- success with -eo- and one char almost done
      end--if
    end--if ((strsel6set=='eo') and ...

    if ((strsel6set=='sv') and (numcha6r==195)) then
      numtem6p = mathbitwrit (numcxa6rel,5,false) -- bad way to do AND $DF
      if ((numtem6p==196) or (numtem6p==197) or (numtem6p==201) or (numtem6p==214)) then
        boois6uppr = (numtem6p==numcxa6rel) -- UC below, block of 32
        boois6lowr = not boois6uppr
        if (boois6uppr and boowan6tlowr) then
          numdel6ta = 32 -- UPPER->lower
        end--if
        if (boois6lowr and booup6cas) then
          numdel6ta = -32 -- lower->UPPER
        end--if
        break -- success with -sv- and one char almost done
      end--if
    end--if ((strsel6set=='sv') and ...

      break -- finally to join mark -- unknown non-ASCII char is a fact :-(
    end--while -- 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 -- genuine loop over incoming string (this is REST)

  return strinco6cs

end--function lfucaserest

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

---- HIGH LEVEL STRING PROCEDURES [I] ----

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

-- Local function LFIBREWCATHINT

-- Brew sorting hint/key for wiki category by lowering all letters and
-- removing junk chars other than known letters and numbers. All ASCII and
-- selected non-ASCII letters do count as such. Particularly ban spaces
-- and dashes. Controllably UTF8-aware.

-- Input  : * strsignaro  -- "ASCII" (default, empty string or type "nil"
--                           will do too) "eo" "sv" "GENE"
--          * strhinthink -- empty is useless but cannot
--                           cause major harm
--          * bookeepuk   -- keep unknown non-ASCII (by default dropped
--                           for sets other than "GENE", whereas
--                           for "GENE" always kept)

-- Output : * strhasiil -- risk of empty

-- Depends on procedures : (restricted LFUCASEREST)
-- [U] lfulnutf8char lfusearchcasepair lfutristletr lfucaserest
-- [G] lfgpokestring lfgtestnum lfgtestuc lfgtestlc
-- [E] mathdiv mathmod mathbitwrit  !!!FIXME!!! soon can be removed

-- Simplified strategy:
-- * numbers unchanged
-- * ASCII lowercase unchanged
-- * ASCII uppercase lowered
-- * unknown non-ASCII dropped for sets other than "GENE" unless bookeepuk
-- * non-ASCII sent to case sub with attempt to lower
-- * everything else dropped
-- * broken stream aborts and gives empty result

local function lfibrewcathint (strsignaro, strhinthink, bookeepuk)

  local strhasiil = ''
  local strnew7char = ''
  local numstrleon = 0
  local numeindx = 1 -- ONE-based
  local numczaar = 0
  local numczanx = 0 -- pre-picked next char
  local numettfyra = 0

  numstrleon = string.len (strhinthink)

  while true do -- outer genuine loop over source string

    if (numeindx>numstrleon) then
      break -- empty input is useless but cannot cause major harm
    end--if

    numczaar = string.byte (strhinthink,numeindx,numeindx)
    numeindx = numeindx + 1 -- do INC here
    numettfyra = lfulnutf8char (numczaar) -- 1...4 or ZERO on error
    if (numettfyra==0) then
      strhasiil = ''
      break -- broken stream -> bugger all -- exit outer loop
    end--if
    numczanx = 0 -- preASSume none
    if (numeindx<=numstrleon) then -- pick but do NOT INC
      numczanx = string.byte (strhinthink,numeindx,numeindx)
    end--if

    while true do -- inner fake loop
      if (numettfyra==1) then
        if (lfgtestnum(numczaar) or lfgtestlc(numczaar)) then
          strnew7char = string.char (numczaar)
          break -- numbers and ASCII lowercase pass unchanged
        end--if
        if (lfgtestuc(numczaar)) then
          strnew7char = string.char (numczaar+32) -- lower ASCII letter
          break -- lower it
        end--if
      end--if (numettfyra==1) then
      if ((numettfyra==2) and ((strsignaro=='eo') or (strsignaro=='sv') or (strsignaro=='GENE'))) then
        strnew7char = string.char(numczaar,numczanx) -- preASSume unchanged
        if ((not bookeepuk) and (strsignaro~='GENE')) then -- drop unknown
          if (lfutristletr(strsignaro,strnew7char)==0) then -- no known letter
            strnew7char = '' -- discard char
            break
          end--if
        end--if
        -- if (strsignaro=='GENE') then -- below: want lower, do ONE only
          -- strnew7char = lfucasegene (strnew7char,false,false)
        -- else
          strnew7char = lfucaserest (strsignaro,strnew7char,false,false)
        -- end--if
        break -- done with lowercased or unchanged char
      end--if ((numettfyra==2) and ...
      strnew7char = '' -- anything else -> discard char
      break -- finally to join mark
    end--while -- inner fake loop -- join mark

    strhasiil = strhasiil .. strnew7char -- ASCII char or UTF8 char or ""
    numeindx = numeindx + numettfyra - 1 -- do ADD here

  end--while -- outer genuine loop over source string

  return strhasiil

end--function lfibrewcathint

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

-- 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 functions :
-- [G] lfgtestnum lfgtestlc

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

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

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

-- Local function LFIPL2ALTWRE

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

local function lfipl2altwre (strbeforfill, numaskikodo, varsupstitu)

  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 (lfgstringrange(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 lfipl2altwre

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

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

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

-- To be called ONLY from "LFHRECUSURRSTRTAB".

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

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

-- Local function LFIAKUPLUEO

-- Accusativize or pluralize an Esperanto term (minimal version: no
-- stop words, no punctuation, no uppercase, no error checking).

-- Input  : * strtypeinf -- type of inflection "j" "n" "jn" must be lowercase
--          * str28term  -- term in nominative singular form

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

local function lfiakuplueo (strtypeinf, str28term)

  local str28outfo = '' -- we will rebuild it
  local num27lon = 0
  local num27plindex = 0 -- ZERO-based
  local nummychar = 0
  local nummyches = 0
  local boodontbother = false
  local booendofword = false
  local boovowdig = false -- preASSume not yet had a vowel or digit in a word

  while true do -- outer fake loop

    num27lon = string.len (str28term)

    while true do -- inner genuine loop

      if (num27plindex>=num27lon) then
        break -- done whole term
      end--if
      nummychar = string.byte (str28term,(num27plindex+1),(num27plindex+1))
      num27plindex = num27plindex + 1 -- ZERO-based
      str28outfo = str28outfo .. string.char(nummychar) -- copy ch in any case
      boodontbother = (nummychar==32)
      if (not boodontbother) then
        booendofword = (num27plindex==num27lon) -- end of term is end of word
        if (not booendofword) then -- one more chance
          nummyches = string.byte (str28term,(num27plindex+1),(num27plindex+1))
          booendofword = (nummyches==32)
        end--if
        if (booendofword) then -- maybe add suffix soon
          if (boovowdig and (lfgtestvowel(nummychar)==2)) then
            str28outfo = str28outfo .. strtypeinf -- DO IT HERE
          end--if
          boovowdig = false -- reset after having used it
        else
          boovowdig = boovowdig or lfgtestnum(nummychar) or (lfgtestvowel(nummychar)~=0)
        end--if (booendofword) else
      end--if (not boodontbother) then

    end--while -- inner genuine loop

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

  return str28outfo

end--function lfiakuplueo

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

-- Local function LFIVARILINGVONOMO

-- Input  : * strlang29name : such as "Ido" or "dana" or "samea suda"
--          * numtip29mon   : 0 native -- 1 la (AJ SB) -- 2 aj -- 3 sf (root)
--                            4 av (AV real or surrogate) -- 5 pl (AJ and PL)
--          * boomaju

-- Depends on functions :
-- [I] lfiakuplueo
-- [U] lfulnutf8char lfusearchcasepair lfucaserest
-- [G] lfgpokestring lfgtestnum lfgtestuc lfgtestlc lfgtestvowel
-- [E] mathdiv mathmod mathbitwrit  !!!FIXME!!! soon can be removed

-- incoming multiword SB such as "Malnova Volapuko" is bad, we
-- can't process such

-- note that for "numtip29mon" 1 ie "la" we will add the article to
-- native AJ only, never to native SB, whereas type 4 ie "av" can force AJ
-- and subsequently add "la" resulting in something like "en la samea suda"

-- note that we MUST NOT change the word class to AJ
-- for "numtip29mon" 4 ie "av" if multiword (mutiword SB fails here)

-- for "numtip29mon" 5 ie "pl" we change the word class to AJ too

local function lfivarilingvonomo (strlang29name, numtip29mon, boomaju)

  local num29lon     = 0
  local numourchar   = 0
  local numourches   = 0
  local boois29noun  = false
  local boomulwords  = false
  local booaddprepen = false
  local booaddartla  = false

  if ((numtip29mon>=1) and (numtip29mon<=5)) then

    num29lon = string.len (strlang29name)
    numourchar = string.byte (strlang29name,num29lon,num29lon) -- last

    boois29noun = (numourchar==111) -- "o"
    boomulwords = (string.find (strlang29name, ' ', 1, true)~=nil) -- plain tx
    booaddprepen = (numtip29mon==4) and boomulwords -- surrogate AV with "en"
    booaddartla = (not boois29noun) and ((numtip29mon==1) or booaddprepen)

    if ((numtip29mon>=2) and (not booaddprepen)) then -- change word class
      strlang29name = string.sub (strlang29name,1,-2) -- cut off last letter
      strlang29name = lfucaserest ('eo',strlang29name,false,false) -- lower it
    end--if
    if (booaddartla) then
      strlang29name = 'la ' .. strlang29name -- we will NOT pluralize this
    end--if
    if ((numtip29mon==2) or (numtip29mon==5)) then
      strlang29name = strlang29name .. "a" -- now AJ, maybe pluralize later
    end--if
    if (numtip29mon==4) then
      if (boomulwords) then
        strlang29name = "en " .. strlang29name -- surrog AV from PP "en" + ...
      else
        strlang29name = strlang29name .. "e" -- now AV
      end--if
    end--if
    if (numtip29mon==5) then -- can hande multiword AJ too
      strlang29name = lfiakuplueo ('j', strlang29name)
    end--if

  end--if ((numtip29mon>=1) and (numtip29mon<=5)) then

  if (boomaju) then
    strlang29name = lfucaserest ('eo',strlang29name,true,false) -- eo, upper, one
  end--if

  return strlang29name

end--function lfivarilingvonomo

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

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

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

-- Local function LFHVALI1STATUS98CODE

-- Depends on functions :
-- [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 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 lfhvali1status98code

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

-- Local function LFHCONSTRUCTERAR

-- Construct 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] lfnnumto2digit
-- [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 (numerar3code, boopeek3it)
  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 .. lfnnumto2digit(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 lfhconstructerar

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

-- Local function LFHBREWERRSM

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

-- Depends on procedures :
-- [H] lfhconstructerar
-- [I] lfipl2altwre
-- [G] lfgstringrange
-- [N] lfnnumto2digit
-- [E] mathisintrange mathdiv mathmod

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

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

-- We DO NOT eat the name of the guilty submodule since it is included
-- in the error code #E03 #E05 and its message. Also for the submodule
-- we report code only, no description.

local function lfhbrewerrsm (numerar7code, strparent7nm, numsubkodo)
  local stryt7sux = ''
  local strfromsubo = ''
  stryt7sux = constrlaxhu .. constrelabg .. lfipl2altwre(lfhconstructerar(numerar7code,true),64,strparent7nm) .. constrelaen .. constrlaxhu
  if (mathisintrange(numsubkodo,1,98)) then
    strfromsubo = 'Submodule reports ' .. lfhconstructerar (numsubkodo,false)
    stryt7sux = stryt7sux .. '<br>' .. constrlaxhu .. constrelabg .. strfromsubo .. constrelaen .. constrlaxhu
  end--if
  return stryt7sux
end--function lfhbrewerrsm

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

-- Local function LFHRECUSURRSTRTAB

-- 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 -- "eo" or NOPE "sv" to transcode surrogates,
--                          anything else (preferably type "nil") to skip this
--          * bookeys -- transcode keys too (preferably either "true"
--                       or type "nil")

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

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

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

local function lfhrecusurrstrtab (varinkommen, strlingkod, bookeys)

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

  if (type(varinkommen)=='string') then
    if (strlingkod=='eo') then
      varinkommen = lfikodeosg (varinkommen) -- surr
    end--if
    -- if (strlingkod=='sv') then
      -- varinkommen = lfikodsvsg (varinkommen) -- surr
    -- end--if
    varutmatning = varinkommen -- copy, change or no change
    boodone = true
  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 = lfhrecusurrstrtab (k4k, strlingkod, nil) -- RECURSION
      else
        varnky = k4k
      end--if
      if ((type(v4v)=='string') or (type(v4v)=='table')) then
        v4v = lfhrecusurrstrtab (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 lfhrecusurrstrtab

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

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

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

function exporttable.ek (arxframent)

  -- unknown type

  local vartymp = 0 -- variable without type

  -- special type "args" AKA "arx"

  local arxsomons = 0 -- metaized "args" from our own or caller's "frame"

  -- general "tab"

  local tablg76yleft = {} -- y-index -> leftmost column (langcode to langname)

  -- peeked stuff

  local strpiklangcode = '' -- "en" privileged site language
  local strpikkatns    = '' -- "Category"
  local strpikindns    = '' -- "Index"
  local strpikapxns    = '' -- "Appendix"
  local strpikparent   = '' -- "Template:nope" FULLPAGENAME

  -- general "str"

  local strnambah  = ''  -- langname (without prefix "Bahasa")
  local strnambauc = ''  -- langname uppercased begin ("Angla")
  local strnambaty = ''  -- langname processed according to "mon=" and "maj=1"
  local strtymp    = ''
  local strvisgud  = ''  -- visible output or cat insertion on success
  local strviserr  = ''  -- visible error message on error (no tracking cat)
  local strret     = ''  -- result string

  -- parameters "str"

  local strkodbah  = ''  -- langcode (2 or 3 lowercase) from arxsomons[1]
  local strhinhin  = ''  -- name of category KE (langcode will brew the hint)
  local strprfprf  = ''  -- prefix before langname
  local strerarpl  = ''  -- custom but common placeholder

  -- general "num"

  local numerr       = 0 -- 1 inter 2 mali 3 nombri 8 tip 15 cxeno3 16 misc
  local num2statcode = 0
  local numpindex    = 0 -- number of anon params
  local numlangsta   = 0 -- 2 known -- 1 unknown -- 0 obviously invalid

  -- parameters "num"

  local numbasetipe = 0 -- 0 raw -- 1 LA -- 2 LK -- 3 KE -- 4 empty
  local nummontip   = 0 -- 0 native -- 1 la -- 2 aj -- 3 sf -- 4 av -- 5 pl

  -- general boo

  local bootimp = false

  -- parameters boo

  local boonem  = false -- avoid wall in visible link of type LA LK
  local boomaj  = false -- force uppercase
  local boonoc  = false -- infamous "nocat" (cannot cause an error)
  local booper  = false -- keep link or cat insertion on error
  local boohavehin = false
  local boohaveprf = false
  local boohaveerr = 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 SEMIGENEROUS ----

  -- 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
  -- no "strpikpareuf" here

  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
    strpikapxns    = contabovrd.apxprefi or (mw.site.namespaces[102] or {})['name'] or 'Appendix'  -- custom namespace
    strpikparent   = contabovrd.parentfn or arxframent:getParent():getTitle() or 'Template:nope'   -- fullpagename
    if ((type(strpiklangcode)~='string') or (type(strpikkatns)~='string') or (type(strpikapxns)~='string') or (type(strpikparent)~='string')) then
      numerr = 1 -- #E01 internal (unlikely)
    end--if
  end--if (numerr==0) then

  ---- PROCESS MESSAGES SURR ONLY IF NEEDED ----

  -- needed for all errors except #E01

  if (numerr==0) then
    contaberaroj = lfhrecusurrstrtab (contaberaroj, strpiklangcode, nil)
  end--if

  ---- PICK ONE SUBTABLE T76 ----

  -- 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['T76'] -- from "loaddata-tbllingvoj"
    if (type(vartymp)~='table') then -- important check
      numerr = 2 -- #E02 malica
      break -- to join mark
    end--if
    tablg76yleft = vartymp -- y-index -> leftmost column (langcode to langname)

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

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

  -- must be seized independently on "numerr" even if we already suck

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

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

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

  -- do NOT use sub "lfhbrewerror", report our name (NOT of template), in EN

  if (numerr==1) then
    strtymp = '#E01 Internal error in module "mlili".'
    strviserr = constrlaxhu .. constrelabg .. strtymp .. constrelaen .. constrlaxhu
  end--if

  ---- PRELIMINARILY ANALYZE ANONYMOUS PARAMETERS ----

  -- this will catch holes, empty parameters, too long parameters,
  -- and wrong number of parameters

  -- below on exit var "numpindex" will contain number of
  -- prevalidated anonymous params

  -- this depends on 3 constants:
  -- * contabparam[0] minimal number
  -- * contabparam[1] maximal number
  -- * contabparam[2] maximal length (default 200)

  if (numerr==0) then -- using vartymp calling lfgstringrange
    numpindex = 0 -- ZERO-based
    while true do
      vartymp = arxsomons [numpindex+1] -- can be type "nil"
      if ((type(vartymp)~='string') or (numpindex>contabparam[1])) then
        break -- good or bad
      end--if
      if (not lfgstringrange (vartymp,1,contabparam[2])) then
        numerr = 9 -- #E09 anon param empty or too long
        break -- only bad here
      end--if
      numpindex = numpindex + 1 -- on exit has number of valid parameters
    end--while
    if ((numerr==0) and ((numpindex<contabparam[0]) or (numpindex>contabparam[1]))) then
      numerr = 8 -- #E08 number of anon params
    end--if
  end--if

  ---- PROCESS ONE OBLIGATORY ANONYMOUS PARAM INTO ONE STRING AND ONE NUM ----

  -- now var "numpindex" sudah contains number of prevalidated params
  -- and is always 1 and is useless

  -- here we prevalidate and assign "strkodbah" and "numlangsta"

  -- this depends directly on 2 boolean constant:s and indirectly
  -- on constant table "contabisbanned" via "lfhvalidatelnkoadv"

  if (numerr==0) then
    numlangsta = 2 -- this is just weak hope ...
    strkodbah = arxsomons[1] -- langcode (obligatory, "??" permitted)
    if (not lfivalidatelnkoadv(strkodbah,false,true,conbookodlng,conboomiddig,false)) then
      numlangsta = 0 -- 2 known -- 1 unknown -- 0 obviously invalid
    end--if
  end--if

  ---- PROCESS 0...9 OPTIONAL NAMED PARAMS ----

  -- data type "boo" (3+1) (assign a "boo") :
  -- * "nem=" -- "1" -> avoid wall in visible link of type LA LK
  -- * "maj=" -- "1" -> force uppercase
  -- * "noc=" -- "true" -> no cat (this is "nocat", no error possible)
  -- * "per=" -- "1" -> keep link or category if unknown
  -- multiple choice (2) (assign a "num") :
  -- * "tip=" - base type of task, 3 -> 4 possible values (later 5)
  --            (0 raw) -- 1 LA -- 2 LK -- 3 KE -- (later also: 4 empty)
  -- * "mon=" - type of langname, 5 -> 6 possible values
  --            (0 native) -- 1 la -- 2 aj -- 3 sf -- 4 av -- 5 pl
  -- string (2...80 octet:s) (3) (fill string and assign a "boo" to "true") :
  -- * "hin=" - name of category KE (langcode will brew the hint)
  -- * "prf=" - prefix before langname
  -- * "err=" - common placeholder

  if (numerr==0) then
    while true do -- fake loop

      boonem = false
      vartymp = arxsomons['nem'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then
        if (vartymp=='1') then
          boonem = true
        else
          numerr = 16 -- #E16 other param crime
          break
        end--if
      end--if

      boomaj = false
      vartymp = arxsomons['maj'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then
        if (vartymp=='1') then
          boomaj = true
        else
          numerr = 16 -- #E16 other param crime
          break
        end--if
      end--if

      boonoc = (arxsomons['noc']=='true') -- this one needs word "true" and CANNOT cause an error

      booper = false
      vartymp = arxsomons['per'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then
        if (vartymp=="1") then
          booper = true
        else
          numerr = 16 -- #E16 other param crime
          break
        end--if
      end--if

      numbasetipe = 0
      vartymp = arxsomons['tip'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then
        if (vartymp=="LA") then
          numbasetipe = 1
        end--if
        if (vartymp=="LK") then
          numbasetipe = 2
        end--if
        if (vartymp=="KE") then
          numbasetipe = 3
        end--if
        if (numbasetipe==0) then
          numerr = 14 -- #E14 -- faulty base type
          break
        end--if
      end--if

      nummontip = 0 -- default "raw" / "native"
      vartymp = arxsomons['mon'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then
        if (vartymp=='la') then
          nummontip = 1
        end--if
        if (vartymp=='aj') then
          nummontip = 2
        end--if
        if (vartymp=='sf') then
          nummontip = 3
        end--if
        if (vartymp=='av') then
          nummontip = 4
        end--if
        if (vartymp=='pl') then
          nummontip = 5
        end--if
        if (nummontip==0) then
          numerr = 16 -- #E16 other param crime
          break
        end--if
      end--if

      boohavehin = false
      vartymp = arxsomons['hin'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then -- do NOT merge if:s
        if (lfgstringrange (vartymp,2,80)) then
          strhinhin = vartymp
          boohavehin = true
        else
          numerr = 15 -- #E15 -- crime with "hin=" "prf=" "err="
          break
        end--if
      end--if

      boohaveprf = false
      vartymp = arxsomons['prf'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then -- do NOT merge if:s
        if (lfgstringrange (vartymp,2,80)) then
          strprfprf = vartymp
          boohaveprf = true
        else
          numerr = 15 -- #E15 -- crime with "hin=" "prf=" "err="
          break
        end--if
      end--if

      boohaveerr = false
      vartymp = arxsomons['err'] -- optional, NOT prevalidated
      if (type(vartymp)=='string') then -- do NOT merge if:s
        if (lfgstringrange (vartymp,2,80)) then
          strerarpl = vartymp
          boohaveerr = true
        else
          numerr = 15 -- #E15 -- crime with "hin=" "prf=" "err="
          break
        end--if
      end--if

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

  ---- CHECK FOR CONFLICTING PARAMETERS ----

  -- "tip="  no conflicts possible (only values 0...3 can be
  --                                requested, value 4 not directly)
  --         default is fixed to "raw" 0
  --         may be changed later from "LA" 1 "LK" 2 to "raw" 0 (for 1 reason)
  --                              or from "KE" 2 to "empty" 4 (for 2 reasons)
  -- "nem=1" is illegal for base type "KE" 3 or "raw" 0
  --         default is fixed to false (wall with base type "KE" is separate)
  --         is negative (no-default "1" means "no wall")
  -- "mon="  is illegal for base type "KE" 3
  --         is illegal together with "nem=1"
  --         default is fixed to "native" 0
  -- "maj=1" is illegal for base type "KE" 3
  --         is illegal together with "nem=1"
  --         default is fixed to false (lowercasing separate from this type)
  -- "hin="  is illegal for base type other than "KE" 3
  --         default is fixed to false and empty
  -- "prf="  is illegal for base type "raw" 0
  --         is illegal together with "mon="
  --         is illegal together with "hin="
  --         default is fixed to false and empty
  -- "per=1" is illegal for base type "raw" 0
  --         default is fixed to OFF
  -- "err="  no conflicts possible

  if (numerr==0) then

      if (boonem and ((numbasetipe==0) or (numbasetipe==3))) then
        numerr = 16 -- #E16 other param crime
      end--if

      if ((nummontip~=0) and (numbasetipe==3)) then
        numerr = 16 -- #E16 other param crime
      end--if
      if ((nummontip~=0) and boonem) then
        numerr = 16 -- #E16 other param crime
      end--if

      if (boomaj and (numbasetipe==3)) then
        numerr = 16 -- #E16 other param crime
      end--if
      if (boomaj and boonem) then
        numerr = 16 -- #E16 other param crime
      end--if

      if (boohavehin and (numbasetipe~=3)) then
        numerr = 16 -- #E16 other param crime
      end--if

      if (boohaveprf and (numbasetipe==0)) then
        numerr = 16 -- #E16 other param crime
      end--if
      if (boohaveprf and (nummontip~=0)) then
        numerr = 16 -- #E16 other param crime
      end--if
      if (boohaveprf and boohavehin) then
        numerr = 16 -- #E16 other param crime
      end--if

      if (booper and (numbasetipe==0)) then
        numerr = 16 -- #E16 other param crime
      end--if

  end--if

  ---- CHANGE BASE TYPE IF NEEDED DUE TO NOCAT ----

  if ((numerr==0) and boonoc and (numbasetipe==3)) then
    numbasetipe = 4 -- switch to empty result pseudo type 4
  end--if

  ---- PEEK THE LANGNAME ----

  -- * tristate "numlangsta" : 2 known -- 1 unknown -- 0 obviously invalid
  -- * from above 2 or 0, now confirm 2 or downgrade to 1 or skip this

  -- for langname in site language ("c0"):
  -- * type "nil" can become (numlangsta=1) (unknown code) if the site
  --   langcode works, otherwise #E02 (broken submodule)
  -- * "-" is unconditionally evil with #E02 (broken submodule)

  -- here we depend on "strpiklangcode" and submodule "loaddata-tbllingvoj"

  if ((numerr==0) and (numbasetipe~=4) and (numlangsta==2)) then

    while true do -- fake loop
      vartymp = tablg76yleft[strkodbah] -- pick langname
      if (type(vartymp)~='string') then
        if (type(tablg76yleft[strpiklangcode])=='string') then
          numlangsta = 1 -- unknown code given (site code works)
        else
          numerr = 2 -- #E02 broken submodule (site code does NOT work either)
        end--if
        break
      end--if
      if (not lfgstringrange (vartymp,2,60)) then -- less than 2 lett not legal
        numerr = 2 -- #E02 broken submodule
        break
      end--if
      strnambah = vartymp -- got langname :-)
      break -- finally to join mark
    end--while -- fake loop -- join mark

  end--if

  ---- WHINE IF YOU MUST #E02...#E98 ----

  -- reporting depends on uncommentable strings

  if (numerr>1) then
    if (numerr==3) then -- #E03
      strviserr = lfhbrewerrsm(numerr,strpikparent,num2statcode) -- 2 lines
    else
      strviserr = lfhbrewerrsm(numerr,strpikparent) -- 1 line
    end--if
  end--if

  ---- CHANGE BASE TYPE AND MON TYPE TO RAW IF NEEDED DUE TO BAD CODE ----

  -- only if code is bad (obviously invalid 0 or unknown 1) and
  -- "per=1" was NOT used then change base type "LA" 1 or "LK" 2 to type
  -- "raw" 0, and base type "KE" 3 to empty result AKA pseudo type 4

  -- if code is bad then always reset "mon=" type to "raw" 0

  if ((numerr==0) and (numlangsta~=2) and (not booper)) then
    if ((numbasetipe==1) or (numbasetipe==2)) then
      numbasetipe = 0 -- switch to type "raw"
    end--if
    if (numbasetipe==3) then
      numbasetipe = 4 -- switch to empty result AKA pseudo type 4
    end--if
  end--if

  if ((numerr==0) and (numlangsta~=2)) then
    nummontip = 0 -- default "raw" / "native"
  end--if

  ---- APPLY MESSAGES FOR BAD CODES ----

  if ((numerr==0) and (numbasetipe~=4)) then

    if (numlangsta==0) then
      if (boohaveerr) then -- have "err="
        strnambah = strerarpl -- common placeholder, custom message
      else
        strnambah = constrevid -- invalid langcode, fixed message
      end--if
    end--if

    if (numlangsta==1) then
      if (boohaveerr) then -- have "err="
        strnambah = strerarpl -- common placeholder, custom message
      else
        strnambah = constrneli -- unknown langcode, fixed message
      end--if
    end--if

  end--if

  ---- BREW THE VISIBLE PART FOR RAW OR FOR LINK ----

  -- strkodbah   : langcode (not needed anymore here)
  -- strnambah   : langname (without prefix "Bahasa")
  --               always >= 2 letters (from above)
  -- strnambauc  : langname uppercased begin ("Angla") (brew here)
  -- strnambaty  : langname processed according to "mon="
  --               and "maj=1" (brew here)
  -- numbasetipe : 0 raw -- 1 LA -- 2 LK colon YES -- 3 KE -- 4 empty
  -- nummontip   : 0 native -- 1 la -- 2 aj -- 3 sf -- 4 av -- 5 pl
  -- boomaj

  if ((numerr==0) and (numbasetipe~=4)) then
    strnambauc = lfucaserest ('eo',strnambah,true,false) -- make upper one letter
    strnambaty = lfivarilingvonomo (strnambah, nummontip, boomaj)
  end--if

  ---- CARRY OUT THE TRIVIAL WORK WITH BASE TYPE (0) RAW ----

  if ((numerr==0) and (numbasetipe==0)) then
    strvisgud = strnambaty
  end--if

  ---- CARRY OUT THE HARD WORK WITH TYPE (1,2,3) LINK OR CAT INSERTION ----

  -- numbasetipe : 0 raw -- 1 LA -- 2 LK colon YES -- 3 KE -- 4 empty

  -- "prf=" is legal for all LA LK KE but not together with "hin="

  do -- scope
    local strbasepagename = ''
    local strprefix = '' -- "Appendix:" 1 or ":Category:" 2 or "Category:" 3
    local strbefore = '' -- before wall (link target)
    local strafteer = '' -- aft wall (visible link text) (no wall if this empty)

    if ((numerr==0) and (numbasetipe~=0) and (numbasetipe~=4)) then

    if (numbasetipe==1) then
      strprefix = strpikapxns .. ':' -- LA "Appendix:"
    end--if
    if (numbasetipe==2) then
      strprefix = ":" .. strpikkatns .. ':' -- LK ":Category:"
    end--if
    if (numbasetipe==3) then
      strprefix = strpikkatns .. ':' -- KE "Category:"
    end--if

    if (boohaveprf) then
      strbasepagename = strprfprf .. " (" .. strnambah .. ")" -- no force uppercase
    else
      strbasepagename = strnambauc -- force uppercased begin
    end--if

    if (numbasetipe==3) then
      if (boohavehin) then
        strbefore = strhinhin
        strafteer = lfibrewcathint ('eo',strnambah,false)
      else
        strbefore = strbasepagename
        strafteer = '' --  no wall is tolerable
      end--if
    else
      strbefore = strbasepagename
      if (boonem) then
        strafteer = '' --  no wall is tolerable in visible link of type LA LK
      else
        strafteer = strnambaty -- augmented langname
      end--if
    end--if (numbasetipe==3) else

      strvisgud = "[[" .. strprefix .. strbefore -- prefix ends with colon ":"
      if (strafteer~='') then
        strvisgud = strvisgud .. "|" .. strafteer
      end--if
      strvisgud = strvisgud .. "]]"

    end--if ((numerr==0) and (numbasetipe~=0) and (numbasetipe~=4)) then

  end--do scope

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

  if (numerr==0) then -- no separate string for cat:s here
    strret = strvisgud
  else
    strret = strviserr
  end--if
  return strret -- can be empty

end--function

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

return exporttable