Saltu al enhavo

Modulo:mgrup

El Vikivortaro
 MODULO
Memtesto disponeblas sur la paĝo Ŝablono:vfvg.
  • Ĉi tiu modulo efektivigas laboron de ŝablono {{vfvg}}.

--[===[

MODULE "MGRUP" (ligiloj vortfarado vortgrupigado)

"eo.wiktionary.org/wiki/Modulo:mgrup" <!--2025-Oct-08-->

Purpose: checks categories related to a word or root and brews
         either links to those categories or directly lists
         of entries limited in size

Utilo: inspektas kategoriojn rilatajn al vorto aux radiko kaj kreas
       aux ligilojn al tiaj kategorioj aux rekte ...

Used by templates / Uzata far sxablonoj / Digunakan oleh templat:
* vfvg (EO)

Required submodules / Bezonataj submoduloj /
Submodul yang diperlukan / Behoevda submoduler:
* none

Incoming: * one obligatory anonymous parameter
            * langcode
          * 0...7 optional anonymous additional parameters
            - each holding a morpheme type letter, optionally followed
              by dash "-" and adjustment code, or colon ":" and explicit
              morpheme, both optionally followed by percent "%"
              and script code
              * adjustment codes:
                - "1" drop last letter
                - "2" lowercase begin
                - "3" lowercase begin and drop last letter
                - "4" uppercase begin
                - "5" uppercase begin and drop last letter
              * explicit morpheme (length 1...40 octet:s)
              * default is "M" and "W" (becomes internally M W Y)
              * multiple morpheme types must be in this order: C I M N P U W
              * examples of multiple mortyps with langcode attached:
                - "eo|N-1|W"
                - "eo|N:viper|N:vipur|W:viper|W:vipur"
                - "ja|N%K"
          * 2 optional hidden named parameters
            * "pagenameoverridetestonly="
            * "detrc="

Returned: * one string that is inherently "HTML-block-type" and can span
            substantial (but protected from insane) height

Maximally 7 morphemes can be specified, but type "W" later counts double
(can be both group of words and sentence, both will be showed), and the
"post-expand" limit is 9, thus for example if 7 morphemes are given then
maximally 2 of them may be of type "W".

Possible category syntaxes:
* a) M         "Vorto -id- enhavanta morfemon M (api)"        (api, perapian)
* b) M         "Vorto -en- enhavanta morfemon M (sun)"    (sun, Sun, sundown)
* c) C I N P U "Vorto -id- enhavanta morfemon N (endap)"        (pengendapan)
* d) N !!!     "Vorto -eo- enhavanta morfemon N (kapt)"        (kapti, kapto)
* e) W         "Vortgrupo -id- enhavanta (api)"     (kereta api, senjata api)
* f) W         "Vortgrupo -id- enhavanta (kereta api)"   (kereta api,
                                                          stasiun kereta api)
* g) Y         "Frazo -id- enhavanta vorton (api)"       (Ada asap, ada api.)
There is a problem with self-categorization and risk of showing a useless
box containing nothing but a self-link. It is the lemma pagename that can
cause problems, not necessarily the root used in the category name (in
the case "d)" they are different). We cannot drop (in other module) the
self-categorization since as exemplified here they both are crucial and main
pages of the category, see cases "b)" (standalone) and "d)" (non-standalone).
Still we do not want a box with just one word identical to the pagename
(that is "kapti" or "kapto", not "kapt").
In the 7 above cases following applies:
* a) M         -> self-categorization occurs
* b) M         -> self-categorization occurs
* c) C I N P U -> mutually exclusive groups, no self-categorization
                  (lemma page "endap" exists but is NOT in "Vorto -id-
                  enhavanta morfemon N (endap)", lemma page "pengendapan"
                  exists and is in "Vorto -id- enhavanta morfemon N (endap)",
                  but doesn't show words based on the root "endap")
* d) N !!!     -> self-categorization occurs
                  (lemma page "kapt" doesn't exist, but both "kapti" and
                  "kapto" are in "Vorto -eo- enhavanta morfemon N (kapt)"
                  and both show words based on the root "kapt")
* e) W         -> mutually exclusive groups, no self-categorization
                  (since "api" is NOT a word group)
* f) W         -> self-categorization occurs
* g) Y         -> mutually exclusive groups, no self-categorization
The problem occurs in 4 of 7 cases namely "a)" "b)" "d)" "f)". If we find out
that the lemma page (read out via "mw.title.getCurrentTitle().text") is inside
the cat constructed (via mw.title.new + ".categories"), then we increase the
number of members in the cat required for showing the box from ONE to TWO.

Thus the box is showed only if the cat page is created, and contains the
minimal number of members.

If it contains the minimal number of members but is not created, then we
show a "preload" red link to brew the cat.

]===]

local exporttable = {}

require('strict')

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

-- uncommentable EO vs ID constant strings (core site-related features)  !!!FIXME!!! peek those and "myparentoverridetestonly="

      local strpiklangcode = "eo"          -- EO (privileged site language)
        -- local strpiklangcode = "id"        -- ID (privileged site language)
      local strpikkatns = "Kategorio"      -- EO
        -- local strpikkatns = "Kategori"     -- ID
      local strmparent = 'SXablono:vfvg'   -- EO
        -- local strmparent = 'Templat:vfvg'  -- ID

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

  -- length of the list is NOT stored anywhere

  -- "en.wikipedia.org/wiki/Spurious_languages"
  -- "iso639-3.sil.org/code/art" only valid in ISO 639-2
  -- "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

-- constant table (surrogate transcoding table, only needed for EO)

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

-- constant strings (error circumfixes)

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

  -- constant strings (HTML)

  -- note that '<categorytree>' generates "strip markers" only

local constrdivxx = '<div style="width:92%; margin-top:0.15em; margin-bottom:0.15em; margin-left:4%; border:1px solid; background-color:#'
local constrdivbg = 'E8E8FF; padding:0.15em; text-align:center;">'
local constrdivbk = 'E8FFE8; padding:0.15em; text-align:center;">'
local constrdiven = '</div>'
local constrcatbk = '<categorytree mode="pages" hideroot="on" style="column-count:2; background-color:#E8FFE8; font-size:90%; text-align:center;">'
local constrcaten = '</categorytree>'

-- uncommentable EO vs ID strings

      local constrbuggerall = '(neniuj)'                     -- EO
        -- local constrbuggerall = '(tidak ada)'                -- ID
      local constrmissingcat = 'Mankanta kategorio por'      -- EO
        -- local constrmissingcat = 'Belum ada kategori untuk'  -- ID

-- uncommentable EO vs ID constant table (error messages)

-- * #E02...#E98, holes permitted, #E00...#E12 globally reserved
-- * note that #E00 #E01 #E99 are NOT supposed to be included here

local contaberaroj = {}

  contaberaroj[ 2] = 'Erara uzo de %@, legu gxian dokumentajxon'           -- EO #E02
  -- contaberaroj[ 2] = 'Penggunaan salah %@, bacalah dokumentasinya'         -- ID #E02
  contaberaroj[11] = 'Evidente nevalida lingvokodo en %@'                  -- EO #E11
  -- contaberaroj[11] = 'Kode bahasa jelas-jelas salah dalam %@'              -- ID #E11
  contaberaroj[15] = 'Erara uzo de %@ pro nedevigaj aldonaj parametroj'    -- EO #E15
  -- contaberaroj[15] = 'Penggunaan salah %@ oleh parameter opsional tambah'  -- ID #E15

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

  local contabparam = {}
  contabparam[0] = 1   -- minimal number of anon parameters
  contabparam[1] = 8   -- maximal number of anon parameters (1 + 7)
  contabparam[2] = 160 -- maximal length of single para (min is hardcoded 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"

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

---- VAR:S ----

local qbooguard = false  -- only for the guard test, pass to other var ASAP
local qboodetrc = true   -- from "detrc=true" but default is "true" !!!
local qstrtrace = '<br>' -- for main & sub:s, debug report request by "detrc="

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

qbooguard = ((type(strpiklangcode)~='string') or (type(strpikkatns)~='string') or (type(strmparent)~='string'))

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

-- Enhance upvalue "qstrtrace" with fixed text.

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

-- no size limit

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

-- uses upvalue "qboodetrc"

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

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

-- Local function LFDMINISANI

-- Input  : * strdangerous -- must be type "string", empty legal
--          * numlimitdivthree

-- Output : * strsanitized -- can happen to be quasi-empty with <<"">>

-- To be called from "lfdshowvcore" <- "prdshowvar" only.

-- * we absolutely must disallow: cross "#" 35 | apo "'" 39 |
--   star "*" 42 | dash 45 | colon 58 | "<" 60 | ">" 62 | "[" 91 | "]" 93
-- * spaces are showed as "{32}" if repetitive or at begin or at end

local function lfdminisani (strdangerous, numlimitdivthree)

  local strsanitized = '"' -- begin quot
  local num38len = 0
  local num38index = 1 -- ONE-based
  local num38signo = 0
  local num38prev = 0
  local boohtmlenc = false
  local boovisienc = false

  num38len = string.len (strdangerous)
  while true do
    boohtmlenc = false -- % reset on
    boovisienc = false -- % every iteration
    if (num38index>num38len) then -- ONE-based
      break -- done string char after char
    end--if
    num38signo = string.byte (strdangerous,num38index,num38index)
    if ((num38signo<43) or (num38signo==45) or (num38signo==58) or (num38signo==60) or (num38signo==62) or (num38signo==91) or (num38signo==93) or (num38signo>122)) then
      boohtmlenc = true
    end--if
    if ((num38signo<32) or (num38signo>126)) then
      boovisienc = true -- overrides "boohtmlenc"
    end--if
    if ((num38signo==32) and ((num38prev==32) or (num38index==1) or (num38index==num38len))) then
      boovisienc = true -- overrides "boohtmlenc"
    end--if
    if (boovisienc) then
      strsanitized = strsanitized .. '{' .. tostring (num38signo) .. '}'
    else
      if (boohtmlenc) then
        strsanitized = strsanitized .. '&#' .. tostring (num38signo) .. ';'
      else
        strsanitized = strsanitized .. string.char (num38signo)
      end--if
    end--if
    if ((num38len>(numlimitdivthree*3)) and (num38index==numlimitdivthree)) then
      num38index = num38len - numlimitdivthree -- jump forwards
      strsanitized = strsanitized .. '" ... "'
    else
      num38index = num38index + 1 -- ONE-based
    end--if
    num38prev = num38signo
  end--while
  strsanitized = strsanitized .. '"' -- don't forget final quot

  return strsanitized

end--function lfdminisani

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

-- Local function LFDSHOWVCORE

-- Prebrew report about content of a variable including optional full
-- listing of a table with numerical and string indexes.                        !!!FIXME!!!

-- Input  : * vardubious -- content (any type including "nil" is acceptable)
--          * str77name -- name of the variable (string)
--          * vardescri -- optional comment, default empty, begin with "@" to
--                         place it before name of the variable, else after
--          * vartablim -- optional limit, default ZERO -> no listing, max 20

-- Depends on procedures :
-- [D] lfdminisani

local function lfdshowvcore (vardubious, str77name, vardescri, vartablim)

  local taballkeystring = {}
  local strtype = ''
  local strreport = ''
  local numindax = 0
  local numlencx = 0
  local numkeynumber = 0
  local numkeystring = 0
  local numkeycetera = 0
  local numkey77min = 999999
  local numkey77max = -999999
  local boobe77fore = false

  if (type(str77name)~='string') then
    str77name = '??' -- bite the bullet
  else
    str77name = '"' .. str77name .. '"'
  end--if

  if (type(vardescri)~='string') then
    vardescri = '' -- omit comment
  end--if
  if (string.len(vardescri)>=2) then
    boobe77fore = (string.byte(vardescri,1,1)==64) -- prefix "@"
    if (boobe77fore) then
      vardescri = string.sub(vardescri,2,-1) -- CANNOT become empty
    end--if
  end--if

  if (type(vartablim)~='number') then
    vartablim = 0 -- deactivate listing of a table
  end--if

  if ((vardescri~='') and (not boobe77fore)) then
    str77name = str77name .. ' (' .. vardescri .. ')' -- now a combo
  end--if

  strtype = type(vardubious)

  if (strtype=='table') then

    for k,v in pairs(vardubious) do
      if (type(k)=='number') then
        numkey77min = math.min (numkey77min,k)
        numkey77max = math.max (numkey77max,k)
        numkeynumber = numkeynumber + 1
      else
        if (type(k)=='string') then
          taballkeystring [numkeystring] = k
          numkeystring = numkeystring + 1
        else
          numkeycetera = numkeycetera + 1
        end--if
      end--if
    end--for

    strreport = 'Table ' .. str77name
    if ((numkeynumber==0) and (numkeystring==0) and (numkeycetera==0)) then
      strreport = strreport .. ' is empty'
    else
      strreport = strreport .. ' contains '
      if (numkeynumber==0) then
        strreport = strreport .. 'NO numeric keys'
      end--if
      if (numkeynumber==1) then
        strreport = strreport .. 'a single numeric key equal ' .. tostring (numkey77min)
      end--if
      if (numkeynumber>=2) then
        strreport = strreport .. tostring (numkeynumber) .. ' numeric keys ranging from ' .. tostring (numkey77min) .. ' to ' .. tostring (numkey77max)
      end--if
      strreport = strreport .. ' and ' .. tostring (numkeystring) .. ' string keys and ' .. tostring (numkeycetera) .. ' other keys'
    end--if

    if ((numkeynumber~=0) and (vartablim~=0)) then -- !!!FIXME!!!
      strreport = strreport .. ' ### content num keys :'
      numindax = numkey77min
        while true do
          if ((numindax>vartablim) or (numindax>numkey77max)) then
            break -- done table
          end--if
          strreport = strreport .. ' ' .. tostring(numindax) .. ' -> ' .. lfdminisani(tostring(vardubious[numindax]),30)
          numindax = numindax + 1
        end--while
    end--if

    if ((numkeystring~=0) and (vartablim~=0)) then -- !!!FIXME!!!
      strreport = strreport .. ' ### content string keys :'
    end--if

  else

      strreport = 'Variable ' .. str77name .. ' has type "' .. strtype .. '"'
      if (strtype=='string') then
        numlencx = string.len (vardubious)
        strreport = strreport .. ' and length ' .. tostring (numlencx)
        if (numlencx~=0) then
          strreport = strreport .. ' and content ' .. lfdminisani (vardubious,30)
        end--if
      else
        if (strtype~='nil') then
          strreport = strreport .. ' and content "' .. tostring (vardubious) .. '"'
        end--if
      end--if (strtype=='string') else

  end--if (strtype=='table') else

  if ((vardescri~='') and boobe77fore) then
    strreport = vardescri .. ' : ' .. strreport -- very last step
  end--if

  return strreport

end--function lfdshowvcore

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

-- Enhance upvalue "qstrtrace" with report about content of a
-- variable including optional full listing of a table with numerical
-- and string indexes.                                                          !!!FIXME!!!

-- Depends on procedures :
-- [D] lfdminisani lfdshowvcore

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

-- uses upvalue "qboodetrc"

local function prdshowvar (varduubious, strnaame, vardeskkri, vartabljjm)

  if (qboodetrc) then
    qstrtrace = qstrtrace .. lfdshowvcore (varduubious, strnaame, vardeskkri, vartabljjm) .. '.<br>' -- dot added !!!
  end--if

end--function prdshowvar

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

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

-- Find out whether single bit selected by ZERO-based index is "1" / "true".

-- Result has type "boolean".

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

local function mathbittest (numincoming, numbitindex)
  local boores = false
  while true do
    if ((numbitindex==0) or (numincoming==0)) then
      break -- we have either reached our bit or run out of bits
    end--if
    numincoming = mathdiv(numincoming,2) -- shift right
    numbitindex = numbitindex - 1 -- count down to ZERO
  end--while
  boores = (mathmod(numincoming,2)==1) -- pick bit
  return boores
end--function mathbittest

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

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

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

-- Replace single octet in a string.

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

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

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

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

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

-- ******************************************
-- *    NUMBER CONVERSION PROCEDURES [N]    * --------------------------  !!!FIXME!!! must precede [G]
-- ******************************************

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

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

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

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

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

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

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

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

local function lfulnutf8char (numbgoctet)
  local numlen1234x = 0
    if (numbgoctet<128) then
      numlen1234x = 1
    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 prucasegene (strinco7cs, booup7cas, boodo7all)

-- Adjust (generous) case of a single letter or longer string within
-- defined charset (ASCII + selectable bigger subset of UTF8). (this is GENE)

-- Input  : * strinco7cs : single unicode letter (1 or 2 octet:s) or
--                         longer string
--          * booup7cas  : for desired output uppercase "true" and for
--                         lowercase "false"
--          * boodo7all  : "true" to adjust all letters, "false"
--                         only beginning letter

-- Output : * strinco7cs

-- Depends on procedures : (this is GENE)
-- [U] lfulnutf8char
-- [G] lfgpokestring lfgtestuc lfgtestlc
-- [E] mathdiv mathmod mathbittest

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

-- * lowercase is usually above uppercase, but not always, letters can be
--   only misaligned (UC even vs UC odd), and rarely even swapped (French "Y")
-- * case delta can be 1 or $20 or $50 other
-- * case pair distance can span $40-boundary or even $0100-boundary
-- * in the ASCII range lowercase is $20 above uppercase, b5 reveals
--   the case (1 is lower)
-- * the same is valid in $C3-block
-- * this is NOT valid in $C4-$C5-block, lowercase is usually 1 above
--   uppercase, but nothing reveals the case reliably

-- ## $C2-block $0080 $C2,$80 ... $00BF $C2,$BF no letters (OTOH NBSP mm)

-- ## $C3-block $00C0 $C3,$80 ... $00FF $C3,$BF (SV mm) delta $20 UC-LC-UC-LC
-- upper $00C0 $C3,$80 ... $00DF $C3,$9F
-- lower $00E0 $C3,$A0 ... $00FF $C3,$BF
-- AA AE EE NN OE UE mm
-- $D7 $DF $F7 excluded (not letters)
-- $FF excluded (here LC, UC is $0178)

-- ## $C4-$C5-block $0100 $C4,$80 ... $017F $C5,$BF (EO mm)
-- delta 1 and UC even, but messy with many exceptions
-- EO $0108 ... $016D case delta 1
-- for example SX upper $015C $C5,$9C -- lower $015D $C5,$9D
-- $0138 $0149 $017F excluded (not letters)
-- $0178 excluded (here UC, LC is $FF)
-- $0100 ... $0137 UC even
-- $0139 ... $0148 misaligned (UC odd) note that case delta is NOT reversed
-- $014A ... $0177 UC even again
-- $0179 ... $017E misaligned (UC odd) note that case delta is NOT reversed

-- ## $CC-$CF-block $0300 $CC,$80 ... $03FF $CF,$BF (EL mm) delta $20
-- EL $0370 ... $03FF (officially)
-- strict EL base range $0391 ... $03C9 case delta $20
-- $0391 $CE,$91 ... $03AB $CE,$AB upper
-- $03B1 $CE,$B1 ... $03CB $CD,$8B lower
-- for example "omega" upper $03A9 $CE,$A9 -- lower $03C9 $CF,$89

-- Variables "numdel7abs" and "numdel7ta" must be at least 16-bit to avoid
-- misevaluation or wrong wrapping when fitting into the range 128...191,
-- even if no deltas exceeding +-127 are supported (there are very few pairs
-- of char:s exceeding this). Also both can be declared unsigned since only
-- addition and subtraction are performed on them.

-- 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 numlong7den = 0 -- actual length of input string
  local numokt7index = 0
  local numlong7bor = 0 -- expected length of single char

  local numdel7abs = 0 -- at least 16-bit, absolute posi delta
  local numdel7ta = 0 -- quasi-signed at least 16-bit, can be negative

  local numdel7car = 0 -- quasi-signed 8-bit, can be negative

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

  local boowan7tlowr = false
  local boois7uppr = false
  local boois7lowr = false

  local boomy7bit0x = false -- single relevant bits picked -- b0
  local boomy7bit5x = false -- single relevant bits picked -- b5
  local boopen7din = false -- only fake loop

  local boodo7adj = true -- preASSume innocence -- continue changing
  local boobotch7d = false -- preASSume innocence -- NOT yet botched

  local booc3block = false -- $C3 only $00C0...$00FF SV mm delta 32
  local booc4c5blk = false -- $C4 $C5  $0100...$017F EO mm delta 1
  local boocccfblk = false -- $CC $CF  $0300...$03FF EL mm delta 32
  local bood0d3blk = false -- $D0 $D3  $0400...$04FF RU mm delta 32 80

  booup7cas = (booup7cas==true)
  boodo7all = (boodo7all==true)
  boowan7tlowr = (not booup7cas)
  numlong7den = string.len (strinco7cs)

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

    if (numokt7index>=numlong7den) then
      break -- done complete string
    end--if
    if ((not boodo7all) and (numokt7index~=0)) then -- loop can skip index ONE
      boodo7adj = false
    end--if
    boois7uppr  = false -- preASSume on every iteration
    boois7lowr  = false -- preASSume on every iteration
    numdel7ta   = 0 -- preASSume on every iteration
    numlong7bor = 1 -- preASSume on every iteration

    while true do -- fake loop (this is GENE)

      numcha7r = string.byte (strinco7cs,(numokt7index+1),(numokt7index+1))
      if (boobotch7d) then
        numdel7ta = 90 - numcha7r -- "Z" -- delta must be non-ZERO to write
        break -- fill with "Z" char:s
      end--if
      if (not boodo7adj) then
        break -- copy octet after octet
      end--if
      numlong7bor = lfulnutf8char(numcha7r)
      if ((numlong7bor==0) or ((numokt7index+numlong7bor)>numlong7den)) then
        numlong7bor = 1 -- reassign to ONE !!!
        numdel7ta = 90 - numcha7r -- "Z" -- delta must be non-ZERO to write
        boobotch7d = true
        break -- truncated char or broken stream
      end--if
      if (numlong7bor>=3) then
        break -- copy UTF8 char, no chance for adjustment
      end--if

      if (numlong7bor==1) then
        boois7uppr = lfgtestuc(numcha7r)
        boois7lowr = lfgtestlc(numcha7r)
        if (boois7uppr and boowan7tlowr) then
          numdel7ta = 32 -- ASCII UPPER->lower
        end--if
        if (boois7lowr and booup7cas) then
          numdel7ta = -32 -- ASCII lower->UPPER
        end--if
        break -- success with ASCII and one char almost done
      end--if

      booc3block = (numcha7r==195) -- case delta is 32
      booc4c5blk = ((numcha7r==196) or (numcha7r==197)) -- case delta is 1
      boocccfblk = ((numcha7r>=204) and (numcha7r<=207)) -- case delta is 32
      bood0d3blk = ((numcha7r>=208) and (numcha7r<=211)) -- case delta is 32 80 1

      numcha7s = string.byte (strinco7cs,(numokt7index+2),(numokt7index+2)) -- only $80 to $BF
      numcxa7rel = (mathmod(numcha7r,4)*64) + (numcha7s-128) -- 4 times 64
      boomy7bit0x = ((mathmod(numcxa7rel,2))==1)
      boomy7bit5x = mathbittest(numcxa7rel,5)

    if (booc3block) then
      boopen7din = true -- pending flag
      if ((numcxa7rel==215) or (numcxa7rel==223) or (numcxa7rel==247)) then
        boopen7din = false -- not a letter, we are done
      end--if
      if (numcxa7rel==255) then
        boopen7din = false -- special LC silly "Y" with horizontal doubledot
        if (booup7cas) then
          numdel7ta = 121 -- lower->UPPER (distant and reversed order)
        end--if
      end--if
      if (boopen7din) then
        boois7lowr = boomy7bit5x -- mostly regular block, look at b5
        boois7uppr = not boois7lowr
        if (boois7uppr and boowan7tlowr) then
          numdel7ta = 32 -- UPPER->lower
        end--if
        if (boois7lowr and booup7cas) then
          numdel7ta = -32 -- lower->UPPER
        end--if
      end--if (boopen7din) then
      break -- to join mark
    end--if (booc3block) then

    if (booc4c5blk) then
      boopen7din = true -- pending flag
      if ((numcxa7rel==56) or (numcxa7rel==73) or (numcxa7rel==127)) then
        boopen7din = false -- not a letter, we are done
      end--if
      if (numcxa7rel==120) then
        boopen7din = false -- special UC silly "Y" with horizontal doubledot
        if (boowan7tlowr) then
          numdel7ta = -121 -- UPPER->lower (distant and reversed order)
        end--if
      end--if
      if (boopen7din) then
        if (((numcxa7rel>=57) and (numcxa7rel<=73)) or (numcxa7rel>=121)) then
          boois7lowr = not boomy7bit0x -- UC odd (misaligned)
        else
          boois7lowr = boomy7bit0x -- UC even (ordinary align)
        end--if
        boois7uppr = not boois7lowr
        if (boois7uppr and boowan7tlowr) then
          numdel7ta = 1 -- UPPER->lower
        end--if
        if (boois7lowr and booup7cas) then
          numdel7ta = -1 -- lower->UPPER
        end--if
      end--if (boopen7din) then
      break -- to join mark
    end--if (booc4c5blk) then

    if (boocccfblk) then
      boois7uppr = ((numcxa7rel>=145) and (numcxa7rel<=171))
      boois7lowr = ((numcxa7rel>=177) and (numcxa7rel<=203))
      if (boois7uppr and boowan7tlowr) then
        numdel7ta = 32 -- UPPER->lower
      end--if
      if (boois7lowr and booup7cas) then
        numdel7ta = -32 -- lower->UPPER
      end--if
      break -- to join mark
    end--if (boocccfblk) then

    if (bood0d3blk) then
      if (numcxa7rel<=95) then -- messy layout but no exceptions
        boois7lowr = (numcxa7rel>=48) -- delta $20 or $50
        boois7uppr = not boois7lowr
        numdel7abs = 32 -- $20
        if ((numcxa7rel<=15) or (numcxa7rel>=80)) then
          numdel7abs = 80 -- $50
        end--if
      end--if
      if ((numcxa7rel>=96) and (numcxa7rel<=129)) then -- no exceptions here
        boois7lowr = boomy7bit0x -- UC even (ordinary align)
        boois7uppr = not boois7lowr
        numdel7abs = 1
      end--if
      if (numcxa7rel>=138) then -- some misaligns here  !!!FIXME!!!
        boois7lowr = boomy7bit0x -- UC even (ordinary align)
        boois7uppr = not boois7lowr
        numdel7abs = 1
      end--if
      if (boois7uppr and boowan7tlowr) then
        numdel7ta = numdel7abs -- UPPER->lower
      end--if
      if (boois7lowr and booup7cas) then
        numdel7ta = -numdel7abs -- lower->UPPER
      end--if
      break -- to join mark
    end--if (bood0d3blk) then

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

    if ((numlong7bor==1) and (numdel7ta~=0)) then -- no risk of carry here
      strinco7cs = lfgpokestring (strinco7cs,numokt7index,(numcha7r+numdel7ta))
    end--if
    if ((numlong7bor==2) and (numdel7ta~=0)) then -- no risk of carry here
      numdel7car = 0
      while true do -- inner genuine loop
        if ((numcha7s+numdel7ta)<192) then
          break
        end--if
        numdel7ta = numdel7ta - 64 -- get it down into range 128...191
        numdel7car = numdel7car + 1 -- BIG ENDIAN 6 bits with carry
      end--while
      while true do -- inner genuine loop
        if ((numcha7s+numdel7ta)>127) then
          break
        end--if
        numdel7ta = numdel7ta + 64 -- get it up into range 128...191
        numdel7car = numdel7car - 1 -- BIG ENDIAN 6 bits with carry
      end--while
      if (numdel7car~=0) then -- in-place change only if needed
        strinco7cs = lfgpokestring (strinco7cs,numokt7index,(numcha7r+numdel7car))
      end--if
      if (numdel7ta~=0) then -- in-place change only if needed
        strinco7cs = lfgpokestring (strinco7cs,(numokt7index+1),(numcha7s+numdel7ta))
      end--if
    end--if
    numokt7index = numokt7index + numlong7bor -- advance in incoming string

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

  return strinco7cs

end--function prucasegene

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

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

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

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

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

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

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

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

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

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

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

  while true do -- fake (outer) loop

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

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

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

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

  return booisvaladv

end--function lfivalidatelnkoadv

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

local function pripl2altwre (strbeforfill, numaskikodo, varsupstitu)

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

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

-- Output : * strafterfill

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

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

  numlenbigtext = string.len (strbeforfill)

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

return strafterfill

end--function pripl2altwre

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

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

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

-- Output : * strutf8eo -- UTF8 string

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

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

local function prikodeosg (streosurr)

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

  numeoinplen = string.len(streosurr)

  while true do

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

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

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

  end--while

  return strutf8eo

end--function prikodeosg

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

local function lficolonindent (strkarhu)
  return '<dl><dd>' .. strkarhu .. '</dd></dl>'
end--function lficolonindent

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

local function prhrecusurrstrtab (varinkommen, strlingkod, bookeys)

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

-- Input  : * varinkommen -- type "string" or "table"
--          * strlingkod -- langcode "eo" or NOPE "sv" to transcode
--                          surrogates, anything else results in just
--                          copying the table
--          * bookeys -- transcode keys too (preferably either "true"
--                       or type "nil")

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

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

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

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

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

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

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

return varutmatning

end--function prhrecusurrstrtab

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

local function prhconstructerar (numerar3code, boopeek3it)

-- Construct partial error message maybe peeking description.

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

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

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

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

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

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

return stryt3sux

end--function prhconstructerar

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

local function prhbrewerr5hupa (numeror5code, strparent5nm)

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

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

-- Output : * stryt5sux -- message with HTML

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

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

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

local stryt5sux = ''

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

return stryt5sux

end--function prhbrewerr5hupa

-- **********************************************
-- *    MEDIAWIKI INTERACTION PROCEDURES [W]    * ----------------------
-- **********************************************

-- Simple check whether a wiki page exists. The caller must use "pcall". Here
-- we can crash on the spot if we violate the limit of 500 expensive requests.

-- Input  : * strpgnamsi   -- fullpagename (default NS 0, not template NS 10)

-- Output : * boomemangada -- "true" on success

local function lfwifexistsim (strpgnamsi)
  local boomemangada = false
  local metaa = 0
  metaa = mw.title.new (strpgnamsi) -- 1 param
  boomemangada = metaa.exists -- expensive here
  return boomemangada
end--function lfwifexistsim

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

-- Local function LFWISINCAT

-- mw.title.new + ".categories" gives a table with list of cat:s (without
-- ns prefix) a page is inserted in at indexes [1] [2] ...

local function lfwisincat (strfullpagename, strkattname)
  local varmeta = 0
  local varte53mp = 0
  local tabcats = {}
  local num53gindex = 1
  local booitisin = false
  varmeta = mw.title.new ( strfullpagename ) -- 1 param
  tabcats = varmeta.categories -- expensive, type "nil" if page non-existent
  if (type(tabcats)=='table') then
    while true do
      varte53mp = tabcats[num53gindex]
      if (type(varte53mp)~='string') then
        break
      end--if
      if (varte53mp==strkattname) then
        booitisin = true
        break
      end--if
      num53gindex = num53gindex + 1
    end--while
  end--if (type(tabcats)=='table') then
  return booitisin
end--function lfwisincat

-- ***********************
-- *    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 tabtriple   = {} -- 0...8 and 10...18 and 20...28

  -- general str

  local strpagenam  = '' -- "{{PAGENAME}}" o "pagenameoverridetestonly"
  local strkodbah   = '' -- obligatory param
  local strmurf     = ''
  local strtymp     = '' -- temp

  local strviserr   = '' -- visible error
  local strvisgud   = '' -- visible good output
  local strret      = '' -- final result string

  -- general num

  local numerr    = 0 -- 1 inter 2 param 4 evidente 5 additional params
  local numpindex = 0 -- number of anon params

  local numtamp   = 0
  local numtump   = 0
  local numoct    = 0
  local numodt    = 0

  local numlong   = 0
  local numprev   = 0
  local nummrtyp  = 0 -- mortyp
  local numskrip  = 0 -- script code
  local numspesl  = 0 -- adjustment code ZERO or ONE or 49...53 ie "1"..."5"

  -- general boo

  local boofake = false -- if no optional params then we must fake them

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

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

  -- later reporting of #E01 may NOT depend on uncommentable strings

  lfdtracemsg ('This is "Module:mgrup", requested "detrc" report.')

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

  ---- PROCESS MESSAGES ----

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

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

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

  arxsomons = arxframent.args -- "args" from our own "frame"
  if (type(arxsomons)~="table") then
    arxsomons = {} -- guard against indexing error
    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 2 HIDDEN NAMED PARAMS INTO 1 STRING AND 1 BOOLEAN ----

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

  -- "detrc=" and "nocat=" must be seized independently on "numerr"
  -- even if we already suck, but type "table" must be ensured !!!

  strpagenam = ''
  if (numerr==0) then
    vartymp = arxsomons['pagenameoverridetestonly']
    if (type(vartymp)=='string') then
      numtamp = string.len(vartymp)
      if ((numtamp>=1) and (numtamp<=120)) then
        strpagenam = vartymp -- empty is not legal
      else
        numerr = 1 -- #E01 internal
      end--if
    end--if
  end--if

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

  prdshowvar (numerr,'numerr','done with hidden parameters')

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

  -- later reporting of #E01 may NOT depend on uncommentable strings

  -- must be 1...120 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
      numtamp = string.len(vartymp)
      if ((numtamp>=1) and (numtamp<=120)) then  -- !!!FIXME!!! use prgstringrange
        strpagenam = vartymp -- pagename here (empty is NOT legal)
      else
        numerr = 1 -- #E01 internal
      end--if
    end--if
  end--if

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

  prdshowvar (strpagenam,'strpagenam','@Pagename finally seized')
  prdshowvar (numerr,'numerr')

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

  -- reporting of this #E01 MUST NOT depend on "strmparent" "contaberaroj"

  if (numerr==1) then
    strtymp = '#E01 Internal error in "Module:mgrup".'
    strviserr = contabfel.hubg .. strtymp .. contabfel.huen
  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 160)

  if (numerr==0) then
    numpindex = 0 -- ZERO-based
    numtamp = contabparam[1] -- maximal number of params
    while true do
      vartymp = arxsomons [numpindex+1] -- can be "nil"
      if ((type(vartymp)~='string') or (numpindex>numtamp)) then
        break -- good or bad
      end--if
      numlong = string.len (vartymp)
      if ((numlong==0) or (numlong>contabparam[2])) then
        numerr = 2 -- #E02 param/RTFD
        break -- only bad here
      end--if
      numpindex = numpindex + 1 -- on exit has number of valid parameters
    end--while
    if ((numpindex<contabparam[0]) or (numpindex>numtamp)) then
      numerr = 2 -- #E02 param/RTFD
    end--if
  end--if

  ---- PROCESS 1 ANONYMOUS PARAM INTO 1 STRING ----

  -- now var "numpindex" sudah contains number of prevalidated
  -- params (incl langcode) and is equal 1 ... 8

  -- this depends indirectly on const table "contabisbanned" via "lfivalidatelnkoadv"

  if (numerr==0) then
    strkodbah = arxsomons[1] -- langcode (obligatory)
    if (not lfivalidatelnkoadv(strkodbah,false,false,conbookodlng,conboomiddig,false)) then
      numerr = 11 -- #E11
    end--if
  end--if

  lfdtracemsg ('Seized ONE anon param langcode')
  prdshowvar (numpindex,'numpindex')
  prdshowvar (strkodbah,'strkodbah')
  prdshowvar (numerr,'numerr')

  ---- PROCESS 0...7 ANONYMOUS PARAMS INTO TABLE ----

  -- now var "numpindex" sudah contains number of prevalidated
  -- params (incl langcode) and is equal 1 ... 8

  -- mortyp -- (1)
  -- mortyp | percent "%" | script code -- (3)
  -- mortyp | dash "-" | adjustment code 1...5 -- (3)
  -- mortyp | dash "-" | adjustment code 1...5 | percent "%" | script code -- (5)
  -- mortyp | colon ":" | explicit morpheme -- (3...42)
  -- mortyp | colon ":" | explicit morpheme | percent "%" | script code -- (5...44)

  -- percent "%" 37 -- dash "-" 45 -- colon ":" 58

  -- length of explicit morpheme 1...40 octet:s

  -- mortyp goes into "nummrtyp" (7 possible num values, must be valid)
  -- adjustment code goes into "numspesl" (ZERO (as-is) or ONE (expl) or 49...53)
  -- script goes into "numskrip" (all uppercase possible 65...90, or ZERO none)

  -- "tabtriple":
  --  0... 8 (num) mortyp:s (7 possible num values, must be valid, "nil" EOF)
  -- 10...18 (str) translated (no limit) or explicit (1...40) morphemes
  -- 20...28 (num) script codes (ZERO if none specified)
  -- cannot be empty after while-loop due to faking

  numtamp = 2 -- TWO-based index in ARX 2...8
  numtump = 0 -- ZERO-based index in "tabtriple" step 1 or 2 !!!
  numprev = 0 -- previous mortyp
  boofake = (numpindex==1)
  if (boofake) then
    numpindex = 3 -- part of faking
  end--if

  while true do
    if (numerr~=0) then
      break -- jaevlar
    end--if
    if (numtump>=9) then
      numerr = 15 -- #E15 too many (0...8, index 9 is NOT valid anymore)
      break -- done
    end--if
    if (numtamp>numpindex) then -- ZERO iterations NOT possible
      break -- done
    end--if
    if (boofake) then
      if (numtamp==2) then
        strtymp = "M" -- faked index 2
      else
        strtymp = "W" -- faked index 3
      end--if
    else
      strtymp = arxsomons[numtamp] -- guaranteed to be nonempty string
    end--if
    numtamp = numtamp + 1
    numlong = string.len (strtymp)
    if ((numlong==2) or (numlong>44)) then
      numerr = 15 -- #E15 length 2 is blatantly invalid (1 and 3 are valid)
      lfdtracemsg ('Invalid length 2 or >44')
      break
    end--if
    nummrtyp = string.byte (strtymp,1,1)
    if (nummrtyp<numprev) then
      numerr = 15 -- #E15 not ascending (equality is legal)
      lfdtracemsg ('Not ascending: ' .. tostring(nummrtyp) .. '<' .. tostring(numprev))
      break
    end--if
    numprev = nummrtyp
    if ((nummrtyp~=67) and (nummrtyp~=73) and (nummrtyp~=77) and (nummrtyp~=78) and (nummrtyp~=80) and (nummrtyp~=85) and (nummrtyp~=87)) then
      numerr = 15 -- #E15 illegal type C I M N P U W
      break
    end--if
    numspesl = 0 -- preASSume take pagename as-is
    numskrip = 0 -- preASSume none
    if (numlong>=3) then
      numoct = string.byte (strtymp,(numlong-1),(numlong-1)) -- maybe "%"
      numodt = string.byte (strtymp,(numlong  ),(numlong  )) -- maybe uc
      if ((numoct==37) and lfgtestuc(numodt)) then
        numskrip = numodt -- otherwise ZERO
        strtymp = string.sub (strtymp,1,(numlong-2)) -- saw off 2 char:s
        numlong = numlong - 2 -- at least ONE left
      end--if
    end--if
    if (numlong>=3) then -- BEWARE we could have cut above, new eval needed
      numoct = string.byte (strtymp,2,2) -- legal is "-" 45 or ":" 58 only
      if ((numoct~=45) and (numoct~=58)) then
        numerr = 15 -- #E15 invalid separator
        lfdtracemsg ('Invalid separator after mortyp char')
        break
      end--if
      if (numoct==58) then
        numspesl = 1 -- explicit morpheme (seize it later)
      else
        numspesl = string.byte (strtymp,3,3) -- number 1...5 after the dash "-"
        if ((numlong~=3) or (numspesl<49) or (numspesl>53)) then
          numerr = 15 -- #E15 only "1"..."5" legal take -- adjusted pagename
          lfdtracemsg ('Invalid length <>3(+2) with dash or invalid number after dash')
          break
        end--if
      end--if (numoct==58) else
    end--if (numlong>=3) then
    if (numspesl==1) then
      strmurf = string.sub (strtymp,3,numlong) -- explicit, at least 1 ch left
    else
      strmurf = strpagenam -- not explicit as-is or with adjustment code
    end--if
    numlong = string.len (strmurf) -- refresh !!!
    if ((numspesl==49) or (numspesl==51) or (numspesl==53)) then
      strmurf = string.sub (strmurf,1,(numlong-1)) -- saw off last lett 1 3 5
    end--if
    if ((numspesl==50) or (numspesl==51)) then
      strmurf = prucasegene (strmurf,false,false) -- lc for 2 and 3
    end--if
    if ((numspesl==52) or (numspesl==53)) then
      strmurf = prucasegene (strmurf,true,false) -- uc for 4 and 5
    end--if
    tabtriple [numtump]    = nummrtyp -- store mortyp
    tabtriple [numtump+10] = strmurf  -- store morpheme
    tabtriple [numtump+20] = numskrip -- store script
    numtump = numtump + 1
    if (nummrtyp==87) then -- "W" counts double: grupo + frazo
      tabtriple [numtump]    = 89       -- store pseudo-type "Y" frazo
      tabtriple [numtump+10] = strmurf  -- store morpheme
      tabtriple [numtump+20] = numskrip -- store script
      numtump = numtump + 1
    end--if
  end--while

  prdshowvar (tabtriple,'tabtriple','after parameters seized',29)
  prdshowvar (numerr,'numerr')

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

  -- reporting of errors #E02...#E98 depends on uncommentable
  -- stuff and on "strmparent"

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

  ---- BREW THE LIST ----

  -- Need string "strpikkatns" and it is cat prefix and excludes the
  -- colon ":" but must NOT be fed into "mw.site.stats.pagesInCategory"
  -- nor into "<categorytree>" not into "lfwisincat".

  -- "Vorto -zh- enhavanta morfemon M (#) S" ("#" is Chinese simplified lett)
  -- "Vorto -id- enhavanta morfemon M (api)"
  -- "Vortgrupo -id- enhavanta (api)"
  -- "Vortgrupo -id- enhavanta (kereta api)"
  -- "Frazo -id- enhavanta vorton (api)"

  -- * for >=19 brew     : "constrdivxx" + "constrdivbg" + link to cat with
  --                       description and number + "constrdiven"
  -- * for 1 or 2 ...    : "constrdivxx" + "constrdivbk" + description and
  --   18 brew             number + "<br>" + "constrcatbk" +
  --                       raw cat name + "constrcaten" + "constrdiven"
  -- * for < 1 or 2 brew : nothing for now

  -- if nothing else brewed after exhaustive
  -- search, then finally brew ..."(neniuj)"... via "constrbuggerall"

  -- we have a pseudo-type 89 AKA "Y" for "frazo" whereas 87 AKA
  -- "W" is restricted to "vortgrupo"

  -- check whether we are in the own cat, if YES then rise the minimum
  -- "numonetwo" from ONE to TWO

  if (numerr==0) then

    do -- scope
      local var7tmp = 0
      local str7murf = ''
      local str7kato = ''
      local str7visi = ''
      local stronebox = ''
      local num7tymp = 0 -- ZERO-based index in "tabtriple"
      local num7skri = 0
      local numpaink  = 0 -- number from "mw.site.stats.pagesInCategory"
      local numonetwo = 0 -- minimal number for showing the box at all
      local booisinowncat = false
      local boocatpageexi = false

      while true do -- genuine loop over "tabtriple"

        prdshowvar (num7tymp,'num7tymp','@Index in iteration trying to brew a box')

        var7tmp = tabtriple [num7tymp   ] -- pick mortyp with risk of type "nil"
        if (type(var7tmp)~="number") then
          lfdtracemsg ('Done due to type "nil" reached')
          break -- done
        end--if

      nummrtyp = var7tmp
      str7murf = tabtriple [num7tymp+10] -- pick morpheme
      num7skri = tabtriple [num7tymp+20] -- pick script
      num7tymp = num7tymp + 1
      prdshowvar (nummrtyp,'nummrtyp')
      prdshowvar (str7murf,'str7murf')
      prdshowvar (num7skri,'num7skri')
      str7kato = "??" -- fed into "mw.site.stats.pagesInCategory" and more
      str7visi  = "??" -- visible text
      if (nummrtyp<87) then
        str7kato = "Vorto"
        str7visi = "Vortfaradoj"
      end--if
      if (nummrtyp==87) then
        str7kato = "Vortgrupo"
        str7visi = "Vortgrupoj"
      end--if
      if (nummrtyp==89) then
        str7kato = "Frazo"
        str7visi = "Frazoj"
      end--if
      str7kato = str7kato .. ' -' .. strkodbah .. '- enhavanta'
      str7visi = str7visi  .. ' kun'
      if (nummrtyp==89) then
        str7kato = str7kato .. ' vorton' -- YES for "frazo" NO for "vortgrupo"
      end--if
      if (nummrtyp<87) then
        str7kato = str7kato .. ' morfemon ' .. string.char(nummrtyp)
        str7visi = str7visi  .. ' ' .. string.char(nummrtyp)
      end--if
      str7kato = str7kato .. ' (' .. str7murf .. ')'
      str7visi = str7visi  .. ' "' .. str7murf .. '"'
      if (num7skri~=0) then
        str7kato = str7kato .. ' ' .. string.char(num7skri)
        str7visi = str7visi  .. ' ' .. string.char(num7skri)
      end--if

      prdshowvar (str7kato,'str7kato','@Cat name brewed')
      prdshowvar (str7visi,'str7visi','@Boasting text brewed')

      booisinowncat = lfwisincat (strpagenam, str7kato)
      prdshowvar (booisinowncat,'booisinowncat','@Page is in own cat')
      boocatpageexi = lfwifexistsim (strpikkatns .. ':' .. str7kato)
      prdshowvar (boocatpageexi,'boocatpageexi','@Cat page exists')

      var7tmp = mw.site.stats.pagesInCategory(str7kato,'pages')
      prdshowvar (var7tmp,'var7tmp','@After "mw.site.stats.pagesInCategory" (negative legal)')
      numpaink = 0 -- preASSume, only ZERO or ONE or TWO or >=20 relevant
      if (type(var7tmp)=='number') then
        numpaink = var7tmp -- risk for negative values due to stupid MediaWiki
      end--if
      numonetwo = 1
      if (booisinowncat) then
        numonetwo = 2
        lfdtracemsg ('Rising minimum count from 1 to 2')
      end--if

        if (numpaink>=numonetwo) then
          lfdtracemsg ('YES count is eligible')
          str7visi = str7visi .. ' (' .. tostring (numpaink) .. ')' -- visible text
          if (boocatpageexi) then
            if (numpaink>=19) then
              str7kato = '[[:' .. strpikkatns .. ':' .. str7kato .. '|' .. str7visi .. ']]' -- !!!FIXME!!! use proc
              stronebox = constrdivxx .. constrdivbg .. str7kato .. constrdiven
            else
              str7kato = arxframent:preprocess (constrcatbk .. str7kato .. constrcaten) -- !!!FIXME!!! use tag-function
              stronebox = constrdivxx .. constrdivbk .. str7visi .. '<br>' .. str7kato .. constrdiven
            end--if
          else
            stronebox = lficolonindent (constrmissingcat .. ' <b>' .. str7visi .. '</b> -> [[:' .. strpikkatns .. ':' .. str7kato .. ']].') -- !!!FIXME!!! preload
          end--if (boocatpageexi) else
          strvisgud = strvisgud .. stronebox
        end--if (numpaink>=numonetwo) then

      end--while -- genuine loop over "tabtriple"

      if (strvisgud=='') then
        strvisgud = lficolonindent(constrbuggerall) -- nothing found
      end--if

    end--do scope

  end--if (numerr==0) then

  ---- RETURN THE STRING ----

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

end--function

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

return exporttable