MODULO
Memtesto disponeblas sur la paĝo Ŝablono:pate.
  • Ĉi tiu modulo efektivigas laboron de ŝablono {{pate}}.
  • Ĝi ne dependas de iuj moduloj aŭ ŝablonoj, sed dependas de bildo Return arrow.svg (  ).

--[===[

MODULE "PATE" (proxy for analysis of template output)

"id.wikipedia.org/wiki/Modul:Pate" <!--2024-Aug-29-->
"id.wiktionary.org/wiki/Modul:pate"
"eo.wikipedia.org/wiki/Modulo:Pate"
"eo.wiktionary.org/wiki/Modulo:pate"

Purpose: calls a template indirectly with various extra features
         in order to help debugging and documentation

Utilo: alvokas sxablonon nerekte kun diversaj aldonaj funkcioj
       por helpi al sencimigado kaj dokumentado

Manfaat: melakukan panggilan templat secara tidak langsung dengan
         bermacam-macam fungsi tambah supaya membantu penghilangan
         kekutu dan dokumentasi

Syfte: anropar en mall indirekt med diverse extra funktioner ...

Used by templates / Uzata far sxablonoj:
* only "pate" (not to be called from any other place)

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

Required images:
* "File:Return arrow.svg", Public Domain

Name: "pate" is an abbreviation of "PAnggilan TEmplat" = "template
      call", idea born 2018-Dec at ID wikipedia, alternatively
      "Proxy for Analysis of TEmplate output"

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.

Incoming: * one anonymous and obligatory parameter:
            * target, can be one of 4 types (3 detected by content, one
              activated by "nsm=2"):
              * bare name of the target template or module, no walls,
                no parameters
              * complete call to the target template or module with
                parameters, protected by rectangular brackets, at least
                one wall required, requires triplebracket workaround if
                inner {{ ... }} are present
              * more than call, or wikitext containing calls to multiple
                templates, or call to one template + something more, or
                just something else, "raw mode", protected by rectangular
                brackets, activated by "nsm=2", requires triplebracket
                workaround if inner {{ ... }} are present
              * special value
                * special value ":top" begins the HTML table (incl title row)
                * special value ":end" ends the HTML table
            * notes:
              * unless with "nsm=2" for target name the default "Template:" or
                "Templat:" prefix can be omitted, other namespace prefix is
                possible, extra colon for namespace ZERO is NOT permitted,
                use "zer=1" instead
              * calling a module is possible, use the "mod=" parameter
              * complete call must be enclosed in double rectangular brackets
                "[["..."]]" instead of traditional double curly brackets
                "{{"..."}}" used for direct calls, inner wikilinks
                with rectangular brackets permitted
              * if no wall is present then double rectangular
                brackets are prohibited, a violation like
                {{#invoke:pate|ek|[[mall-test]]|sel=111-111}} gives #E04
              * length 1...10'000 octet:s
          * 14 named and optional parameters:
            * list: tab= sel= tit= war= bor= mod= zer= nsm= tag=
                    rem= ref= reg= hlt= ncf=
            * notes:
              * some are relevant for special work ":top" too, namely
                "sel=" "tit=" "war=" "bor="
              * none are relevant for ":end"
            * "tab=" HTML table type, prohibited for special work, one
              digit, can be "0"..."5", default is "2":
              * "0" raw style, do NOT add any table elements (gives #E40
                if parameter "sel=" requests more than 1 table cell)
              * "1" brew one or several table cells with "<td>" and "</td>"
                but no "<tr>" and "</tr>" (this is rarely useful)
              * "2" brew one or several table cells with "<td>" and "</td>"
                together inside a table row delimited by "<tr>" and "</tr>"
              * "3" generate a complete horizontal table with 1+1 rows (one
                row for titles, one row for one target) and 1...6 columns
              * "4" generate a complete horizontal table with 1 row only
                (no titles, one target) and 1...6 columns
              * "5" generate a complete vertical table with 1+1 columns
                (one column for titles, one column for one target) and
                1...6 rows (this is useful for "big" target templates)
            * "sel=" control string for cells, 7 characters, 6 values, data
              types boolean tristate fourstate, for "tab=0" the default is
              "010-000" (show only parsed result), otherwise for "tab<>0"
              it is "111-110" (show all except debug), value "000-000"
              prohibited, see below under "Misc technical stuff", the values
              (left to right) correspond to:
              * X..-... # calling code, with link to the target template if
                "call"    not same page, showed even if the template doesn't
                012       exist, the link can be red, with all parameters,
                          text without link if same page, value "1" to enable,
                          "2" for small text
              * .X.-... # parsed result from the target template (called
                "parsed"  with all possible example parameters and hidden
                 01       parameters) displayed, or whining if problem (target
                          not found, returned empty (ZERO octet:s), returned
                          invalid (see below) content)
              * ..X-... # remark ie extra usage information from parameter
                "rem"     "rem=", or placehodler string "--" if not supplied,
                 01       showed even if target not found
              * ...-X.. # links to categories resulting from category
                "cat"     insertions generated by the template with sorting
                012       hints (every of them split into 2 lines, all
                          categories are counted, up to 12 listed), or
                          whining if problem, value "1" to enable, "2"
                          to omit sorting hints
              * ...-.X. # links to list of backlinks and redirects to the
                "back"    target template (showed even if template is not
                01        found, as links to these services cannot be red)
              * ...-..X # technical information for debug purposes including
                "debug"   bloat of the result brewed by the template,
                0123      findings, full raw wikitext (always unparsed, either
                          simple "hard nowiki" or coloured "hard nowiki" or
                          coloured map), fourstate ("0" omit column, "1" show
                          per simple "hard nowiki", "2" show per coloured
                          "hard nowiki", "3" show map)
            * "tit=" defines the titles for the table, 1...6 title strings
              attached together by double at-signs "@@", relevant if the
              anonymous parameter is ":top" or the "tab=" parameter is
              "3" or "5", otherwise ignored, the default or specified
              "sel=" value determines the size of the table, only selected
              columns (for "tab=3" "sel=100-003" and "tit=calling@@insanity"
              there will be 2 columns, the latter one named "insanity"
              containing the debug map), superfluous title strings are
              ignored, missing ones are replaced by default names hardcoded
              in "contabcolumns", total length 1...200, equal sign "=" and
              wall "|" are problematic, LF illegal, [[...]] illegal, <b> and
              <br> legal
            * "war=" table line color, always 6 hex digits without
              cross "#", default is "60A0A0" AKA bluegreen
            * "bor=" CSS "border:" value, legal range "05" ... "80" becomes
              "0.05em"..."0.80em", default is "20" becoming "0.20em",
              always 2 digits
            * "mod=" target is a module, value is the name of function ("1"
              prohibited), prohibited for special work, prohibited together
              with "zer=1", prohibited for "nsm=2"
            * "zer=1" allow target in ns ZERO (prefix for ns 10 will NOT
              get added, instead check whether it indeed is in ns ZERO),
              prohibited for special work, prohibited together with
              "mod=", prohibited for "nsm=2"
            * "nsm=" activate workaround for nested template calls or raw
               mode, "1" to enable triplebracket workaround, "2" for
              raw mode, prohibited for special work, see "Notes on
              protection" below
            * "tag=1" activate ne-workaround for aggressive tags and
              replace for example "<nonewiki>" <--> "<nowiki>", see
              below under "Misc technical stuff"
            * "rem=" remark ie extra usage information for the target
              template or given case, undergoes same translation as the
              call target parameter, length 1...1000, equal sign "=" and
              wall "|" are problematic, LF illegal, [[...]] legal, <b>
              and <br> legal, prohibited for special work
            * "ref=" eat away references, name of group or "1" for default one
            * "reg=" eat away references, name of group or "1" for default one
            * "hlt=1" send "trackingcategorieshiglow=true" to the target
              and activate bypassing, prohibited for special work, note that
              literal "trackingcategorieshiglow=true" sent into "pate" will
              NOT work, see below "Notes on tracking categories"
            * "ncf=1" (deprecated) send "nocat=false" to a legacy target
              template, prohibited for special work, note that literal
              "nocat=false" will NOT work, see below "Notes on category
              suppression"

Returned: * large and complicated string as documented below, may or may
            not contain part of a HMTL table or a complete such

Restrictions for incoming strings:
* risks and needs
  * LF is prohibited (LF + star "*" or LF + cross "#" would break layout,
    use <br> instead)
  * "[http" is prohibited
  * <b> and <br> are legal
* list with parameters:
  * "tit="
    * 1...6 title strings attached together by double at-signs "@@"
    * total length 1...200 (all up to 6 strings plus all double at-signs)
    * equal sign "=" and wall "|" are problematic (see below
      under "Notes on protection")
    * LF illegal
    * [[...]] illegal
    * <b> and <br> legal
    * plain cross "#" at begin is safe (such as "#T99") ie no
      wiki-like-begin-of-line-specific rules apply
  * "rem="
    * undergoes very same translation as the call target parameter
      controlled by "nsm=" and "tag=1"
    * length 1...1000
    * equal sign "=" and wall "|" are problematic (see below
      under "Notes on protection")
    * LF illegal
    * [[...]] legal
    * <b> and <br> legal
    * plain cross "#" at begin is safe (such as "#T99") ie no
      wiki-like-begin-of-line-specific rules apply

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

### Usage (user manual)

Features:
* calls a target (usually template) and separates cat insertions
  from remaining wikitext, thus performs category suppression, provided
  on one place for all templates in a wiki
* drastically simplifies tasks like template and module documentation and
  self-testing, template debugging, listing of multiple template results
  (like userboxes) in a table, provides easy linking to templates
  with parameters
* completely replaces all other legacy category suppression tricks (except
  title page rule) thus greatly simplifies template design and reduces
  risk of miscategorizations
* allows to show the generated category insertions including sorting hints
* can handle hidden parameters (special parameters that can pretend to the
  target facts that it detects and depends on, like pagename, namespace,
  protection level, preview-vs-submit, existence of a file or page,
  date, ...)
* preserves the sortorder and formatting of parameters
* works with misplaced templates ie templates in other namespaces
  such as "User:" (still namespaces 14 "Category:" as well as 6 and -2
  "File:" and "Media:" are prohibited), works even for target in ns
  ZERO (needs "zer=1")
* can usually work even with legacy templates internally still relying
  on legacy category suppression tricks
* can handle nested template calls (needs "nsm=1")
* can call a module too (so far only usable for modules callable from
  templates, ie not those callable only from other modules)
* can "fire" and analyze wikitext consisting of more than just one
  template call (needs "nsm=2")
* can handle template calls containing some aggressive tags in
  parameters (needs "tag=1")
* accepts most of same and less sane content that templates can generate,
  can handle "<pre>" and HTML comments there
* can handle references generated by the target in a decent and useful
  manner, see below "Notes on references"
* detects and reports various errors in output from templates, severe
  errors result in early rejection ie no separation, but still at least
  partial report

Limitations and caveats:
* uncommon syntax of the template call with double rectangular brackets
  (note: this is due to some rules or priciples in the MediaWiki parser
  that are old and inherent, thus this will NOT get "fixed" to "standard"
  double curly brackets, see below "Notes on protection")
* uncommon syntax of nested inner template calls using the triplebracket
  workaround {({ })} (same here, this will NOT get "fixed" to "standard"
  double curly brackets)
* uncommon syntax of some aggressive tags such as references or galleries
  using the ne-workaround (same here, this will NOT get "fixed" to any
  "standard" form)
* uncommon syntax of the hidden forwarded parameters by using "*HIDDEN*"
  instead in the equal sign "="
* nested template calls will NOT work out of the box since the innermost
  template is expanded at the earliest, but there is a solution
  (triplebracket workaround), see above as well as below "Notes on protection"
* some aggressive tags such as references or galleries passed as template
  parameters don't work out of the box, but there is a solution
  (ne-workaround), see above as well as below "Notes on protection"
* intermediate type template parameters ie holes in anonymous parameter
  chain are not supported (for example "{{mytemplate|junk|4=green}}" is bad)    !!!FIXME!!! is this true at all ??
* there is NO LIMIT for number of forwarded parameters, but there is a
  size limit max 10'000 octet:s resulting in a theoretical limit a bit
  below 5'000 anonymous parameters, note that chess needs only slightly
  more than 64 parameters for a standard chessboard
* equal sign "=" and wall "|" are prohibited (must be html-dec-encoded) in
  title strings (parameter "tit=") and remark (parameter "rem=") (limitation
  comes from the wiki parser), and several titles have to be separated by
  "@@" and must NOT be empty
* due to MediaWiki parser again, testing broken call syntax like {{doit|}}
  is a problem, written as [[doit|]] it gets victim of the "pipe trick",
  but there is a solution, see below "Notes on protection"
* may cause problems with some legacy templates (try the "ncf=1"
  ie "nocat=false" trick)
* fires one expensive request and thus must be used less than 500 times         !!!FIXME!!!
  on one page
* some aggressive tags rarely output by templates result in strip markers
  showed and detected here (but no strip markers arise if the content
  comes from a module), they make debug output less informative but parsed
  output still looks OK, see also "Notes on protection" and "Notes on
  references" below
* <syntaxhighlight lang="text"> does not work (rejected as error)               !!!FIXME!!!
* "<nowiki>" does NOT work (rejected as error), note that "<nowiki>" does       !!!FIXME!!!
  NOT work at all in output from a module thus modules must NOT emit such
  content even if "pate" is not involved, still "<nowiki>" could be useful
  in output from an old-style template, for now <nowiki> is prohibited,
  <pre> works strangely when coming from a module but we fix it
* augmented <pre> such as <pre style="overflow:auto;"> does not work, other
  augmented tags don't work either (still augmented <ref> works mostly)
* upperCASE and malformed tags such as "<NOWIKI>" or "<nowiki  >" don't work    !!!FIXME!!!
* some templates output stuff such as "[[]]" or "[[ ]]" as part of well
  useful mesages like
    rectangular brackets [[ ]] are prohibited in parameter "qq="
  that will cause an error in "pate", to fix this the target template
  should use the "FSI-workaround" like this
    rectangular brackets [&#8296;[ ]&#8296;] are prohibited in parameter "qq="

Notes on protection:
* this is about passing parameters into templates only
* problems:
  * the MediaWiki parser has an inherent and old principle "expand and
    process from inside outwards", this means that for example with
      {{umbox|text=NO WE{{br}}CAN'T}}
    the outer template "umbox" will NOT find "{{br}}" in the incoming
    parameter, instead it gets the result of expansion such as "<br>",
    "(Breton)" or similar
  * some aggressive tags in template parameters ar problematic, for
    example with
      {{umbox|text=There is a God <ref>The Bible</ref>.}}
    the template will NOT find "<ref>" in the incoming parameter, instead it
    gets a useless strip marker, and the relevant text "The Bible" is gone too
  * some char:s, most notably equal sign "=" and wall "|", are problematic
    in template calls, while particularly the equal sign is common in url:s,
    the traditional workarounds are parser functions {{=}} and {{!}}
    (note the exclam instead of wall in the latter) and html-dec-encoding
    "&#61;" and "&#124;"
* those principles are bad for "pate" and thus we must workaround them,
  otherwise the concept of "pate" will NOT work at all
* workarounds:
  * the main always active workaround is to place the forwarded template
    call into double rectangular brackets like
      [[umbox|text=NO WE CAN'T]]
    for
      {{umbox|text=NO WE CAN'T}}
    and that way "protecting" plain walls and equal signs used to delimit
    template parameters from premature effect (it does not protect
    from expansion of {{=}} and {{!}} though)
  * in some cases further layers of protection must be added:
    * for nested templates such as
        {{umbox|text=NO WE{{br}}CAN'T}}
      we must prevent premature expansion of {{br}}, the way
      to do it is called triplebracket workaround used like this
        [[umbox|text=NO WE{({br})}CAN'T]]
      and let "pate" replace triplebrackets {({...})} by ordinary {{...}}
      after they have been smuggled in behind the back of the parser,
      the "nsm=1" or "nsm=2" parameter activates this workaround
    * for some aggressive tags in parameters (pre, nowiki, ref, gallery), we
      have to use the ne-workaround and write for example <renef>...</renef>
      and let "pate" fix it back to <ref>...</ref>, this is activated by
      "tag=1" independently of "nsm="
    * when forwarding broken call syntax like {{doit|}} written as [[doit|]]
      it gets victim of the "pipe trick" highly undesirable in the context,
      again, "nsm=2" helps ("nsm=1" is insufficient), write [[{({doit|})}]]
* those workarounds are applied to the target parameter as well as to "rem="
  since it can be desirable to write things like "<gallery> can be used"
  or "bad use, <gallery> is prohibited"
* there is an alternative way to protect
    {{umbox|text=NO WE{{br}}CAN'T}}
  and
    {{umbox|text=NO WE{{=}}CAN'T}}
  and
    {{umbox|text=my <gallery>Hole.svg</gallery> hole}}
  namely using "<noinclude/>" this way:
    {{umbox|text=NO WE{<noinclude/>{br}<noinclude/>}CAN'T}}
  and
    {{umbox|text=NO WE{<noinclude/>{=}<noinclude/>}CAN'T}}
  and
    {{umbox|text=my <gal<noinclude/>lery>Hole.svg</gal<noinclude/>lery> hole}}
  with the advantage that the trick is native to MediaWiki not needing
  "nsm=" or "tag=1", and the disadvantage of very long and unreadable text
* with "pate" in forwarded parameters inside [[ ]] parser functions {{=}}
  and {{!}} DO NOT WORK as possibly desired, they give same bad result as
  raw "=" and "|", valid solutions are either "&#61;" and "&#124;", or
  modified {({=})} and {({!})} together with "nsm=1" or "nsm=2"
* with "pate" in "rem=" both parser functions {{=}} and {{!}} and
  html-dec-encoding do work as desired

Notes on category suppression:
* All active cat insertions are carefully cut out from the output coming from
  the target, whereas links to categories such as "[[:Category:Chemistry]]"
  and commented-out cat insertions are kept. This works even for malformed
  cat insertions with wrong letter case or redundant spaces, untranslated
  prefix "Category:" on non-English wikis, and cat insertions with
  sorting hints.
* The target template should NOT use any other type of category
  suppression like the infamous "CategoryHandler", namespace detection,
  "nocat=" parameter, or similar tricks. Reasonably behaving legacy
  templates have a good chance to work with "pate", though (try the
  "ncf=1" ie "nocat=false" trick).
* Do NOT submit "nocat=" or similar into this template, neither directly, nor
  indirectly ie via the [[...]]-protected parameter (ordinary or hidden via
  *HIDDEN*) intended for target template. Doing so in any of those manners
  (as long as the name is "nocat") is an error. If the "ncf=1" ie
  "nocat=false" trick is used then "nocat=false" will be sent to the
  target template.
* The target template still may avoid categorizing when finding itself
  on the title page, but even here there is a better way: send the
  transclusion through "pate" with "nsm=2", or through other module
  "Suppress Categories" limited to the core task category suppression.

Notes on tracking categories:
* A tracking category is there to collect pages suffering from a certain
  type of problem, or even technical feature.
* Tracking categories are (non-technically) separated from content
  categories of a wiki, and may be hidden, or not created ie kept red.
* Insertion into a tracking cat is done either by MediaWiki, or by
  templates and modules.
* Usually a tracking cat is populated by pages (articles, lemmas, even
  appendixes) using a temlate in a wrong way. "pate" perfectly supports
  this use, and allows to check that the tracking categorization works
  as supposed. Then templates and modules do NOT have to bother at all
  with "nocat" nor "pate".
* Rarely there is a need for a tracking cat collecting high level templates
  using a low level template in a wrong way. This is more difficult to
  accomplish, since we do NOT want that pages using the broken high level
  template "get caught" too. "pate" has a solution for this in the form
  of "hlt=1" that sends "trackingcategorieshiglow=true" to the target
  and activates bypassing. Then the low level template must help "pate"
  in following way:
  * evaluate the parameter "trackingcategorieshiglow=true"
  * if this is NOT true, do NOT emit the tracking cat insertions
  * if this applies, then do emit the tracking cat insertions, but
    embed EVERY SINGLE such (of not many anyway) in:
    * <span title="bypass-pate">
    * </span>
    for example like this:
    * <span title="bypass-pate">[[Kategori:Tracking-ubx-text-too-long]]</span>
  If "hlt=1" is used then "pate" will find those markers and bypass the
  strict cat suppression, if favor of BOTH displaying the tracking cat,
  AND letting the page get caught into it, exactly as intended. This method
  is not as trivial and straightforward as one might dream of, but it is
  still the probably easiest approach, and definitely better than the legacy
  way ie "CategoryHandler" (ns detection + caller detection + whitelists +
  blackists + greylists + ...) + "nocat" + overuse of <includeonly> +
  other obscure tricks.

Notes on namespaces:
* the "Template:" namespace is the preferred one for templates
* per MediaWiki it is possible to transclude pages in other namespaces
  including ns ZERO
* MediaWiki adds automatically the ns prefix "Template:" to template calls
  per {{...}} if no other prefix is present, for ns ZERO the title must be
  prefixed with an extra colon ":"
* "pate" supports other namespaces including ns ZERO, the extra colon
  must NOT be fed into "pate" since it is added automatically if needed
* some namespaces are prohibited by "pate", most notably "Category:" and
  "File:", they cannot be used in the anonymous and obligatory parameter,
  a violation gives #E05

Notes on references:
* templates can theoretically generate references, this is a troublesome
  habit, but there are templates around that indeed do so
* for reasons hardcoded into MediaWiki, "pate" cannot detect or capture
  accumulated references, but it can let MediaWiki dump them below the
  parsed result if manually instructed to do so
* there can be several groups of references
* failure to request MediaWiki harvest the default group results in it
  being dumped at the bottom of the screen in an uncontrolled manner
* failure to request MediaWiki harvest any additional group results in
  a red error message displayed at the bottom of the screen
* the parameter "ref=" of "pate" can be used if the template generates
  references, failure to use it results in a messy output, if it is used
  then there must NOT be any accumulated references from before, parameter
  "reg=" is for an additional group
* for reasons hardcoded into MediaWiki, passing the syntax <ref>...</ref>
  in a template parameter is a problem and causes a mess (strip markers),
  this can affect the target template or a possible inner nested template,
  the only solution is "tag=1" hiding the tag from the MediaWiki parser, see
  above "Notes on protection"

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

### Mapping of wikitext

The mapping is performed by "lfhparsemap" and is a crucial step towards
a reliable parsing. See separate document "Mapping of wikitext" for a deeper
explanation. The map essentially reveals for every position in a piece of
wikitext what type of content is located there: plain wikitext, inside
wikilink of specified depth, inside template call (not used here), inside
HTML comment, inside "nowiki", etc.

Some of such content areas are nestable:
* wikilink [[ ]]
* template call {{ }}
* seizure of template parameter {{{ }}}

Whereas others are not:
* external HTTP link [http ... ]
* nowiki
* pre
* HTML comment "<!--" ... "-->"
* ...

For the mapping the following area types are defined:
* #A00 0 ordinary plain wikitext no link
* 1...9 wikilink of given depth
* #A14 14 strip marker listed pre nowiki ref references  !!!FIXME!!! not yet
* #A15 15 strip marker other
* #A17 17 "<syntaxhighlight"
* #A22 22 pre
* #A23 23 nowiki
* #A24 24 ref
* #A27 27 HTML comment "<!--" ... "-->"
* #AFF 255 invalid

Here we fully ignore:
* template call {{ ... }}
* seizure of template parameter {{{ ... }}}
* external HTTP link [http ... ]

Nested wikilinks are inherently dubious, but MediaWiki does not discourage
them sufficiently vigorously. They appear particularly in image syntax
with depth 2, and that's the limit we tolerate for ordinary links, still
a cat insertion on a level different from ONE is unacceptable. Since we
ignore template calls and template parameters, we could happen to miscount
the levels of [[ ]], but a template generating such stuff is an error anyway.

MediaWiki will handle many types of areas and nesting in a particular
way. For example comments can get stripped off during some types of
transclusion (most notably the "frame" service "preprocess"), but kept
in other contexts (most notable dumping the content of a template
onto a wiki page by means of "subst:"). This happens irrespective
whether located inside links and cat insertions
[[Category:Chemis<!--don't-->try]] or elsewhere.

"lfhparsemap" returns a nested table containing 3 subtables:
* [0] findings ie status fields #M?? (see list below)
* [1] main map of area types
* [2] boundary map with tristate flags

We map in two different contexts:
* the incoming target parameter (also being wikitext) is
  mapped to "tabinparmap" in order to:
  * detect and reject strip markets giving #E46
  * reliably detect "nocat=" and "trackingcategorieshiglow=" prohibited there   !!!FIXME!!! not yet
  * reliably detect unbalanced [[ ... ]]                                        !!!FIXME!!! not yet
* the output from expansion (also being wikitext) is
  mapped to "tabutparmap" in order to:
  * perform category suppression
  * brew the debug map if requested

The full nested table of mapping output is handled by:
* lfhparsemap -- brew
* lfhtablewithmap -- read only
* lfysplitkat -- read only

Only the subtable [0] of mapping output is used by:
* lfhreportfindings -- read only

Note that on error the main map and the boundary map can be incomplete
ie contain type "nil" in some positions (particularly relevant for the
main map, whereas the boundary map is mostly full of "nil" anyway).

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

### Error handling and statuses

* this module CANNOT produce insertions to a tracking category
* missing target template, or existing template returning empty or garbage
  is NOT fatal, partial results (in a table if requested) will be showed
* red error-like text coming from the target template is possible
  and considered as ordinary output

Error codes in "numerr" (order: tab= sel= tit= war= bor= mod= zer=              !!!FIXME!!! move up error codes (#E00...#E12 reserved)
nsm= tag= rem= ref= reg= hlt= ncf=):
* #E01 internal
* #E02 unknown param fed in                                                     !!!FIXME!!! not detected
* #E03 obviously bad anon param (some problems land in #E04 instead)
* #E04 bad use of [[ | ]] [ ] in anon param
* #E05 bad or nonstandard target title or ns (on wikipedia this typically
       results from wrong capitalization, ie "Mall:bo" instead of "Mall:Bo")
* #E06 named parameter used together with ":end"                                !!!FIXME!!! detect all
* #E07 named parameter other than sel=, tit=,                                   !!!FIXME!!! detect all
       war=, bor= used together with ":top"
* #E08 "tab=" bad
* #E21 "sel=" bad
* #E23 "tit=" bad (contains LF, [[...]], ...)
* #E25 "war=" bad
* #E26 "bor=" bad
* #E28 "mod=" bad (equal "1", ...)
* #E29 "zer=" bad (other than "0" or "1", also for target NOT in ns ZERO)
* #E31 "nsm=" bad
* #E32 "tag=" bad
* #E34 "rem=" bad (contains LF, ...)
* #E35 "ref=" "reg=" bad (both same, "reg=" without "ref=", ...)
* #E36 "hlt=" bad
* #E37 "ncf=" bad
* #E40 "sel=" conflicts with "tab=" since no raw with multiple columns
* #E41 "mod=" and "zer=1" must NOT be used together
* #E43 "trackingcategorieshiglow=" sent into "pate"
* #E44 "nocat=" sent into "pate"
* #E46 strip marker detected in incoming target parameter
* #E48 hidden parameter contains double brackets [[ ]] {{
* #E49 *HIDDEN* without preceding wall "|"

Status codes from expand mm ie "arxframent:preprocess" mm in "numpexpasta":
* #X00 NOT attempted yet
* #X01 exists but expand NOT attempted yet
* #X70 success
* #X80 target (template or page) does NOT exist
* #X81 no string ie expand failure
* #X82 empty string from expansion by "arxframent:preprocess", note that
       a size of ONE is perfectly valid, no chance for a cat insertion,
       but consider for example the old template {{=}}

Status fields from parse&map done in sub "lfhparsemap" stored
in "tabutparmap" subtable [0]:
* [0]  #M00 mapping attempted
* [1]  #M01 unclosed inactive area (no other discovery possible)
* [2]  #M02 unclosed strip marker (impossible unless deliberately
            constructed) (no other discovery possible)
* [3]  #M03 unbalanced [[ ]] in active areas (no other discovery possible)
* [4]  #M04 strip markers (pre nowiki ref references)                           !!!FIXME!!!
* [5]  #M05 strip markers (other)
* [6]  #M06 "<syntaxhighlight" ...
* [8]  #M08 pre
* [9]  #M09 nowiki
* [10] #M10 ref
* [11] #M11 HTML comment "<!--","-->"
* [12] #M12 <includeonly>,</includeonly>,<onlyinclude>,</onlyinclude>,          !!!FIXME!!!
            <noinclude>,</noinclude> detected
* [20] #M20 max wikilink depth (counting aborts at 9->10, special value 97
            denotes an underflow error, and special value 98 denotes
            an overflow error)
* [22] #M22 treacherous space attached to inner side of double rectangular
            brackets "[[ " and " ]]", different from #S02

Do NOT use extraneous spaces in cat insertions:
[[Category:Crime]]    OK
[[Category: Crime]]   bad, accepted with complaint #S02
[[Category:  Crime]]  rejected by "lfysplitkat" with #S27
[[Category :Crime]]   bad, will fail to find the category
[[ Category:Crime]]   rejected by "lfhparsemap" with #M22
[[  Category:Crime]]  rejected by "lfhparsemap" with #M22
[[Category:Crime ]]   rejected by "lfhparsemap" with #M22

Do NOT put cat insertions on a level different from ONE:
[[Category:Crime]]                              OK
[[Category:Crime|Crime]]                        OK
[[CRIME|indeed [[Category:Crime]] yes we can]]  bad

Status codes from split ie "lfysplitkat" in
"numzsplitsta" ZERO OK vs 2...6 (non-fatal) vs 20...40 (fatal):
* #S00 OK
* #S02 single extra space attached after colon (still split success,
       different from #M22)
* #S03 empty cat hint "[[Category:|]]" or "[[Category:| ]]" (still split
       success, former fault apparently cannot even be created by
       ordinary wiki editing)
* #S20 split NOT attempted due to other #M06 #M09 #M12 or or #M22 !!!FIXME!!!
* #S21 split NOT attempted due to nesting #M01 #M02 #M03 #M20>2
* #S23 broken wikilink lacking left part "[[]]" or like "[[|crap]]"
* #S24 cat insertion on level different from ONE
* #S25 invalid title (empty "[[Category:]]" etc)
* #S27 more than one space "[[Category:  Crap]]" !!!FIXME!!!

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

### Alignment, centering, newline-sensitivity, overlong lines

The alignment is fixed for the sake of simplicity.

The whole big table is positioned left on the screen,
outside left, CSS "margin:0.6em;" (this is top).

Text is centered in all cells of the title row
"call", "parsed", "rem", "cat", "back", "debug".

Text is theoretically centered in all content cells, but this
is later workarounded in some cells:
* in "call" an inner table outside left inside left "lfipretable"
* in "parsed" an inner <div> (NOT table) outside left inside
  left hog full width "lfilefqdivqlefqtxt"
* in "cat" an inner table only if more than 2 cat:s "lficenqtabqlefqtxt"
  via "lfhtabletohtmllista" outside center inside left, other
  text above centered
* in "debug" upper text centered, findings via "lfhtabletohtmllista"
  outside center inside left, finally lower text either "hard nowiki"
  outside center inside left, or map with table outside center
  inside many small cells

In "call" we need left alignment because this is how code is always showed.

In "parsed" we need left alignment for both text and block elements because
that is the default behaviour of wiki that we mimic there. Further we need
a newline before the generated content to make sure that "#" ":" "*" and
wikitables do work as supposed, again we mimic default behaviour. This
newline does NOT consume any vertical space due to the "single LF in HTML
is ignored" rule.

In "rem" we do NOT add any newline thus no begin-of-line-specific
rules apply.

In "cat" there is NO risk of overlong lines due to underscores "_" since
we standardize the titles by means of "lfwsplit3title" which includes
replacing of underscores by spaces.

In "debug" we can get overlong lines due to underscores "_" but this doesn't
matter since overlong lines appear there anyway an we force-break them.

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

### Misc technical stuff

We do NOT use any "pairs" in this module.

Parameter "sel=" (6 + 1 digits):
* for "tab=0" the default is "010-000" (show only parsed result)
* for "tab<>0" the default is "111-110" (show all except debug)
* value "000-000" (do not show anything at all) is prohibited
* relevant also for ":top", but then the extra features activable
  by values 2 or 3 do NOT apply, thus all values are binary
* number bigger than "1" in the control string "sel=" is used (design
  decision) to hold a value instead of another named parameter if:
  * there is a low number of possible discrete values
  * the issue is specific to a column of the table
  * the issue is about display only, not about operation
* following display details are "built-in" into "sel=":
  * column "call" : small text size (boolean -> tristate)
  * column "cat" : omit sorting hints (boolean -> tristate)
  * column "debug" : type of display (black text, coloured
    text, map) (tristate -> fourstate)
* there is a conflict of interest about the column "back", it is
  useful when exposing many calls to different templates, but undesirable
  (alternatively use some "rowspan" ...) for many calls to same template
  with varying parameters

Strip markers:
* tags and parser functions vulnerable to strip markers:
  * <pre> and <nowiki> #A14 #M04
  * <ref> and <references> #A14 #M04
  * both <categorytree> and {{#categorytree}} #A15 #M05
  * <templatestyles> #A15 #M05
  * <gallery> #A15 #M05
  * <math> #A15 #M05
  * "<syntaxhighlight" ...
* unproblematic tags:
  * <b> <i> <s>
  * <sub> <sup>
  * <br>
* parameter "tag=1" activates workaround for some of the vulnerable ones:
  * <pre> <--> <pnere>
  * <nowiki> <--> <nonewiki> observed in template parameters
  * <ref> <--> <renef> observed in template parameters (works
    even for "<ref group=" and "<ref name=")
  * <gallery> <--> <gallenery> observed in template parameters where
    a single image is expected (probably not sane)

Storage of parameter chains:
* Raw string such as "nope|center=YES" used in incoming anonymous
  parameter together with the name of the target, with rectangular
  brackets and adjacent whitespace later removed, equal sign is
  protected inside [[ ]] thus "{{=}}" is NOT needed.
* We do NOT use a table with parameters for a call.
* Still we must look for "trackingcategorieshiglow=" or "nocat=" and
  whine if found.
* Also we must look for "*HIDDEN*".
* Unless in the raw mode by "nsm=2", we apply "partial preservation of
  trimmed whitespace". Then [[ ]] serve as replacement of {{ }} and
  we preserve ONE SPACE or ONE LF separately on every side (inside of
  the [[ ]]), but NOT more. This is needed to preserve the arrangement
  of the call (see below).

For "nsm=2" ie "boo3nsm2m" following changes apply:
* column "call": the call is never linked
* column "back": backlinks are not showed, if the column is enabled
  (discouraged) then it will contain the standard placeholder instead
* operation:
  * [[ ]] must always be used, and do NOT serve as replacement
    of {{ }}, extra {({ })} must be used inside
  * partial preservation of trimmed whitespace does NOT apply
  * check for the existence of target is skipped, "#X80" cannot occur
  * many sanity checks skipped
  * "zer=1" does NOT work, bad old extra colon ":" must be used
  * "hlt=1" and "ncf=1" do NOT work

Hidden parameters:
* denoted per "*HIDDEN*" instead of equal sign "=" in the call string
* are available for all "nsm=0" "nsm=1" "nsm=2"
* are available also for "mod=1"
* must NOT contain wikilinks
* must NOT contain nested templates, not even with "nsm=1", still outside
  of the hidden parameter, nested template calls are permitted in same call
* hidden parameters can be several and anywhere in the chain, but
  preferably put them always together at the end
* main anon parameter is split into two strings:
  * one with "*HIDDEN*" replaced by "=" intented to be fed into
    expansion ie "arxframent:preprocess"
  * one with parameters using "*HIDDEN*" removed intended to be
    showed in the table

Handling of ns prefixes:
* the {{-}}-syntax (indirectly used here) defaults to ns 10
  ie "Template:", other prefix still possible
* parser function "arxframent:expandTemplate" (not used
  here) defaults to ns 10 too
* unless "zer=1" we have to always add the prefix for the purpose
  of linking by means of [[...]]-syntax and backlink evaluation by
  means of "Special:WhatLinksHere"
* if "zer=1" then we must add an extra colon for the {{-}}-syntax for
  both expansion and display

The "<pre>" tag is insanely useful in plain wikitext, but behaves
inconsistently when coming from a template or module. We want it
to work for via "pate" in the rare cases when it's needed, and must
then care about it by html-dec-encoding.

Both <ref> and <references/> do NOT work in output from a module. We need
the latter only and must workaround it by "frame:extensionTag".

Link to the target template is showed without a possible default "Template:"
prefix, other prefixes are visible, even if template not found. Internal
structure of the link:
* a) "[[" (not visible, do NOT encode)
* b) name of the template always with prefix (not visible, link target)
* c) wall "|" (not visible, do NOT encode)
* d) "{{" (visible) (works without encoding)
* e) name of the template without default prefix + parameter chain (visible)
* f) "}}" (visible) (works without encoding)
* g) "]]" (not visible, do NOT encode)
Note that the HTML parser can split the string into multiple lines
(or more lines than requested by EOL:s) if there are spaces in                  !!!FIXME!!! EOL to <br> and underscore to space
but not if underscores "_" are used instead.

Encoding of text:
* For backlinks and redirects we have to urlencode the parameter,
  "lfwbrew3url" with help of "{{canonicalurl:...}}" does encode the
  service page "Special:WhatLinksHere" but the target parameter
  must be encoded separately by "mw.uri.encode".
* For the call column, we must preserve the layout of preformatted text
  and block all parsing, but we want to have all the stuff in a link to the
  template, with optionally reduced font size, "lfidecencodbr" does the
  encoding natively blocking all wikilink parsing, HTML tag parsing and HTML
  entity parsing, output from it must be placed into a special inner table
  (see below) ensuring monospace.
* For the output column, we disallow "<nowiki>" and "<syntaxhighlight
  lang="text">" in content coming from the template, further we need special
  handling for "<pre>" where we must encode all "[" "]" "&" to prevent parsing
  of wikilinks (this phenomenon may indeed occur in some cases) and HTML
  entities. Cat insertions inside areas poorly inactivated by "<pre>" are NOT
  caught, they are fixed by encoding in "lfysplitkat" instead.
* For the debug column, we must avoid any parsing, and make spaces, LF:s and
  broken char:s visible, "lfiultencode" provides this natively.

There are several possible ways to display monospaced preformatted text:
* "<pre>" (NOT used for the call)
  + nice appearance with box
  + simple syntax
  - aggressive high-priority tag with inconsistent and dangerous
    behaviour when coming from a template, can cause strip markers
* "<code><nowiki>" (NOT used for the call)
  + less aggressive tag
  + can be combined with "<small>"
  + can be placed into a link
  - "<nowiki>" does not work when coming from a template, still can
    cause strip markers
  - needs manual fixing of spaces and LF:s
  - bad appearance for multiline text, particularly if it
    contains empty lines, no box
* do all manually, use a HTML table for the box (used here for the call)
  + nice appearance with box
  + no aggressive or dangerous tags
  + can vary text size
  + can be placed into a link
  - needs manual fixing of spaces and LF:s
  - needs complicated HTML+CSS

There are several imaginable ways to submit a call to a target
template into a calling proxy ie "pate". None of them is perfect,
and none of them will support plain nested template calls.
* submit target as an anonymous parameter followed by forwarded
  parameters and seize them by "pairs" (NOT used)
  + nice syntax, no extra brackets
  - sortorder of parameters NOT preserved
  - formatting of the call NOT preserved
  - needs some "ultrahidden" parameters for submitting instructions
    into the calling proxy
  - nested template calls will not work and cannot be supported
* complete call protected by rectangular brackets (used here)
  + fairly nice syntax, only 4 extra brackets [[ ]]
  + sortorder of parameters preserved
  + formatting of the call preserved
  + nested template calls can be supported with a workaround
  - syntax is a bit strange with a faked wikilink

There are several ways how to expand a template:
* arxframent:expandTemplate (eats title + table, adds ns prefix, crashes
  on the spot if template does not exist) (NOT used)
* arxframent:preprocess (eats raw string, adds ns prefix if {{ }} is used,
  does NOT work with substitution but this doesn't really matter here)
  (used here)
Note that above methods for expanding a template are NOT expensive whereas
wiki's native "ifexist" and LUA's "getContent()" are. Unfortunately we have
to expand two times, but both are free. In order to get free "ifexist" we
need "msgnw:", but for the main work we must avoid "msgnw:".
Another problem are strip markers deeply buried in the design of MediaWiki.
Some aggressive tags such as <pre> and <nowiki> in a classic template being
expanded result in strip markers, whereas this does NOT occur if a module
generates such content. The function "mw.text.unstrip" is inherently useless
since it returns raw text for <nowiki> (with the tags removed), and a
completely empty string in all other cases including <pre>.

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

### Arrangements

There are several useful styles to arrange template calls.

Inline compact:
  {{doall|now|priority=urgent}}

Inline with spaces:
  {{ doall | now | priority = urgent }}

Multiline with spaces:
  {{ taxobox
  | domain = none
  | kingdom = fungi
  | phylum = unknown
  }}

Chess:
  {{chessboard
  |--|--|--|--|kd|--|--|rd
  |--|--|--|--|--|--|--|--
  |--|--|--|--|--|bl|--|pl
  |--|--|--|rl|--|kl|pd|pl
  |--|--|--|--|--|--|--|--
  |--|--|--|--|--|--|--|--
  |--|--|--|--|--|--|--|--
  |--|--|--|--|--|--|--|--
  |btm = deliver checkmate in two moves
  }}

We do NOT really bother, and keep the complete call to the target template
as-is except replacing outer [[ ]] by {{ }}. We must, however, isolate
the target title (since we link to it in various ways), and strip
all possible whitespace around it.

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

### Examples of usage

Two templates (by default tab=2 and sel=111-110):
  {{pate|:top}}
  {{pate|hr3}}
  {{pate|hr4}}
  {{pate|:end}}

Two templates with manually selected some columns (by default tab=2):
  {{pate|sel=110-110|:top}}
  {{pate|sel=110-110|hr3}}
  {{pate|sel=110-110|hr4}}
  {{pate|:end}}

Two templates with manually selected all columns (by default tab=2):
  {{pate|sel=111-111|:top}}
  {{pate|sel=111-111|hr3}}
  {{pate|sel=111-111|hr4}}
  {{pate|:end}}

One template suboptimal way horizontal table (by default tab=2):
  {{pate|sel=110-100|:top}}
  {{pate|sel=110-100|Pengguna transportasi publik}}
  {{pate|:end}}

One template better way horizontal table:
  {{pate|tab=3|sel=110-100|Pengguna transportasi publik}}

One template better way horizontal table without title row:
  {{pate|tab=4|sel=110-100|Pengguna transportasi publik}}

One template better way vertical table (no way to show multiple templates):
  {{pate|tab=5|sel=110-100|Pengguna transportasi publik}}

One template minimal use no table, used only to suppress
categorization (tab=0 brings default sel=010-000):
  {{pate|tab=0|Pengguna transportasi publik}}

One template reduced output show only parsed result and categories:
  {{pate|tab=3|sel=010-100|Pengguna transportasi publik}}

One template reduced output show only parsed result
and categories, and without title row:
  {{pate|tab=4|sel=010-100|Pengguna transportasi publik}}

Two different calls to one template with custom title (by default tab=2):
  {{pate|sel=110-111|:top|tit=CALL@@PARSED@@REM@@CAT@@BACK-LINKS@@DEBUG}}
  {{pate|sel=110-111|[[ambox|text=this is a test|bgcolor=FF8080]]}}
  {{pate|sel=110-111|[[ambox|text=this is a test|bgcolor=#FF8080]]|rem=invalid usage}}
  {{pate|:end}}

One template suboptimal way horizontal table small text (by default tab=2):
  {{pate|sel=111-000|:top}}
  {{pate|sel=211-000|[[
    taxobox
    | domain = none
    | kingdom = fungi
    | phylum = unknown
  ]]}}
  {{pate|:end}}

One template better way horizontal table small text:
  {{pate|tab=3|sel=211-000|[[
    taxobox
    | domain = none
    | kingdom = fungi
    | phylum = unknown
  ]]}}

* make sure to use same value for "sel=" for the ":top" call and
  all core calls below it
* do NOT feed "tab=" or "sel=" into the final ":end" call
* make sure to use the colon in ":top" and ":end", this convention allows
  to call templates named "top" and "end"
* the order of named parameters relative to each other and to the only
  anonymous parameter technically does not matter, but the style above
  with "tab=" and "sel=" preceding the big anonymous parameter and all other
  named parameters following it is strongly encouraged for best readability

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

]===]

local exporttable = {}

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

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

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

  -- uncommentable constant strings (lang and core site-related features)

    -- local constrpriv = "en"                   -- EN (privileged site language) !!!FIXME!!! peek this
      local constrpriv = "eo"                 -- EO (privileged site language)
        -- local constrpriv = "id"               -- ID (privileged site language)
          -- local constrpriv = "sv"             -- SV (privileged site language)

  local constrprli = "Special:WhatLinksHere" -- EN worx everywhere (must NOT end with a blackslash, must NOT be urlencoded)

  -- uncommentable constant table (error messages)                              !!!FIXME!!! incomplete and no "constrkoll" yet

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

  local contaberaroj = {}
  -- contaberaroj[ 2] = 'Unknown parameter'                                     -- EN #E02 !!!FIXME!!! incomplete, and move up
  contaberaroj[ 2] = 'Nekonata parametro'                                    -- EO #E02
  -- contaberaroj[ 2] = 'Parameter tidak dikenal'                               -- ID #E02
  -- contaberaroj[ 2] = 'Ok\aend parameter'                                     -- SV #E02
  -- contaberaroj[ 3] = 'Bad anonymous parameter'                               -- EN #E03
  contaberaroj[ 3] = 'Erara anonima parametro'                               -- EO #E03
  -- contaberaroj[ 3] = 'Parameter anonim salah'                                -- ID #E03
  -- contaberaroj[ 3] = 'Felaktig anonym parameter'                             -- SV #E03
  -- contaberaroj[ 4] = 'Wrong use of [[ | ]] in anonymous parameter'           -- EN #E04
  contaberaroj[ 4] = 'Erara uzo de [[ | ]] en anonima parametro'             -- EO #E04
  -- contaberaroj[ 4] = 'Penggunaan [[ | ]] salah dalam parameter anonim'       -- ID #E04
  -- contaberaroj[ 4] = 'Felaktig anv\\aendning av [[ | ]] i anonym parameter'  -- SV #E04
  -- contaberaroj[ 5] = 'Bad or nonstandard target title or namespace'             -- EN #E05
  contaberaroj[ 5] = 'Erara aux nestandarda cela titolo aux nomspaco'           -- EO #E05
  -- contaberaroj[ 5] = 'Judul atau ruang nama sasaran salah atau takbaku'         -- ID #E05
  -- contaberaroj[ 5] = 'Felaktig eller icke-standardiserad titel eller namnrymd'  -- SV #E05

    -- contaberaroj[ 8] = '"tab=" bad'                                               -- EN #E08
      contaberaroj[ 8] = '"tab=" erara'                                           -- EO #E08
        -- contaberaroj[ 8] = '"tab=" salah'                                         -- ID #E08
    -- contaberaroj[21] = '"sel=" bad'                                               -- EN #E21
      contaberaroj[21] = '"sel=" erara'                                           -- EO #E21
        -- contaberaroj[21] = '"sel=" salah'                                         -- ID #E21

    -- contaberaroj[40] = '"sel=" conflicts with "tab="'                             -- EN #E40
      contaberaroj[40] = '"sel=" konfliktas kun "tab="'                           -- EO #E40
        -- contaberaroj[40] = '"sel=" tidak sesuai dengan "tab="'                    -- ID #E40
    -- contaberaroj[41] = '"mod=" conflicts with "zer=1"'                            -- EN #E41
      contaberaroj[41] = '"mod=" konfliktas kun "zer=1"'                          -- EO #E41
        -- contaberaroj[41] = '"mod=" tidak sesuai dengan "zer=1"'                   -- ID #E41
    -- contaberaroj[43] = '"trackingcategorieshiglow=" sent into "pate"'             -- EN #E43
      contaberaroj[43] = '"trackingcategorieshiglow=" sendita al "pate"'          -- EN #E43
        -- contaberaroj[43] = '"trackingcategorieshiglow=" dimasukkan ke "pate"'     -- EN #E43
    -- contaberaroj[43] = '"nocat=" sent into "pate"'                                -- EN #E44
      contaberaroj[43] = '"nocat=" sendita al "pate"'                             -- EN #E44
        -- contaberaroj[43] = '"nocat=" dimasukkan ke "pate"'                        -- EN #E44

    contaberaroj[46] = 'Strip marker detected in incoming target parameter, use "tag=1"'  -- EN #E46
    contaberaroj[48] = 'Hidden parameter contains double brackets [[ ]] {{'               -- EN #E48
    contaberaroj[49] = '*HIDDEN* without preceding wall "|"'                              -- EN #E49

  -- 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 constant strings (messages)

  local constrbalik = "Backlinks<br>Ligioj cxi tien"  -- EO
  -- local constrbalik = "Backlinks<br>Pranala balik"    -- ID
  local constrredir = "Redirects<br>Alidirektiloj"    -- EO
  -- local constrredir = "Redirects<br>Pengalihan"       -- ID

  -- local constrca3d = "(no category)"            -- EN
  local constrca3d = "(ne estas kategorio)"     -- EO
  -- local constrca3d = "(tidak ada kategori)"     -- ID
  -- local constrca3d = "(ingen kategori)"         -- SV
  local constrca4d = "Eniras"                   -- EO
  -- local constrca4d = "Masuk ke"                 -- ID
  -- local constrca4d = "Fastnar i"                -- SV
  local constrca5d = "kategoriojn"              -- EO
  -- local constrca5d = "kategori"                 -- ID
  -- local constrca5d = "kategorier"               -- SV

  -- default column names (short, always English, one bare word)

  local contabcolumns = {[0]="call","parsed","rem","cat","back","debug"} -- [0]...[5] see "lfhatmergetab"

  -- cell background colors (2 or 6 hex digits) for "lfhtablewithmap" only

  local contabbk = {}
  contabbk[ 0] = "FF"     -- white no link
  contabbk[ 1] = "E9"     -- -22 grey
  contabbk[ 2] = "D4"     -- -21 grey
  contabbk[ 3] = "C4"     -- -16 grey
  contabbk[ 4] = "B4"     -- -16 grey
  contabbk[ 5] = "A8"     -- -12 grey
  contabbk[ 6] = "9C"     -- -12 grey
  contabbk[ 7] = "92"     -- -10 grey
  contabbk[ 8] = "88"     -- -10 grey
  contabbk[ 9] = "7E"     -- -10 grey
  contabbk[15] = "FF7070" -- deep red strip markers
  contabbk[16] = "FF8000" -- orange nowiki
  contabbk[17] = "C06090" -- violet syntaxhighlight
  contabbk[22] = "2020FF" -- blue pre
  contabbk[23] = "20FF20" -- green comment

  -- uncommentable constant strings (more messages, #M00 omitted here, for "lfiexplain" only)

  local contabxxplain = {}
  -- contabxxplain ['#X80'] = 'target (template or page) does NOT exist'     -- EN
  contabxxplain ['#X80'] = 'celo (sxablono aux pagxo) NE ekzistas'        -- EO
  -- contabxxplain ['#X80'] = 'sasaran (templat atau halaman) TIDAK ada'     -- ID
  -- contabxxplain ['#X82'] = 'empty string from expansion'                  -- EN
  contabxxplain ['#X82'] = 'maplena signocxeno el ekspando'               -- EO
  -- contabxxplain ['#X82'] = 'string kosong dari ekspansi'                  -- ID
  -- contabxxplain ['#M01'] = 'unclosed inactive area'                       -- EN
  contabxxplain ['#M01'] = 'nefermita neaktiva areo'                      -- EO
  -- contabxxplain ['#M01'] = 'wilayah nonaktif tidak ditutup'               -- ID
  contabxxplain ['#M02'] = 'unclosed strip marker'
  contabxxplain ['#M03'] = 'unbalanced [[ ]] in active areas'
  contabxxplain ['#M04'] = 'strip markers (pre nowiki ref)'
  contabxxplain ['#M05'] = 'strip markers (other)'
  contabxxplain ['#M06'] = 'syntaxhighlight'
  contabxxplain ['#M08'] = 'pre'
  contabxxplain ['#M09'] = 'nowiki'
  contabxxplain ['#M10'] = 'ref'
  contabxxplain ['#M11'] = 'HTML comment'
  contabxxplain ['#M12'] = '<includeonly> et al'
  contabxxplain ['#M20'] = 'max wikilink depth' -- follows number
  contabxxplain ['#M22'] = 'treacherous space'
  contabxxplain ['#S20'] = 'split not attempted due to other'             -- EN
  contabxxplain ['#S21'] = 'split not attempted due to nesting'           -- EN
  contabxxplain ['#S23'] = 'broken wikilink lacking left part'            -- EN
  contabxxplain ['#S24'] = 'cat insertion on level different from ONE'    -- EN

  -- for "lfiultencode" only

  local constrpilen = '[[File:Return arrow.svg|20px|link=]]' -- the file is Public Domain
  local contabempatwarna = {[0]='FFA0A0','D0FFD0','A0A0FF','D0D0D0'} -- red, light green, blue, light grey

  -- prohibited stuff -- see above under "Limitations and caveats" why          !!!FIXME!!! unused

  local tabprohi = {[0]="<includeonly>","</includeonly>","<noinclude>","</noinclude>"}

  -- constant LUA tables for HTML tables with CSS

  local contabtabeg = {[0]='<table style="margin:0.6em;border:0.','em solid #',';border-collapse:collapse;">'} -- [0]...[2]
  local contabtdbeg = {[0]='<td style="border:0.','em solid #',';padding:0.5em;text-align:center;">'} -- [0]...[2]

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

  -- surrogate transcoding table (only needed for SV)

  local contabtranslutsv = {}
  contabtranslutsv['AA'] = 0xC385 -- Aring
  contabtranslutsv['Aa'] = 0xC385 -- Aring
  contabtranslutsv['aa'] = 0xC3A5 -- aring
  contabtranslutsv['AE'] = 0xC384
  contabtranslutsv['Ae'] = 0xC384
  contabtranslutsv['ae'] = 0xC3A4
  contabtranslutsv['EE'] = 0xC389 -- rarely used
  contabtranslutsv['Ee'] = 0xC389 -- rarely used
  contabtranslutsv['ee'] = 0xC3A9 -- rarely used
  contabtranslutsv['OE'] = 0xC396
  contabtranslutsv['Oe'] = 0xC396
  contabtranslutsv['oe'] = 0xC3B6

-- constant strings (misc)

local constrempt = '--' -- placeholder for empty table cell "rem=" or "back"

-- math constants and boolean switches

local contabboo2int = {[false]=0,[true]=1}
local contabboo2siz = {[false]='100',[true]='70'}

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

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

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

  -- SPECIAL VAR:S

local qbooguard = false  -- only for the guard test, pass to other var ASAP
local qstrtabegin = ''   -- from "contabtabeg" added col & wi, begin of table
local qstrtdbegin = ''   -- from "contabtdbeg" added col & wi, begin of cell
local qstrtdendus = ''   -- end of cell
local qstrtaendus = ''   -- end of table
local qtabmy6columns = {} -- based on "contabcolumns" used in "lficondicelrow"

  -- GUARD AGAINST INTERNAL ERROR

qbooguard = ((type(constrpriv)~='string') or (type(constrprli)~='string'))

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

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

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

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

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

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

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

-- Local function MATHBITTEST

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

-- Result has type "boolean".

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

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

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

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

-- Local function LFDEC1DIGIT

-- Convert 1 decimal ASCII digit to integer 0...9 (255 if invalid).

local function lfdec1digit (num1digit)
  num1digit = num1digit - 48 -- may become invalid
  if ((num1digit<0) or (num1digit>9)) then
    num1digit = 255 -- report ERROR on invalid input digit
  end--if
  return num1digit
end--function lfdec1digit

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

-- Local function LFSTRONDIG2INT

-- Convert string with 1 decimal ASCII digit
-- to integer 0...9 (255 if invalid).

-- Depends on functions :
-- [N] lfdec1digit

local function lfstrondig2int (strmasukk)
  local numalone = 255 -- preASSume guilt
  if (string.len(strmasukk)==1) then
    numalone = string.byte (strmasukk,1,1)
    numalone = lfdec1digit (numalone) -- 255 if invalid, ZERO would be valid
  end--if
  return numalone
end--function lfstrondig2int

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

-- Local function LFSTRTWDIG2INT

-- Convert string with always 2 decimal ASCII digit:s
-- to integer 0...99 (255 if invalid).

-- Depends on functions :
-- [N] lfdec1digit

local function lfstrtwdig2int (strmasuuk)
  local numleft = 255 -- preASSume guilt
  local numrayt = 0
  if (string.len(strmasuuk)==2) then
    numleft = string.byte (strmasuuk,1,1)
    numleft = lfdec1digit (numleft) -- 255 if invalid, ZERO would be valid
    numrayt = string.byte (strmasuuk,2,2)
    numrayt = lfdec1digit (numrayt) -- 255 if invalid, ZERO would be valid
    if (numrayt==255) then
      numleft = 255 -- 255 is invalid, note that ZERO would be valid
    else
      numleft = numleft * 10 + numrayt -- valid integer number 0...99 now
    end--if
  end--if
  return numleft
end--function lfstrtwdig2int

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

-- Local function LFNONEHEXTOINT

-- Convert single quasi-digit (ASCII HEX "0"..."9" "A"..."F") to
-- integer (0...15, 255 invalid).

-- Only uppercase accepted.

local function lfnonehextoint (numdigit)
  local numresult = 255
  if ((numdigit>47) and (numdigit<58)) then
    numresult = numdigit-48
  end--if
  if ((numdigit>64) and (numdigit<71)) then
    numresult = numdigit-55
  end--if
  return numresult
end--function lfnonehextoint

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

-- Local function LFNUMTO2DIGIT

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

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

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

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

-- Local function LFUINT8TOHEX

-- Convert UINT8 (0...255) to a 2-digit hex string.

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

local function lfuint8tohex (numinclow)
  local strheksulo = ''
  local numhajhaj = 0
  numhajhaj = mathdiv (numinclow,16)
  numinclow = mathmod (numinclow,16)
  if (numhajhaj>9) then
    numhajhaj = numhajhaj + 7 -- now 0...9 or 17...22
  end--if
  if (numinclow>9) then
    numinclow = numinclow + 7 -- now 0...9 or 17...22
  end--if
  strheksulo = string.char (numhajhaj+48) .. string.char (numinclow+48)
  return strheksulo
end--function lfuint8tohex

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

---- LOW LEVEL STRING FUNCTIONS [G] ----

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

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

-- Test whether incoming ASCII char is very safe (0...9 A...Z a...z).

-- Depends on functions :
-- [G] lfgtestnum lfgtestuc lfgtestlc

local function lfgis62safe (numcxair)
  local booguud = false
  booguud = lfgtestnum (numcxair) or lfgtestuc (numcxair) or lfgtestlc (numcxair)
  return booguud
end--function lfgis62safe

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

-- Local function LFGTRIMWHITE

-- Input  : * strputihsekali -- string, empty tolerable, but type "nil" is NOT

local function lfgtrimwhite (strputihsekali)
  local numtrimthemall = 0
  local boodonenow = false
  while true do
    if (strputihsekali=='') then
      break
    end--if
    boodonenow = true
    numtrimthemall = string.byte(strputihsekali,1,1)
    if ((numtrimthemall==32) or (numtrimthemall==10)) then
      strputihsekali = string.sub (strputihsekali,2,-1)
      boodonenow = false
    end--if
    numtrimthemall = string.byte(strputihsekali,-1,-1)
    if ((numtrimthemall==32) or (numtrimthemall==10)) then
      strputihsekali = string.sub (strputihsekali,1,-2)
      boodonenow = false
    end--if
    if (boodonenow) then
      break
    end--if
  end--while
  return strputihsekali -- same var for input and output
end--function lfgtrimwhite

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

-- Local function LFGDELETEALLWHITE

local function lfgdeleteallwhite (strin52in)
  local strut52ut = ''
  local numlen52len = 0
  local numind52ind = 0
  local numchr52chr = 0
  numlen52len = string.len(strin52in)
  while true do -- genuine loop
    if (numind52ind>=numlen52len) then
      break
    end--if
    numchr52chr = string.byte(strin52in,(numind52ind+1),(numind52ind+1))
    if ((numchr52chr~=10) and (numchr52chr~=32)) then
      strut52ut = strut52ut .. string.char(numchr52chr)
    end--if
    numind52ind = numind52ind + 1
  end--while
  return strut52ut
end--function lfgdeleteallwhite

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

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

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

-- Local function LFULNUTF8CHAR

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

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

-- Output : * numlen1234x -- 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 -- $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

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

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

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

-- Local function LFIPILLEGAL

-- Check whether a string contains illegal stuff such
-- as LF < > [ ] '' [[ ]] {{ }} UNIQ [http. Note that codes
-- 0...9 and 11...31 are unconditionally prohibited.

-- Input  : * strinpil  -- empty cannot cause major harm and returns "false"
--          * boonolflf -- disallow LF
--          * boonotags -- disallow single diamond brackets <> ie HTML tags
--          * boonobold -- disallow double apo "''" ie both italics and bold
--                         (<b> and <i> will still pass)
--          * numnorect -- 0 allow rect | 1 disallow double "[[" and "]]"
--                         but not single ones | 2 disallow even single [ ]
--          * boonotskl -- disallow "{{" and "}}" but not single ones
--          * boonostrp -- disallow strip
--          * boonohttp -- disallow "[http" (this is redundant
--                         for numnorect=2)

-- Output : * boocriminal ("true" if "strinpil" contains illegal stuff)

local function lfipillegal (strinpil, boonolflf, boonotags, boonobold, numnorect, boonotskl, boonostrp, boonohttp)

  local numleunin = 0
  local numindoxx = 1 -- ONE-based
  local numtecken = 0
  local numtecprv = 0 -- previous char
  local boocriminal = false

  while true do -- fake loop

    boocriminal = boonostrp and (string.find(strinpil,(string.char(127,39,34,96)..'UNIQ'),1,true)~=nil) -- striptease marker
    if (boocriminal) then
      break
    end--if
    boocriminal = boonohttp and (string.find(strinpil,"[http",1,true)~=nil) -- external link  !!!FIXME!!! this is weak
    if (boocriminal) then
      break
    end--if
    boocriminal = boonohttp and (string.find(strinpil,"[ http",1,true)~=nil) -- external link  !!!FIXME!!! this is weak
    if (boocriminal) then
      break
    end--if

    numleunin = string.len (strinpil) -- length of input string to be tested
    while true do
      if (numindoxx>numleunin) then
        break -- innocent now
      end--if
      numtecken = string.byte (strinpil,numindoxx,numindoxx)
      if ((numtecken<31) and (numtecken~=10)) then
        boocriminal = true -- unconditionally prohibited
        break
      end--if
      if (boonolflf and (numtecken==10)) then
        boocriminal = true -- LF
        break
      end--if
      if (boonotags and ((numtecken==60) or (numtecken==62))) then
        boocriminal = true -- single "<" or ">" is criminal
        break
      end--if
      if ((numnorect==2) and ((numtecken==91) or (numtecken==93))) then
        boocriminal = true -- catch even single [ ]
        break
      end--if
      if (numtecken==numtecprv) then
        if (boonobold and (numtecken==39)) then
          boocriminal = true -- "''" ie italics or bold
          break
        end--if
        if ((numnorect~=0) and ((numtecken==91) or (numtecken==93))) then
          boocriminal = true
          break
        end--if
        if (boonotskl and ((numtecken==123) or (numtecken==125))) then
          boocriminal = true
          break
        end--if
      end--if
      numtecprv = numtecken -- previous char
      numindoxx = numindoxx + 1
    end--while

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

  return boocriminal

end--function lfipillegal

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

-- Local function LFIDECENCODBR

-- Moderately encode char:s to prevent parsing (all wikilink parsing, HTML tag
-- parsing, HTML entity parsing), and optionally apply special treatment to
-- LF:s turning them into <br> as well as to spaces preventing reduction. This
-- is intended for showing sane UTF8 text, NOT for debugging purposes.

-- Input  : * strinkkkkin -- string, empty tolerable, but type "nil" is NOT
--          * boopreformat -- true for special treatment preserving
--                            preformatted text

-- Output : * strbrbrcod -- string, empty in worst case

local function lfidecencodbr (strinkkkkin, boopreformat)

  local strwanchar = ''
  local strbrbrcod = ''
  local numsrtlen = 0
  local numpekkinx = 1 -- ONE-based index
  local numchmuar = 0
  local boonbtp = true -- "true" needed for junk lines containing only space

  numsrtlen = string.len (strinkkkkin)

  while true do -- outer genuine loop
    if (numpekkinx>numsrtlen) then
      break
    end--if
    numchmuar = string.byte (strinkkkkin,numpekkinx,numpekkinx)
    numpekkinx = numpekkinx + 1
    while true do -- inner fake loop
      if (numchmuar==9) then
        numchmuar = 32 -- TAB to SPACE always
      end--if
      if ((numchmuar==10) and boopreformat) then -- special treatment
        strwanchar = '<br>'
        boonbtp = true -- "true" needed for junk lines containing only space
        break
      end--if
      if ((numchmuar==32) and boopreformat) then -- special treatment
        if (boonbtp) then
          strwanchar = '&nbsp;' -- this prevents space reduction
        else
          strwanchar = ' '
        end--if
        boonbtp = not boonbtp
        break
      end--if
      if ((numchmuar==10) or (numchmuar==32) or (numchmuar>127)) then
        strwanchar = string.char (numchmuar) -- pass LF SPACE UTF8 untouched
        break
      end--if
      if (numchmuar<32) then
        numchmuar = 63 -- filter away crap to "?" -- then encode it below
      end--if
      strwanchar = '&#' .. tostring (numchmuar) .. ';' -- encode all ASCII
      break -- finally to join mark
    end--while -- inner fake loop -- join mark
    strbrbrcod = strbrbrcod .. strwanchar
  end--while -- outer genuine loop

  return strbrbrcod

end--function lfidecencodbr

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

-- Local function LFIULTENCODE

-- Generously encode char:s to prevent parsing and show hex if needed, make
-- single chars visible, bypass all wiki parsing and HTML parsing. Our cool
-- module has brewed something with "[["..."]]" and repeated spaces but we
-- want to see plain text for debugging purposes. Thus we dec-encode some
-- char:s, use NBSP to fix spaces, workaround EOL, and maybe add colour.

-- Input  : * strkrampuj  : string, empty tolerable, but type "nil" is NOT
--          * nummxwidth  : maximal width of text (20...200, default 80)
--          * boowarrna   : "true" to enable color
--          * boosplitutf : "true" to split UTF8 char:s into hex numbers

-- Output : * strkood     : string, empty in worst case

-- Depends on functions :
-- [U] lfulnutf8char
-- [G] lfgtestnum lfgtestuc lfgtestlc lfgis62safe
-- [N] lfuint8tohex
-- [E] mathdiv mathmod

-- Depends on constants :
-- * string constrpilen [[File:...]]
-- * table contabempatwarna 0...3

-- This helps with:
-- * "[["..."]]", "["..."]", "*", "#", ":" (note that there is no
--   problem with plain "{{"..."}}")
-- * multiple spaces (they are no longer reduced to one piece due to HTML)
-- * EOL:s (they do not vanish in favor of spaces due to HTML, instead
--   the EOL arrow is showed)
-- * too long lines (they are force-broken)
-- * codes below 32 other than EOL

-- There is also "mw.text.nowiki" with some limitations, most notably
-- about multiple spaces and EOL:s.

-- In order to fix EOL we show the EOL arrow (preceded by space) for every
-- incoming LF, but do a "<br>" only once after multiple subsequent LF:s.

-- We must be UTF8-aware. A UTF8 char must be either split into hex codes,
-- or preserved over its complete length ie not split nor encoded at all.

-- Note that this causes BLOAT. The caller is responsible for
-- adding "<big>"..."</big>" if desired.

local function lfiultencode (strkrampuj,nummxwidth,boowarrna,boosplitutf)

  local stronechar = ''
  local strkolorr = ''
  local strkood = ''
  local numstrlne = 0
  local numpeekynx = 1 -- ONE-based index
  local numcahr = 0
  local numcxxhr = 0
  local numutf8len = 0
  local numaccuwidth = 0 -- accumulated width
  local numcolour = 0 -- 0,1,2,3 -- R,G,B,Y
  local boonbsp = true -- "true" needed for junk lines containing only space
  local boosplnow = false -- allow forced split in some cases
  local boofickpilen = false -- true after LF arrow causes "<br>" later

  if (type(nummxwidth)~='number') then
    nummxwidth = 80
  end--if
  if ((nummxwidth<20) or (nummxwidth>200)) then
    nummxwidth = 80
  end--if
  numstrlne = string.len (strkrampuj)

  while true do -- outer genuine loop

    if (numpeekynx>numstrlne) then
      break
    end--if
    numcahr = string.byte (strkrampuj,numpeekynx,numpeekynx)
    numpeekynx = numpeekynx + 1 -- ONE-based index

    while true do -- inner fake loop
      if (numcahr==10) then
        break -- to join mark -- inner fake loop -- special processing for LF
      end--if
      if (numcahr==32) then
        if (boonbsp) then
          stronechar = '&nbsp;' -- this prevents space reduction
        else
          stronechar = ' '
        end--if
        boonbsp = not boonbsp
        break -- to join mark -- inner fake loop
      end--if
      if (numcahr<32) then
        stronechar = '{$' .. lfuint8tohex (numcahr) .. '}' -- always as hex
        break -- to join mark -- inner fake loop
      end--if
      if (numcahr>127) then
        boosplnow = boosplitutf
        numutf8len = lfulnutf8char (numcahr)
        if (numutf8len==0) then
          boosplnow = true -- forced split for broken UTF8 sequence
        else
          numutf8len = numutf8len - 1 -- more char:s to pick
        end--if
        if ((numpeekynx+numutf8len)>(numstrlne+1)) then
          boosplnow = true -- forced split for truncated UTF8 sequence
        end--if
        if (boosplnow) then
          stronechar = '{$' .. lfuint8tohex (numcahr) .. '}'
        else
          stronechar = string.char (numcahr) -- preserve "numcahr" below
          while true do -- deep loop copy UTF8 char
            if (numutf8len==0) then
              break
            end--if
            numcxxhr = string.byte (strkrampuj,numpeekynx,numpeekynx)
            numpeekynx = numpeekynx + 1
            numutf8len = numutf8len - 1
            stronechar = stronechar .. string.char (numcxxhr)
          end--while -- deep loop copy UTF8 char
        end--if
        break -- to join mark
      end--if (numcahr>127) then
      if (lfgis62safe(numcahr)) then -- safe ASCII ie 0...9 A...Z a...z
        stronechar = string.char (numcahr) -- do NOT encode safe char:s
        break -- to join mark
      end--if
      stronechar = '&#' .. tostring (numcahr) .. ';' -- dec-encode some ASCII
      break -- finally to join mark
    end--while -- inner fake loop -- join mark

    if (numcahr==10) then
      if (numaccuwidth>=nummxwidth) then
        strkood = strkood .. '<br>'
        numaccuwidth = 0
        boonbsp = true -- "true" needed for junk lines containing only space
      end--if
      strkood = strkood .. '&nbsp;' .. constrpilen
      numaccuwidth = numaccuwidth + 2 -- counts doubly
      boofickpilen = true
    else
      if (boofickpilen or (numaccuwidth>=nummxwidth)) then
        strkood = strkood .. '<br>'
        numaccuwidth = 0
        boonbsp = true -- "true" needed for junk lines containing only space
      end--if
      if (boowarrna) then
        strkolorr = contabempatwarna [numcolour]
        numcolour = mathmod ((numcolour+1),4) -- index 0...3
        strkood = strkood .. '<span style="background-color:#' .. strkolorr .. ';">' .. stronechar .. '</span>'
      else
        strkood = strkood .. stronechar
      end--if
      numaccuwidth = numaccuwidth + 1
      boofickpilen = false
    end--if (numcahr==10) else

  end--while -- outer genuine loop

  return strkood

end--function lfiultencode

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

-- Local function LFISPACUNDR !!!FIXME!!! unused -- column "call" ??

-- Spaces to underscores or vice-versa.

-- Incoming boolean "true" for underscores and "false" for spaces.

local function lfispacundr (strxxin, boounderlig)
  local numleang = 0
  local numandex = 0 -- ZERO-based
  local numcxarr = 0
  local strkatrol = ''
  numleang = string.len (strxxin)
  while true do -- genuine loop
    if (numandex==numleang) then
      break
    end--if
    numcxarr = string.byte (strxxin,(numandex+1),(numandex+1))
    if ((numcxarr==32) or (numcxarr==95)) then
      if (boounderlig) then
        numcxarr = 95
      else
        numcxarr = 32
      end--if
    end--if
    strkatrol = strkatrol .. string.char (numcxarr)
    numandex = numandex + 1
  end--while
  return strkatrol
end--function lfispacundr

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

-- Local function LFIKATPALDIGU

-- Brew cat insertion (no extra colon ":") or link to cat (with extra
-- colon ":") or link to page (appendix, other ns, even ns ZERO) from
-- 3 elements with optimization.

-- Input  : * strprephyx -- ns prefix without colon, empty or
--                          non-string if not desired ie ns ZERO
--          * strkataldnomo
--          * strhintvisi -- right part, empty or non-string if not desired
--          * numkattxtrakol -- ZERO for non-cat, ONE for cat insertion
--                              (needed for optimization), TWO for extra
--                              colon ie "colon rule" link to cat

local function lfikatpaldigu (strprephyx, strkataldnomo, strhintvisi, numkattxtrakol)
  local strtigatipwiki = ''
  if (type(strprephyx)~="string") then -- optional
    strprephyx = ''
  end--if
  if (type(strhintvisi)~="string") then -- optional
    strhintvisi = ''
  end--if
  if ((numkattxtrakol==1) and (strhintvisi==strkataldnomo)) then
    strhintvisi = '' -- optimize: default is without ns prefix for cat:s
  end--if
  if (strprephyx~='') then
    strkataldnomo = strprephyx .. ':' .. strkataldnomo -- now prefix plus name
  end--if
  if ((numkattxtrakol~=1) and (strhintvisi==strkataldnomo)) then
    strhintvisi = '' -- optimize: default is with ns prefix if such is present
  end--if
  if (numkattxtrakol==2) then
    strkataldnomo = ':' .. strkataldnomo -- ":Category"... apply "colon rule"
  end--if
  if (strhintvisi=='') then
    strtigatipwiki = '[[' .. strkataldnomo .. ']]'
  else
    strtigatipwiki = '[[' .. strkataldnomo .. '|' .. strhintvisi .. ']]'
  end--if
  return strtigatipwiki
end--function lfikatpaldigu

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

-- Local function LFIGETLEFTRIGHT

-- Split wikilink (in wide sense including cat insertions) into left
-- and right part detecting some common errors.

-- Input  : * strinasy -- like " Category :  Crime in Rhodesia | rho  "

-- Output : * numlerikodo, strinasy, strhinto

-- Depends on functions :
-- [G] lfgtrimwhite

-- Status values:
-- *  0 : OK (also if no wall)
-- * 75 : empty string or wall only (both invalid per MediaWiki)
-- * 76 : left empty or whitespace only (both invalid per MediaWiki)
-- * 77 : wall YES and right empty (dubious: "[[gluon|]]")
-- * 78 : right whitespace only (common: "[[Category:Time machines| ]]")
-- * 79 : junk spaces left or right (dubious: "[[Wiktionary:Bots |bot]]")

local function lfigetleftright (strinasy)

  local vartrovumuron = 0
  local strhinto = '' -- preASSume no right part
  local str9tmp = ''
  local numlerikodo = 0 -- preASSume innocence

  if ((strinasy=='') or (strinasy=='|')) then
    numlerikodo = 75
    strinasy = '' -- invalid
  else
    vartrovumuron = string.find(strinasy,'|',1,true)
    if (vartrovumuron) then
      if (vartrovumuron<(string.len(strinasy))) then
        strhinto = string.sub (strinasy,(vartrovumuron+1),-1)
      else
        numlerikodo = 77 -- right empty, overrides right whitespace only
      end--if
      if (vartrovumuron>1) then
        strinasy = string.sub (strinasy,1,(vartrovumuron-1))
      else
        numlerikodo = 76 -- left empty, overrides right empty (theoretically)
        strinasy = '' -- left part empty, invalid
      end--if
    end--if (vartrovumuron) then
  end--if ((strinasy=='') or (strinasy=='|')) else

  if (strinasy~='') then -- possible codes 0 77 so far
    str9tmp = lfgtrimwhite (strinasy) -- left
    if (str9tmp=='') then
      numlerikodo = 76 -- left empty or whitespace only, overrides right empty
    end--if
    if (strinasy~=str9tmp) then
      strinasy = str9tmp
      if (numlerikodo==0) then
        numlerikodo = 79 -- junk spaces
      end--if
    end--if
  end--if

  if (strhinto~='') then
    str9tmp = lfgtrimwhite (strhinto) -- right
    if ((str9tmp=='') and (numlerikodo==0)) then
      numlerikodo = 78 -- right whitespace only, overrides junk spaces left
    end--if
    if (strhinto~=str9tmp) then
      strhinto = str9tmp
      if (numlerikodo==0) then
        numlerikodo = 79 -- junk spaces
      end--if
    end--if
  end--if

  return numlerikodo, strinasy, strhinto

end--function lfigetleftright

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

-- Local function LFIVALIDATE6HEX

-- Validate a 6-digit HEX colour, only uppercase.

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

-- Depends on functions :
-- [N] lfnonehextoint

-- Criteria:
-- * length 6 chars
-- * "0"..."9" and "A"..."F"

local function lfivalidate6hex (strmyhexsixcolor)
  local numuuinx = 0 -- ZERO-based
  local numcyyaar = 0
  local booh6hisvalid = false -- preASSume guilt -- will be the final verdict
  if (string.len(strmyhexsixcolor)==6) then
    booh6hisvalid = true -- preASSume innocence
    while true do
      if (numuuinx==6) then
        break -- done search, all OK
      end--if
      numcyyaar = string.byte(strmyhexsixcolor,(numuuinx+1),(numuuinx+1))
      if (lfnonehextoint(numcyyaar)>15) then
        booh6hisvalid = false -- guilt proven
        break -- abort search
      end--if
      numuuinx = numuuinx + 1 -- ZERO-based
    end--while
  end--if
  return booh6hisvalid
end--function lfivalidate6hex

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

-- Local function LFIVALIUMDCTLSTR

-- Validate control string against restrictive pattern (dec).

-- Input  : * strresdpat -- restrictive pattern (max 200 char:s)
--          * strctldstr -- incoming suspect

-- Output : * numbadpos -- bad position, or 254 wrong length, or 255 success

-- Depends on functions :
-- [N] lfdec1digit

-- Content of restrictive pattern:
-- * "."                           -- skip check
-- * "-" and "?"                   -- must match literally
-- * digit "1"..."9" ("0" invalid) -- inclusive upper limit (min ZERO)

local function lfivaliumdctlstr (strresdpat, strctldstr)

  local numlenresdpat = 0
  local numldninkom = 0
  local numcomperindex = 0 -- ZERO-based
  local numead2 = 0
  local numead3 = 0
  local numbadpos = 254 -- preASSume guilt (len differ or too long or ...)
  local booddaan = false

  numlenresdpat = string.len(strresdpat)
  numldninkom = string.len(strctldstr)
  if ((numlenresdpat<=200) and (numlenresdpat==numldninkom)) then
    while true do
      if (numcomperindex==numlenresdpat) then
        numbadpos = 255
        break -- success
      end--if
      numead2 = string.byte(strresdpat,(numcomperindex+1),(numcomperindex+1)) -- rest
      numead3 = string.byte(strctldstr,(numcomperindex+1),(numcomperindex+1)) -- susp
      booddaan = false
      if ((numead2==45) or (numead2==63)) then
        if (numead2~=numead3) then
          numbadpos = numcomperindex
          break -- "-" and "?" must match literally
        end--if
        booddaan = true -- position OK
      end--if
      if (numead2==46) then -- skip for dot "."
        booddaan = true -- position OK
      end--if
      if (not booddaan) then
        numead2 = lfdec1digit(numead2) -- rest
        if (numead2>9) then -- limit defined or bad ??
          numbadpos = 254
          break -- bad restrictive pattern
        else
          numead3 = lfdec1digit(numead3) -- susp
          if (numead3>numead2) then
            numbadpos = numcomperindex
            break -- value limit violation
          end--if
        end--if (numead2>9) else
      end--if (not booddaan) then
      numcomperindex = numcomperindex + 1
    end--while
  end--if ((numlenresdpat<=200) and (numlenresdpat==numldninkom)) then

  return numbadpos

end--function lfivaliumdctlstr

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

-- Local function LFICHKPARAM

-- Check one incoming param against type and range.

-- Input  : * varmyparam
--          * numtypeofparam -- 0 string | 1 one-digit dec | 2 two-digit dec
--                              6 hex six-digit colour
--                              8 hex integer                                   !!!FIXME!!! incomplete
--          * numzzmin, numzzmax -- relevant for all types except 6

-- Output : * numverdictparam -- 0 nope | 1 exists invalid | 2 exists valid
--          * varparvalue -- copy (for types ZERO and 6) or converted value
--                           (for other types) or type "nil", do NOT use to
--                           check validity

-- Depends on functions :
-- [I] lfivalidate6hex
-- [N] lfdec1digit lfstrondig2int lfstrtwdig2int lfnonehextoint

local function lfichkparam (varmyparam, numtypeofparam, numzzmin, numzzmax)

  local varparvalue = 0 -- preASSume nope

  local numverdictparam = 0 -- preASSume nope
  local numlenof66param = 0
  local num66temp = 0

  if (type(varmyparam)=='string') then
    numverdictparam = 1 -- preASSume guilt
    numlenof66param = string.len(varmyparam)
    if ((numtypeofparam==0) and (numlenof66param>=numzzmin) and (numlenof66param<=numzzmax)) then -- 0 string
      numverdictparam = 2 -- type ZERO OK !!!
      varparvalue = varmyparam
    end--if
    if (numtypeofparam==1) then -- 1 one-digi
      num66temp = lfstrondig2int (varmyparam)
      if ((num66temp>=numzzmin) and (num66temp<=numzzmax)) then
        numverdictparam = 2 -- type ONE OK !!!
        varparvalue = num66temp -- converted
      end--if
    end--if
    if (numtypeofparam==2) then -- 2 two-digi
      num66temp = lfstrtwdig2int (varmyparam)
      if ((num66temp>=numzzmin) and (num66temp<=numzzmax)) then
        numverdictparam = 2 -- type 2 OK !!!
        varparvalue = num66temp -- converted
      end--if
    end--if
    if (numtypeofparam==6) then -- 6 hex six-digit colour
      if (lfivalidate6hex(varmyparam)) then
        numverdictparam = 2 -- type 6 OK !!!
        varparvalue = varmyparam
      end--if
    end--if
  end--if (type(varmyparam)=='string') then

  if (numverdictparam~=2) then
    varparvalue = nil
  end--if

  return numverdictparam, varparvalue

end--function lfichkparam

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

-- Local function LFIFILLNAME

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

-- To be called ONLY from "lfhfillsurrstrtab".

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

local function lfifillname (strmessage, strcaller)

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

  numstrloen = string.len (strmessage)

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

  return strhasill

end--function lfifillname

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

-- Local function LFIKODEOSG

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

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

-- Output : * strutf8eo -- UTF8 string

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

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

-- To be called ONLY from "lfhfillsurrstrtab".

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

local function lfikodeosg (streosurr)

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

  numeoinplen = string.len(streosurr)

  while true do

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

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

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

  end--while

  return strutf8eo

end--function lfikodeosg

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

-- Local function LFIKODSVSG

-- Transcode sv blackslash-surrogates in a single string (sv only).

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

-- Output : * strutf8sv -- UTF8 string

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

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

-- To be called ONLY from "lfhfillsurrstrtab".

-- * the latter letter in a surr triple is case insensitive,
--   for example both "\AEgare" and "\Aegare" give same result

local function lfikodsvsg (strsvsurr)

  local varsvpeek = 0
  local strutf8sv = ''
  local strsvdouble = ''
  local numsvinplen = 0
  local numinpinx = 0 -- ZERO-based source index
  local numsvonechar = 0 -- current char

  numsvinplen = string.len(strsvsurr)

  while true do

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

    numsvonechar = string.byte(strsvsurr,(numinpinx+1),(numinpinx+1))
    strsvdouble = '' -- preASSume no dblchar
    if ((numsvonechar==92) and ((numinpinx+2)<numsvinplen)) then
      strsvdouble = string.sub(strsvsurr,(numinpinx+2),(numinpinx+3))
    end--if

    varsvpeek = contabtranslutsv[strsvdouble] -- UINT16 or type "nil"
    if (type(varsvpeek)=='number') then
      strutf8sv = strutf8sv .. string.char(mathdiv(varsvpeek,256),mathmod(varsvpeek,256)) -- add UTF8 char
      numinpinx = numinpinx + 3 -- eaten 3 written 2
    else
      strutf8sv = strutf8sv .. string.char(numsvonechar) -- copy char
      numinpinx = numinpinx + 1 -- eaten 1 written 1
    end--if

  end--while

  return strutf8sv

end--function lfikodsvsg

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

-- Local function LFICENQTABQLEFQTXT

-- Outside center inside left, invisible.

local function lficenqtabqlefqtxt (strwhatever)
  strwhatever = '<table style="margin:auto;"><tr><td style="text-align:left;">' .. strwhatever .. '</td></tr></table>'
  return strwhatever
end--function lficenqtabqlefqtxt

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

-- Local function LFILEFQDIVQLEFQTXT

-- Outside left inside left, invisible.

local function lfilefqdivqlefqtxt (strwhootever)
  strwhootever = '<div style="text-align:left;">' .. strwhootever .. '</div>'
  return strwhootever
end--function lfilefqdivqlefqtxt

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

-- Local function LFIPRETABLE

-- Outside left inside left, visible.

local function lfipretable (strpredabletext, numfontsizepercent)
  strpredabletext = '<table><tr><td style="border:1px solid #E0E0E0;padding:0 0.15em 0 0.15em;background:#F8F8F8;font-family:monospace;text-align:left;font-size:' .. tostring(numfontsizepercent) .. '%;">' .. strpredabletext .. '</td></tr></table>'
  return strpredabletext
end--function lfipretable

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

-- Local function LFIRESTOREFROMTRIPLE

-- Restore ordinary doublebracket form from triplebracket workaround.

local function lfirestorefromtriple (strin9x9ut)

  local varfiindnes = 0
  local booalreadydone = false

  while true do -- genuine loop but no index

    booalreadydone = true

    varfiindnes = string.find(strin9x9ut,'{({',1,true)
    if (varfiindnes) then
      strin9x9ut = string.sub(strin9x9ut,1,varfiindnes) .. string.sub(strin9x9ut,(varfiindnes+2),-1)
      booalreadydone = false
    end--if
    varfiindnes = string.find(strin9x9ut,'})}',1,true)
    if (varfiindnes) then
      strin9x9ut = string.sub(strin9x9ut,1,varfiindnes) .. string.sub(strin9x9ut,(varfiindnes+2),-1)
      booalreadydone = false
    end--if

    if (booalreadydone) then
      break
    end--if

  end--while

  return strin9x9ut -- same var for in and out

end--function lfirestorefromtriple

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

-- Local function LFIRESTOREFROMNETAG

-- Restore ordinary form from ne-workaround for aggressive tags.

-- <pre> <--> <pnere>
-- <nowiki> <--> <nonewiki>
-- <ref> <--> <renef> even for "<ref group=" and "<ref name="
-- <gallery> <--> <gallenery>

local function lfirestorefromnetag (strin9y9ut)

  local varfiindtag = 0
  local booalreadydoen = false

  while true do -- genuine loop but no index

    booalreadydoen = true

    varfiindtag = string.find(strin9y9ut,'<pnere>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+1)) .. string.sub(strin9y9ut,(varfiindtag+4),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'</pnere>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+2)) .. string.sub(strin9y9ut,(varfiindtag+5),-1)
      booalreadydoen = false
    end--if

    varfiindtag = string.find(strin9y9ut,'<nonewiki>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+2)) .. string.sub(strin9y9ut,(varfiindtag+5),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'</nonewiki>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+3)) .. string.sub(strin9y9ut,(varfiindtag+6),-1)
      booalreadydoen = false
    end--if

    varfiindtag = string.find(strin9y9ut,'<renef>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+2)) .. string.sub(strin9y9ut,(varfiindtag+5),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'<renef name=',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+2)) .. string.sub(strin9y9ut,(varfiindtag+5),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'<renef group=',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+2)) .. string.sub(strin9y9ut,(varfiindtag+5),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'</renef>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+3)) .. string.sub(strin9y9ut,(varfiindtag+6),-1)
      booalreadydoen = false
    end--if

    varfiindtag = string.find(strin9y9ut,'<gallenery>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+4)) .. string.sub(strin9y9ut,(varfiindtag+7),-1)
      booalreadydoen = false
    end--if
    varfiindtag = string.find(strin9y9ut,'</gallenery>',1,true)
    if (varfiindtag) then
      strin9y9ut = string.sub(strin9y9ut,1,(varfiindtag+5)) .. string.sub(strin9y9ut,(varfiindtag+8),-1)
      booalreadydoen = false
    end--if

    if (booalreadydoen) then
      break
    end--if

  end--while

  return strin9y9ut -- same var for in and out

end--function lfirestorefromnetag

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

-- Local function LFIBREWCURLYCALL

local function lfibrewcurlycall (boonsiszero, strcurlytemcall, numbeeg, numennd)
  if (boonsiszero) then
    strcurlytemcall = ':' .. strcurlytemcall -- add colon for ns ZERO
  end--if
  if ((numbeeg==10) or (numbeeg==32)) then
    strcurlytemcall = string.char (numbeeg) .. strcurlytemcall
  end--if
  if ((numennd==10) or (numennd==32)) then
    strcurlytemcall = strcurlytemcall .. string.char (numennd)
  end--if
  strcurlytemcall = '{{' .. strcurlytemcall .. '}}'
  return strcurlytemcall
end--function lfibrewcurlycall

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

local function lfiexpandstatus (numpexpaste)
  return '#X' .. lfnumto2digit(numpexpaste)
end--function lfiexpandstatus

local function lfisplitstatus (numzsplitste)
  return '#S' .. lfnumto2digit(numzsplitste)
end--function lfisplitstatus

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

-- Local function LFICOMBOSTATUS

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

local function lficombostatus (num3exp, num3spl)
  local strcomplaint = '' -- keep empty on success
  if (num3exp==70) then -- #X70
    if (num3spl>=20) then -- #S20
      strcomplaint = lfisplitstatus (num3spl) -- exp pass split fail
    end--if
  else
    strcomplaint = lfiexpandstatus (num3exp) -- exp fail split irrelevant
  end--if
  return strcomplaint
end--function lficombostatus

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

-- Local function LFIEXPLAIN

-- Depends on constants :
-- * table "contabxxplain" with string keys

local function lfiexplain (strrezultinut)
  local varriskofnil = 0
  varriskofnil = contabxxplain[strrezultinut]
  if (type(varriskofnil)=='string') then
    strrezultinut = strrezultinut .. ' ' .. varriskofnil
  end--if
  return strrezultinut -- same var for in and out
end--function lfiexplain

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

-- Local function LFICONDICELROW

-- Brew either raw or cell <td>...</td> or row <tr>...</tr>.

-- Depends on upvalues :
-- * 2 strings "qstrtdbegin" and "qstrtdendus"
-- * table "qtabmy6columns"

local function lficondicelrow (strmytext, numtype0of5table, numindexofcell)
  if (numtype0of5table~=0) then -- add cell "td" for all except ZERO
    strmytext = qstrtdbegin .. strmytext .. qstrtdendus
  end--if
  if (numtype0of5table==5) then -- row of vertical table
    strmytext = '<tr>' .. qstrtdbegin .. qtabmy6columns[numindexofcell] .. qstrtdendus .. strmytext .. '</tr>'
  end--if
  return strmytext -- same var for in and out
end--function lficondicelrow

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

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

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

-- Local function LFHCONSTRUCTERAR

-- Input  : * numerar6code -- 1 ... 99 or 2 ... 99 (resistent against invalid
--                            data type, giving "??" on such)
--          * boopeek6it -- do peek description #E02...#E99 from table

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

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

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

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

end--function lfhconstructerar

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

-- Local function LFHBREWERROR

-- Input  : * numerar7code -- TWO-based error code 2 ... 99 (resistent
--                            against invalid data type, giving "??" on such)

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

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

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

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

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

-- Local function LFHFILLSURRSTRTAB

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

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

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

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

local function lfhfillsurrstrtab (varinkommen, varfyllo, strlingkod)

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

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

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

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

  return varutmatning

end--function lfhfillsurrstrtab

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

-- Local function LFHTABLETOHTMLLISTA

-- Convert LUA table into a HTML list optionally inside
-- an invisible HTML table.

-- Input  : * tabsomeytems -- ZERO-based LUA table, empty legal
--          * booaddtable

-- Output : * strhotomolo -- * empty string if incoming table is empty
--                           * raw string if incoming table has ONE element
--                           * else HTML list optionally inside
--                             an invisible HTML table

-- Depends on functions :
-- [I] lficenqtabqlefqtxt

-- <li></li> for single items in list
-- <ul></ul> for complete list

local function lfhtabletohtmllista (tabsomeytems, booaddtable)

  local var3elemento = 0
  local strhotomolo = ''
  local numindekso = 0 -- ZERO-based

  while true do -- genuine loop over incoming table
    var3elemento = tabsomeytems [numindekso]
    if (type(var3elemento)~='string') then
      break -- done
    end--if
    if (numindekso==0) then
      strhotomolo = var3elemento
    end--if
    if (numindekso==1) then
      strhotomolo = '<li>' .. strhotomolo .. '</li>' -- postponed work for #0
    end--if
    if (numindekso>=1) then
      strhotomolo = strhotomolo .. '<li>' .. var3elemento .. '</li>'
    end--if
    numindekso = numindekso + 1
  end--while

  if (numindekso>=2) then
    strhotomolo = '<ul>' .. strhotomolo .. '</ul>'
    if (booaddtable) then
      strhotomolo = lficenqtabqlefqtxt(strhotomolo) -- outsid cent insid left
    end--if
  end--if

  return strhotomolo

end--function lfhtabletohtmllista

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

-- Local function LFHATMERGETAB

-- Merge multifield string into table.

-- Input  : * streniroat -- string with some "@@", empty legal
--          * tabcolspan -- [0]...[5] booleans whether col active, no holes
--          * tabdefaultat -- [0]...[5] default names, ZERO-based, no holes

-- Output : * tabeliroat -- new table [0]...[5] may have holes,
--                          special [9] true on error

-- This is applied on "tit=". Maximally 5 separators "@@" and 6 substrings.

local function lfhatmergetab (streniroat, tabcolspan, tabdefaultat)

  local varfiinndd = 0

  local tabfromtit = {} -- temp, can be too small or too big, but no holes
  local tabeliroat = {} -- final, [0]...[5] may have holes

  local numcxeloj0x5x = 0 -- ZERO-based
  local numwithholes = 0

  local bootitisbad = false -- preASSume innocence

  while true do
    varfiinndd = string.find (streniroat, "@@", 1, true)
    if (not varfiinndd) then
      if (streniroat~='') then
        tabfromtit[numcxeloj0x5x] = streniroat -- take the last one
      end--if
      break -- good
    end--if
    if ((varfiinndd==1) or (varfiinndd==(string.len(streniroat)-1))) then
      bootitisbad = true -- baad, found at begin or end
      break
    end--if
    if (numcxeloj0x5x==5) then -- OFF-BY-ONE here the last one is added elsewhere
      bootitisbad = true -- baad, exceeded max 6 substrings
      break
    end--if
    tabfromtit[numcxeloj0x5x] = string.sub(streniroat,1,(varfiinndd-1))
    streniroat = string.sub(streniroat,(varfiinndd+2),-1)
    numcxeloj0x5x = numcxeloj0x5x + 1 -- NOT valid on exit from loop
  end--while

  if (bootitisbad) then
    tabeliroat[9] = true -- baad
  else
    numcxeloj0x5x = 0
    numwithholes = 0
    while true do
      if (numcxeloj0x5x==6) then
        break
      end--if
      if tabcolspan[numcxeloj0x5x] then -- fill in only if used
        if (tabfromtit[numwithholes]) then -- take only if available
          tabeliroat[numcxeloj0x5x] = tabfromtit[numwithholes] -- custom
          numwithholes = numwithholes + 1
        else
          tabeliroat[numcxeloj0x5x] = tabdefaultat[numcxeloj0x5x] -- default
        end--if
      end--if tabcolspan[numcxeloj0x5x] then
      numcxeloj0x5x = numcxeloj0x5x + 1 -- INC this irrespective of tabcolspan
    end--while
  end--if

  return tabeliroat

end--function lfhatmergetab

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

-- Local function LFHPARSEMAP

-- Parse and map the wikitext and record findings.

-- Input  : * strwiki7text

-- Output : * tabmy3table -- nested: [0] findings | [1] main map |
--                           [2] boundary map

-- We can avoid maintaining a stack since we count wikilinks only and
-- give a f**k about template calls that could be mixed with them.

-- There is a problem with markers like "[[" and "]]". If they are nested
-- and adjacent like "[[[[" or "]]]]" then we must prevent the same char
-- from being shared among two hits.

local function lfhparsemap (strwiki7text)

  local tabmy3table = {} -- nested
  local tabmainmap = {} -- risk of type "nil" inside
  local tabbouncer = {} -- tristate (1 2 type "nil"), many type "nil" inside
  local tabfindings = {[0]=true,[1]=false,[2]=false,[3]=false} -- heterogeneous
  local numwiki7len   = 0      -- also for embedded func -- upper limit
  local numlower7li   = 1      -- only for embedded func -- lower limit
  local numparseindex = 1      -- also for embedded func -- ONE-based
  local numareatype   = 0
  local numyyyhitlng  = 0      -- also for embedded func -- length of hit
  local numareabykp   = 0      -- only types 0...9 can be backuped
  local nummaxdepth   = 0      -- 97 under | 98 over
  local boogotst4     = false  -- strip 4 listed #A14 #M04
  local boogotst5     = false  -- strip all other #A15 #M05
  local boogotshl     = false  -- !!!FIXME!!! not yet evaluated
  local boogotpre     = false
  local boogotnwk     = false
  local boogotref     = false
  local boogotkom     = false
  local bootreaspace  = false  -- !!!FIXME!!! not yet evaluated

    -- embedded function ie procedure, no output value, includes current char
    -- IN PRARAM: stryyyfind
    -- IN SHARED: strwiki7text numwiki7len numlower7li
    --            numparseindex (ONE-based)
    -- UT SHARED: numyyyhitlng
    local function compareforwards (stryyyfind)
      local booyyyfaund = false -- preASSume NOT found
      numyyyhitlng = string.len(stryyyfind) -- searched string
      if (numyyyhitlng>=1) then
        if ((numparseindex>=numlower7li) and ((numparseindex+numyyyhitlng-1)<=numwiki7len)) then
          booyyyfaund = (string.sub(strwiki7text,numparseindex,(numparseindex+numyyyhitlng-1))==stryyyfind)
        end--if
      end--if
      if (not booyyyfaund) then
        numyyyhitlng = 0
      end--if
    end--function compareforwards

    -- embedded function ie procedure, no output value, fill
    -- IN PRARAM: numyyydx (ONE-based)     -- numyyyaratyp
    --            triyyybgen (1 beg 2 end) -- numyyysize
    -- CH SHARED: tabbouncer tabmainmap
    local function filldesti (numyyydx, numyyyaratyp, triyyybgen, numyyysize)
      if (numyyysize~=0) then
        if (triyyybgen==1) then
          tabbouncer [numyyydx] = 1 -- begin flag
        end--if
        while true do -- genuine loop, at least ONE iteration
          tabmainmap [numyyydx] = numyyyaratyp
          numyyysize = numyyysize - 1
          if (numyyysize==0) then
            break
          end--if
          numyyydx = numyyydx + 1 -- do this AFTER exit check
        end--while genuine loop
        if (triyyybgen==2) then
          tabbouncer [numyyydx] = 2 -- end flag
        end--if
      end--if (numyyysize~=0) then
    end--function filldesti

  numwiki7len = string.len(strwiki7text) -- quasi-const
  numlower7li = 1 -- const

  while true do -- outer genuine loop over wikitext, char after char

    if (numparseindex>numwiki7len) then -- ONE-based
      break -- completed
    end--if
    numyyyhitlng = 0 -- preASSume no hit for every position

    while true do -- fake loop

      if (numareatype==15) then -- !!!FIXME!!! 14 not yet supported
        compareforwards ('QINU'..string.char(96,34,39,127))
        if (numyyyhitlng~=0) then
          filldesti (numparseindex, numareatype, 2, numyyyhitlng) -- old type
          numareatype = numareabykp -- after filling
          break
        end--if
      end--if (numareatype==15) then

      if (numareatype==22) then
        compareforwards ('</pre>')
        if (numyyyhitlng~=0) then
          filldesti (numparseindex, numareatype, 2, numyyyhitlng) -- old type
          numareatype = numareabykp -- after filling
          break
        end--if
      end--if (numareatype==22) then

      if (numareatype==23) then
        compareforwards ('</nowiki>')
        if (numyyyhitlng~=0) then
          filldesti (numparseindex, numareatype, 2, numyyyhitlng) -- old type
          numareatype = numareabykp -- after filling
          break
        end--if
      end--if (numareatype==23) then

      if (numareatype==27) then
        compareforwards ('-->')
        if (numyyyhitlng~=0) then
          filldesti (numparseindex, numareatype, 2, numyyyhitlng) -- old type
          numareatype = numareabykp -- after filling
          break
        end--if
      end--if (numareatype==27) then

      if (numareatype>=10) then
        break -- no more chance for a hit at this position
      end--if

      compareforwards (']]')
      if (numyyyhitlng~=0) then
        filldesti (numparseindex, numareatype, 2, numyyyhitlng) -- old type
        if (numareatype==0) then
          nummaxdepth = 97 -- special value for underflow, recoverable
        else
          numareatype = numareatype - 1 -- inclusive limit from ONE to ZERO
        end--if
        break
      end--if

      compareforwards ('[[')
      if (numyyyhitlng~=0) then
        if (numareatype==9) then
          nummaxdepth = 98 -- special value for overflow, FATAL
          numareatype = 0 -- avoid #M03 below
        else
          numareatype = numareatype + 1 -- inclusive limit from 8 to 9
          nummaxdepth = math.max(nummaxdepth,numareatype)
          filldesti (numparseindex, numareatype, 1, numyyyhitlng) -- new type
        end--if
        break
      end--if

      compareforwards (string.char(127,39,34,96)..'UNIQ') -- &#127;'"&#96;UNIQ--pre-00000002-QINU&#96;"'&#127;
      if (numyyyhitlng~=0) then
        numareabykp = numareatype
        numareatype = 15 -- #A14 #A15 for now  !!!FIXME!!!
        boogotst4 = true -- #M04 never to false again
        boogotst5 = true -- #M05 never to false again
        filldesti (numparseindex, numareatype, 1, numyyyhitlng) -- new type
        break
      end--if

      compareforwards ('<pre>')
      if (numyyyhitlng~=0) then
        numareabykp = numareatype
        numareatype = 22
        boogotpre = true -- never to false again
        filldesti (numparseindex, numareatype, 1, numyyyhitlng) -- new type
        break
      end--if

      compareforwards ('<nowiki>')
      if (numyyyhitlng~=0) then
        numareabykp = numareatype
        numareatype = 23
        boogotnwk = true -- never to false again
        filldesti (numparseindex, numareatype, 1, numyyyhitlng) -- new type
        break
      end--if

      compareforwards ('<!--')
      if (numyyyhitlng~=0) then
        numareabykp = numareatype
        numareatype = 27
        boogotkom = true -- never to false again
        filldesti (numparseindex, numareatype, 1, numyyyhitlng) -- new type
        break
      end--if

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

    if (nummaxdepth==98) then
      break -- FATAL
    end--if

    if (numyyyhitlng==0) then
      filldesti (numparseindex, numareatype, 0, 1) -- current type, no hit
      numparseindex = numparseindex + 1 -- ONE-based, no hit
    else
      numparseindex = numparseindex + numyyyhitlng -- ONE-based, YES hit
    end--if

  end--while outer genuine loop over wikitext, char after char

  if (numparseindex<=numwiki7len) then -- ONE-based
    filldesti (numparseindex, 255, 0, (numwiki7len-numparseindex+1)) -- fill up on FATAL
  end--if

  while true do -- lower fake loop

    if ((numareatype>=16) and (numareatype<=30)) then -- do NOT incl 14 15 her
      tabfindings [1] = true -- #M01 no other discovery possible
      break -- to join mark
    end--if
    if ((numareatype==14) or (numareatype==15)) then
      tabfindings [2] = true -- #M02 no other discovery possible
      break -- to join mark
    end--if
    if (numareatype~=0) then
      tabfindings [3] = true -- #M03 no other discovery possible
      break -- to join mark
    end--if
    tabfindings [20] = nummaxdepth -- #M20 value 97 or 98 is an error
    if (nummaxdepth>=10) then
      break -- to join mark -- no other discovery possible for both 97 and 98
    end--if
    tabfindings [ 4] = boogotst4    -- #M04  !!!FIXME!!! does not work yet fully 4 vs 5
    tabfindings [ 5] = boogotst5    -- #M05
    tabfindings [ 6] = boogotshl    -- #M06  !!!FIXME!!! does not work yet
    tabfindings [ 8] = boogotpre    -- #M08
    tabfindings [ 9] = boogotnwk    -- #M09
    tabfindings [10] = boogotref    -- #M10  !!!FIXME!!! does not work yet
    tabfindings [11] = boogotkom    -- #M11
    tabfindings [22] = bootreaspace -- #M22  !!!FIXME!!! does not work yet

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

  tabmy3table = {[0]=tabfindings,[1]=tabmainmap,[2]=tabbouncer}

  return tabmy3table

end--function lfhparsemap

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

-- Local function LFHSPLITHIDDEN

-- Split one string into two strings based on "*HIDDEN*".

-- Input  : * stranontextin  -- one complete call without the outer
--                              enclosing {{ ... }}, or one or several
--                              calls each with its private {{ ... }}

-- Output : * numstratus -- ZERO or #E48 or #E49
--          * strekspandu
--          * strmontru

-- Hidden parameters must not contain [[ ]] {{ (note that "}}" closes
-- a call), violation gives #E48. "*HIDDEN*" must NOT occur without
-- preceding wall, violation gives #E49.

-- For "nsm=2" there can be several calls, each terminated by "}}".

-- Theoretically hidden parameters are on depth ZERO if no enclosing {{ ... }}
-- is included, and on depth ONE if it is there when using "nsm=2", but we do
-- NOT evaluate the depth for {{ ... }} at all when mapping, and do NOT use
-- the map of the incoming anonymous parameter here.

-- Instead we do a very dumb parsing delimited by "|" and "}}" (not by "{{").
-- On every "*HIDDEN*" we skip back until preceding wall (inclusive), and
-- forward until following wall or "}}" or end of string (exclusive). We
-- leave every legal content pass, but do not catch every faulty content.

-- Legal:
-- ax|pagenameoverridetestonly*HIDDEN*Pohon
-- ax|pagenameoverridetestonly*HIDDEN*Pohon|nodog*HIDDEN*true
-- ax|pagenameoverridetestonly*HIDDEN*Pohon|text={{#ifexist:sillypage|COOL}}
-- ax|text={{#ifexist:sillypage|COOL}}|pagenameoverridetestonly*HIDDEN*Pohon|nodog*HIDDEN*true
-- {{ax|pagenameoverridetestonly*HIDDEN*Pohon}}
-- {{ax|pagenameoverridetestonly*HIDDEN*Pohon}}{{bx|pagenameoverridetestonly*HIDDEN*Pohon}}
-- <br>{{ax|pagenameoverridetestonly*HIDDEN*Pohon}}{{bx|pagenameoverridetestonly*HIDDEN*Pohon}}<br>

-- Faulty:
-- pagenameoverridetestonly*HIDDEN*Pohon
-- ax|pagenameoverridetestonly*HIDDEN*[[File:Pohon.png|10000px]]|nodog*HIDDEN*true
-- ax|pagenameoverridetestonly*HIDDEN*{{#ifexist:sillypage|COOL}}|nodog*HIDDEN*true
-- ax|pagenameoverridetestonly*HIDDEN*XX{{#ifexist:sillypage|COOL}}|nodog*HIDDEN*true

-- * strategy:
--   * assign boohad3wall to false
--   * assign boo3hidden boo3bracket to false
--   * walk through text char after char accumulating single chars
--     into one temporary string
--     * on every wall "|" or "}}" or end of input string
--       * if both boo3hidden boo3bracket give #E48
--       * if boo3hidden is true dump from one temporary string
--         only into the expansion output
--       * else dump from one temporary string into both output
--         strings
--       * empty the temporary string and copy the "|" or "}}" to it
--       * assign boohad3wall to true on wall but NOT to false on "}}"
--       * reassign both boo3hidden boo3bracket to false
--     * if *HIDDEN* is found then
--       * if boohad3wall is false give #E49
--       * skip it in the source
--       * drop "=" into the temporary string
--       * assign boo3hidden to true
--     * if doublebracket if found then assign boo3bracket to true but no skip
--     * if nothing else then copy single char

local function lfhsplithidden (stranontextin, tabanonmainmap)

  local strekspandu = ''
  local strmontru   = ''
  local strakkumu   = ''

  local numstratus  = 0 -- preASSume innocence
  local numin3len   = 0
  local numin3dex   = 0
  local num3kk3kk   = 0
  local numre3serve = 0

  local boohad3wall   = false
  local boo3hidden    = false
  local boo3bracket   = false
  local bootwo3same   = false
  local booprizorgita = false

  numin3len = string.len(stranontextin)

  while true do
    numre3serve = numin3len - numin3dex -- can be ZERO but not less
    if (numre3serve~=0) then
      num3kk3kk = string.byte(stranontextin,(numin3dex+1),(numin3dex+1))
    end--if
    bootwo3same = ((numre3serve>=2) and (string.byte(stranontextin,(numin3dex+2),(numin3dex+2))==num3kk3kk))
    booprizorgita = false
    if ((numre3serve==0) or (num3kk3kk==124) or (bootwo3same and (num3kk3kk==125))) then
      if (boo3hidden and boo3bracket) then
        numstratus = 48 -- #E48 invalid doublebrackets in hidden
        break
      end--if
      if (not boo3hidden) then
        strmontru = strmontru .. strakkumu -- dump to both
      end--if
      strekspandu = strekspandu .. strakkumu
      if (numre3serve==0) then
        break -- done !!!
      end--if
      if (num3kk3kk==124) then
        strakkumu = '|'
        boohad3wall = true
        numin3dex = numin3dex + 1
      end--if
      if (bootwo3same and (num3kk3kk==125)) then
        strakkumu = '}}'
        numin3dex = numin3dex + 2
      end--if
      boo3hidden = false  -- & evaluated per parameter
      boo3bracket = false -- &
      booprizorgita = true -- do NOT copy a char this time
    end--if
    if ((not booprizorgita) and (numre3serve>=8)) then
      if (string.sub(stranontextin,(numin3dex+1),(numin3dex+8))=='*HIDDEN*') then
        if (not boohad3wall) then
          numstratus = 49 -- #E49 *HIDDEN* without preceding wall "|"
          break
        end--if
        numin3dex = numin3dex + 8 -- reserve soon can be ZERO but not less
        strakkumu = strakkumu .. '='
        boo3hidden = true
        booprizorgita = true -- do NOT copy a char this time
      end--if
    end--if
    if ((not booprizorgita) and bootwo3same) then
      if ((num3kk3kk==91) or (num3kk3kk==93) or (num3kk3kk==123)) then
        boo3bracket = true -- YES DO copy a char below
      end--if
    end--if
    if (not booprizorgita) then
      strakkumu = strakkumu .. string.char(num3kk3kk)
      numin3dex = numin3dex + 1
    end--if
  end--while

  if (numstratus~=0) then
    strekspandu = ''
    strmontru = ''
  end--if

  return numstratus, strekspandu, strmontru

end--function lfhsplithidden

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

-- Local function LFHTABLEWITHMAP

-- Visualize the map by brewing a HTML table from wikitext plus parallel map.

-- Input  : * stralltextm -- must NOT be empty
--          * tabthismap -- nested: [0] findings | [1] main map |
--                          [2] boundary map -- MUST MATCH THE STRING !!!

-- Depends on functions :
-- [G] lfgtestnum lfgtestuc lfgtestlc lfgis62safe

-- Depends on constants :
-- * table "contabbk" for background colors

-- length 01...08 -> width  8 & 1 row
-- length 09...18 -> width  6 & 2 or 3 rows
-- length 19...64 -> width  8 & 3...8 rows
-- length >=65    -> width 10 & >=6 rows

local function lfhtablewithmap (stralltextm, tabthismap)

  local strvisualizedmap = ''
  local strhex6color = ''
  local stronecell = ''

  local numlencofwikitext = 0
  local numwidthincells = 0 -- 6 or 8 or 10
  local numasciizindex = 0 -- ZERO-based
  local numxcoordinate = 0 -- ZERO-based
  local numkodchar = 0
  local numfrommap = 0
  local tribegiend = 0

  numlencofwikitext = string.len(stralltextm)
  numwidthincells = 8 -- preASSume
  if ((numlencofwikitext>=9) and (numlencofwikitext<=18)) then
    numwidthincells = 6
  end--if
  if (numlencofwikitext>=65) then
    numwidthincells = 10
  end--if

  while true do -- genuine loop over double content
    if ((numasciizindex==numlencofwikitext) or (numxcoordinate==numwidthincells)) then
      strvisualizedmap = '<tr>' .. strvisualizedmap .. '</tr>'
    end--if
    if (numasciizindex==numlencofwikitext) then
      break -- done whole table
    end--if
    if (numxcoordinate==numwidthincells) then
      numxcoordinate = 0 -- done row only
    end--if
    numkodchar = string.byte(stralltextm,(numasciizindex+1),(numasciizindex+1))
    numfrommap = tabthismap[1][numasciizindex+1] -- get area type
    tribegiend = tabthismap[2][numasciizindex+1] -- get "B" or "E" maybeee
    strhex6color = contabbk[numfrommap] or "58" -- dark grey if bad
    if (string.len(strhex6color)==2) then
      strhex6color = strhex6color .. strhex6color .. strhex6color -- 2->6 digits ;-)
    end--if
    stronecell = '<td style="border:1px solid #000000;padding:0 0.2em 0 0.2em;background:#' .. strhex6color .. ';">'
    if (tribegiend==1) then
      stronecell = stronecell .. '<b>B</b>&nbsp;'
    end--if
    if (tribegiend==2) then
      stronecell = stronecell .. '<b>E</b>&nbsp;'
    end--if
    stronecell = stronecell .. tostring(numfrommap) .. ':<br>' .. tostring(numkodchar) .. '&nbsp;'
    if (lfgis62safe(numkodchar)) then
      stronecell = stronecell .. string.char(numkodchar) .. '</td>'
    else
      if ((numkodchar>=33) and (numkodchar<=126)) then
        stronecell = stronecell .. '&#' .. tostring(numkodchar) .. ';</td>'
      else
        stronecell = stronecell .. '?</td>'
      end--if
    end--if (lfgis62safe(numkodchar)) else
    strvisualizedmap = strvisualizedmap .. stronecell
    numasciizindex = numasciizindex + 1
    numxcoordinate = numxcoordinate + 1
  end--while

  strvisualizedmap = '<table style="margin:auto;">' .. strvisualizedmap .. '</table>' -- done whole table

  return strvisualizedmap

end--function lfhtablewithmap

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

-- Local function LFHREPORTFINDINGS

-- Input  : * tabhugemap -- theoretically ZERO-based, only findings

-- Output : * streirreport -- empty impossible

-- Depends on functions :
-- [H] lfhtabletohtmllista
-- [I] lfiexplain
-- [N] lfnumto2digit
-- [E] mathisintrange mathdiv mathmod

-- Depends on constants :
-- * table "contabxxplain" with string keys

local function lfhreportfindings (tabhugemap)

  local varfinding = 0
  local tabtemp77q = {}
  local numconsecutiveindex = 1 -- ZERO valid but skip #M00
  local numdestindex = 0
  local streirreport = ''

  while true do -- genuine loop 1...40
    if (numconsecutiveindex==41) then
      break
    end--if
    varfinding = tabhugemap [numconsecutiveindex] -- type "nil" or "boolean" or "number"
    if (varfinding) then
      tabtemp77q[numdestindex] = lfiexplain('#M' .. lfnumto2digit(numconsecutiveindex)) .. ':&nbsp;' .. tostring(varfinding)
      numdestindex = numdestindex + 1
    end--if
    numconsecutiveindex = numconsecutiveindex + 1
  end--while

  streirreport = 'Mapping done, '
  if (numdestindex==0) then
    streirreport = streirreport .. 'no finddings'
  end--if
  if (numdestindex==1) then -- <br> needed
    streirreport = streirreport .. 'finding:<br>' .. lfhtabletohtmllista(tabtemp77q,true) .. '<br>'
  end--if
  if (numdestindex>=2) then -- no <br> here
    streirreport = streirreport .. 'findings:' .. lfhtabletohtmllista(tabtemp77q,true)
  end--if

  return streirreport

end--function lfhreportfindings

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

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

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

-- Local function LFWISTITLESAFE

-- Check whether title is safe (valid, not cat, not file,
-- no interwiki, standard form) returning a boolean.

-- Input  : * strsftitle

-- Output : * bootitlecxsafe -- true if safe (false also
--                              for non-string and invalid title)

-- Function "mw.title.new":
-- * returns type "nil" on invalid title string, and crashes if
--   input type is not string
-- * is NOT expensive (for our use) and works for non-existent titles too
-- * some useful fields:
--   * ".interwiki" is always a string, empty if no interwiki included
--   * ".namespace" is always a number, values <0 and 6 and 14
--     are unsafe ("Media:" gives -2)
--   * ".nsText" only bare ns prefix such as "Mall"
--   * ".prefixedText" {{FULLPAGENAME}} + maybe interwiki; veryfullpagename
--     including ns prefix and interwiki, a strictly standardized
--     form of title:
--     * adjusts letter case
--     * replaces underscores by spaces
--     * "Category:" becomes "Kategori:" in some languages
--     * removes extraneous spaces around colon ":"
--   * ".fullText" {{FULLPAGENAME}} + maybe interwiki + HTML section "#"

local function lfwistitlesafe (strsftitle)
  local tabtitleobject = {}
  local numnsoftheobject = 0
  local bootitlecxsafe = false -- preASSume guilt
  if (type(strsftitle)=='string') then
    tabtitleobject = mw.title.new(strsftitle) -- type "nil" for invalid
    if (type(tabtitleobject)=='table') then
      numnsoftheobject = tabtitleobject.namespace
      bootitlecxsafe = ((tabtitleobject.interwiki=='') and (numnsoftheobject>=0) and (numnsoftheobject~=6) and (numnsoftheobject~=14) and (tabtitleobject.prefixedText==strsftitle))
    end--if
  end--if
  return bootitlecxsafe
end--function lfwistitlesafe

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

-- Local function LFWGETNSOFTITLE

local function lfwgetnsoftitle (strnstitle)
  local tabtitlensobject = {}
  local numnsoftheobjk = 65535 -- preASSume guilt
  if (type(strnstitle)=='string') then
    tabtitlensobject = mw.title.new(strnstitle) -- type "nil" for invalid
    if (type(tabtitlensobject)=='table') then
      numnsoftheobjk = tabtitlensobject.namespace
    end--if
  end--if
  return numnsoftheobjk
end--function lfwgetnsoftitle

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

-- Local function LFWSPLIT3TITLE

local function lfwsplit3title (strsptitle)
  local tabtitlespobject = {}
  local strunfull = '' -- without prefix
  local strnstoxt = ''
  local numnsdetheobjk = 65535 -- preASSume guilt
  if (type(strsptitle)=='string') then
    tabtitlespobject = mw.title.new(strsptitle) -- type "nil" for invalid
    if (type(tabtitlespobject)=='table') then
      numnsdetheobjk = tabtitlespobject.namespace -- number
      if (numnsdetheobjk<=2000) then
        strunfull = tabtitlespobject.text -- string
        strnstoxt = tabtitlespobject.nsText -- string, can be empty, no colon
      else
        numnsdetheobjk = 65535
      end--if
    end--if
  end--if
  return numnsdetheobjk, strunfull, strnstoxt
end--function lfwsplit3title

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

-- Local function LFWBREW3URL

-- Brew httplink to current project from title + http parameters + visible
-- text, making it look like an ordinary wikilink.

-- Input  : * arxinp4
--          * strwikipage -- title ie {{FULLPAGENAME}} or
--                           for example "Special:WhatLinksHere"
--          * strhttpparam -- for example "hidetrans=1&hidelinks=1" or empty
--          * strvisible -- for example "do NOT click here" or empty
--          * boozordinary -- "true" to look like an ordinary wikilink,
--                            "false" if this is undesirable or done outside

-- URL structure:
-- * https://en.wiktionary.org/wiki/Category:Vulgarities
-- * https://en.wiktionary.org/wiki/Category:Vulgarities?action=purge
-- * https://en.wiktionary.org/w/index.php?title=Category:Vulgarities
-- * https://en.wiktionary.org/w/index.php?title=Category:Vulgarities&action=purge
-- * https://en.wiktionary.org/wiki/Special:AllPages
-- * https://en.wiktionary.org/wiki/Special:AllPages?namespace=5
-- * https://en.wiktionary.org/wiki/Special:WhatLinksHere/Template:Bugger
-- * https://en.wiktionary.org/wiki/Special:WhatLinksHere/Template:Bugger?hidetrans=1&hidelinks=1
-- * https://en.wiktionary.org/w/index.php?title=Special:WhatLinksHere/Template:Bugger&hidetrans=1&hidelinks=1
-- * https://en.wiktionary.org/w/index.php?title=Special:WhatLinksHere&target=Template:Bugger&hidetrans=1&hidelinks=1

-- Only 4 ways to brew a URL:
-- * {{canonicalurl:{{FULLPAGENAME}}|action=purge}}
--       we use this one via ":preprocess", will work on any project
-- * {{SERVER}}{{SCRIPTPATH}}/index.php?title={{urlencode:{{FULLPAGENAME}}}}&action=purge
--       unnecessarily complicated
-- * LUA field "canonicalUrl" or "fullUrl" on "mw.uri" or "title object"
--       complicated and dubious
-- * manually
--       where to take project URL from ?? plus routine would land in [I] then

-- <span class="plainlinks">...</span>

local function lfwbrew3url (arxinp4, strwikipage, strhttpparam, strvisible, boozordinary)

  local strourresult = ''

  if (strhttpparam~='') then
    strwikipage = strwikipage .. '|' .. strhttpparam
  end--if
  strourresult = arxinp4:preprocess ('{{canonicalurl:' .. strwikipage .. '}}')
  if (strvisible=='') then
    strourresult = '[' .. strourresult .. ' ' .. strourresult .. ']'
  else
    strourresult = '[' .. strourresult .. ' ' .. strvisible .. ']'
  end--if
  if (boozordinary) then
    strourresult = '<span class="plainlinks">' .. strourresult .. '</span>'
  end--if
  return strourresult

end--function lfwbrew3url

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

-- Local function LFWEATREFERENCES

-- Input  : * arxinp5
--          * strgroupname -- digit "1" for default group

local function lfweatreferences (arxinp5, strgroupname)
  local strzxcvbn = '<br><b>References '
  if (strgroupname=='1') then
    strzxcvbn = strzxcvbn .. 'main group'
  else
    strzxcvbn = strzxcvbn .. 'group "' .. strgroupname .. '"'
  end--if
  strzxcvbn = strzxcvbn .. '&nbsp;:</b>'
  if (strgroupname=='1') then
    strzxcvbn = strzxcvbn .. arxinp5:extensionTag('references')
  else
    strzxcvbn = strzxcvbn .. arxinp5:extensionTag('references','',{group=strgroupname})
  end--if
  return strzxcvbn
end--function lfweatreferences

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

-- Local function LFWIFEXISIM  !!!FIXME!!! use the tightfisted one

-- 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 lfwifexisim (strpgnamsi)
  local boomemangada = false
  local metaa = 0
  metaa = mw.title.new (strpgnamsi) -- 1 param
  boomemangada = metaa.exists -- expensive here
  return boomemangada
end--function lfwifexisim

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

---- VERY HIGH LEVEL FUNCTIONS [Y] ----

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

-- Local function LFYSPLITKAT

-- Split wikitext into two parts, namely cat insertions vs originally
-- visible remainder, format those cat insertions into a list, and
-- maybe brew an extra part too.

-- Input  : * strwikitextin
--          * tabparsmap -- nested: [0] findings | [1] main map |
--                          [2] boundary map -- MUST MATCH THE STRING !!!
--          * boonohynts
--          * boobypass -- allow bypassing "hlt=1"

-- Output : * tabsplout -- 7 elements [0] str | [1] str | [2] str
--                         [3] num err | [4] num | [5] num dupes | [6] bypass

-- Depends on functions :
-- [W] lfwsplit3title
-- [H] lfhtabletohtmllista
-- [I] lfikatpaldigu lfigetleftright

-- Depends on constants :
-- * 3 strings (plain text) "constrca3d" "constrca4d" "constrca5d"

-- This is our holy core ;-)

-- We take one string and produce 2 strings, one of them for the cat
-- insertions, and the other one for the remaining ie originally visible
-- content. We walk forwards through string "strwikitextin" char after char
-- from begin of string to the end and take care of all "[[" (maybe a cat
-- insertion that needs to be cut out) and of "<pre>" (#A22, needs special
-- encoding of the affected area). All found cat insertions (except dupes
-- ie same name) are placed into two temporary tables (name and hint) and
-- skipped (also dupes, including both [[ and ]]) from copying as the main
-- visible content. Subsequently we walk through that table and postprocess
-- all those cat insertions. Finally we format them with help of
-- "lfhtabletohtmllista" into "strkattfinal".

-- A template can produce same cat insertion multiple times. This is not
-- optimal but possible. Even worse, the repetitive cat insertions may vary in
-- the sorting hint, or syntax details such as translated vs non-translated,
-- letter case or extra spaces. We grant a dupe if the names are identical,
-- irrespective of ns prefixes and hints. A target template producing
-- differing dupes would be highly dubious.

-- Visible wikilinks on level 2 are tolerated:
-- [[File:Shoplifting.PNG|thumb|a [[shoplifter]] in [[action]]]]
-- Cat insertions on level 2 are not, they give #S24:
-- [[File:Shoplifting.PNG|thumb|a shoplifter in action [[Category:Crime]]]]
-- We must also defend (with same #S24 on violation) against ordinary
-- wikilink inside a cat insertion:
-- [[Category:Crime|I [[be|am]] criminal]]

-- On a level 1...9 we skip from copying and collect instead into 2
-- destinations parallelly, namely "tabcollectlevel" separated by levels,
-- and into "strflatcoll" in a flat manner. At every "[[" the level is
-- increased (done by previous mapping, above 9 impossible). At level ONE
-- we empty "strflatcoll" and assign "boowasnested" to false. At every
-- level 2...9 we set "boowasnested" to true. It remains true when we
-- later drop back to level ONE. At every "]]" we close a level and
-- check title from "tabcollectlevel" invoking "lfwsplit3title" caring
-- about possible ":" whether it's a cat insertion assigning "boocathere".
-- A cat on level different from ONE gives immediate abort with #S24. When
-- closing level ONE going back to ZERO and
-- cat and "boowasnested" true then we abort with #S24, else ("boowasnested"
-- false) we try to store the cat, note that "tabcollectlevel[1]" and
-- "strflatcoll" had same content in that situation (sinking from ONE
-- to ZERO, cat here, and "boowasnested" false). If no cat then we dump
-- the accumulated text inside [[ ]] into "strmainout" instead.

-- The upper loop uses 4 tables, 3 of them are parallel and limited
-- to 12 cat:s (names, hints, bypass flags). A bypassed cat must look like:
-- <span title="bypass-pate">[[Kategori:Tracking-ubx-text-too-long]]</span>
-- Only if parameter "boobypass" requests it, we check for bypassed cat:s.
-- Only if bypass and not dupe, we count it in "booisbypas".
-- Up to 4 such are added into "strallbypass" standardized with possible hint.  !!!FIXME!!! imp incomplete

-- Sorting hints are displayed after the categories. For
-- example "[[Category:Crap|tujuh]]" gives visible text
-- "Category:<br>Crap| (<small>tujuh</small>)" where the visible wall must
-- be encoded (there is one more wall before that is NOT encoded). Spaces
-- in the hint are converted to underscores "_". The brackets make              !!!FIXME!!! underscores and why this ??
-- hints with spaces (like "[[Category:Crap| tujuh]]") more visible.

-- The cat insertions are formatted here (empty result impossible), whereas
-- the remaining ie originally visible content is raw (empty result possible).

local function lfysplitkat (strwikitextin, tabparsmap, boonohynts, boobypass)

  local tabcollectlevel = {} -- for upper loop, collect all wikilinks [[]]
  local tabnodupect = {} -- for upper loop, all cat names stored
  local tabtmpcatnm = {} -- for upper & lower loop, max 12 cat:s
  local tabtmpcathi = {} -- for upper & lower loop, max 12 cat:s
  local tabtmpcatby = {} -- for upper & lower loop, only "true", max 12 cat:s
  local tabsplout = {} -- 3 strings plus 4 integers inside, our final result
  local strkattfinal = '' -- for lower loop, all cat:s formatted -- for caller
  local strmainout = '' -- for upper loop, never reset -- for caller
  local strallbypass = '' -- for caller
  local strmy4left = '' -- for upper loop, split on wall "|"
  local strmy4rajt = '' -- for upper loop, split on wall "|"
  local strflatcoll = '' -- for upper loop, single cat, regularly reset
  local strwancxar = '' -- for upper loop
  local strtitfrom3sub = '' -- for upper loop, unfull title
  local strnstfrom3sub = '' -- for upper loop, nstext
  local strtajtl = '' -- for lower loop
  local strhiint = '' -- for lower loop
  local strvisible = '' -- for lower loop
  local numsperrorko = 0 -- split status / error code -- for caller
  local numkcount = 0 -- found cat:s excl dupes, count all -- for caller
  local numdupes = 0 -- number of dupes -- for caller
  local numbypass = 0 -- number of bypasses -- for caller
  local numwallstatus = 0 -- for upper loop, split on wall "|"
  local numnsfrom3sub = 0 -- for upper loop
  local numstrilen = 0 -- for upper loop
  local numchariix = 0 -- for upper loop, current
  local numtypearr = 0 -- for upper loop, current
  local numbounbb = 0 -- for upper loop, 1 for "B"
  local numbounee = 0 -- for upper loop, 2 for "E"
  local numreadonepos = 1 -- for upper loop -- ONE-based
  local numcnntcat = 0 -- for lower loop
  local booisbypass = false -- for upper loop
  local booinsidewili = false -- for upper loop
  local boowasnested = false -- for upper loop
  local boocathere = false -- for upper loop
  local booskiptwo = false -- for upper loop

  numstrilen = string.len (strwikitextin)

  if (tabparsmap[0][6] or tabparsmap[0][9] or tabparsmap[0][12] or tabparsmap[0][22]) then
    numsperrorko = 20 -- #S20 split NOT attempted due to other #M06 #M09 ...
  end--if
  if (tabparsmap[0][1] or tabparsmap[0][2] or tabparsmap[0][3] or (tabparsmap[0][20]>2)) then
    numsperrorko = 21 -- #S21 split NOT attempted due to nesting #M01 #M02 #M03 #M20>2
  end--if

  while true do -- upper genuine loop -- index is "numreadonepos"

    if ((numreadonepos>numstrilen) or (numsperrorko>=20)) then
      break -- no further hits possible or already f**ked
    end--if

    booskiptwo = false -- preASSume again for every iteration
    numbounee = 0 -- preASSume, worx even for "]]" at end of text
    numchariix = string.byte(strwikitextin,numreadonepos,numreadonepos)
    numtypearr = tabparsmap[1][numreadonepos] -- current type
    numbounbb = tabparsmap[2][numreadonepos] -- maybe "B" from current
    if (numreadonepos<numstrilen) then
      numbounee = tabparsmap[2][numreadonepos+1] -- maybe "E" on next
    end--if

    if (booinsidewili and (numbounee==2)) then -- "]]"
      numwallstatus, strmy4left, strmy4rajt = lfigetleftright (tabcollectlevel[numtypearr])
      tabcollectlevel[numtypearr] = nil -- kick after use
      if (numwallstatus==77) then
        numsperrorko = 3 -- #S03 wall + empty right, accept with complaint
      end--if
      if ((numwallstatus==75) or (numwallstatus==76)) then
        numsperrorko = 23 -- #S23 all empty or empty left, give up
        break
      end--if
      if (numwallstatus==79) then
        numsperrorko = 2 -- #S02 junk spaces, accept with complaint
      end--if
      numnsfrom3sub, strtitfrom3sub, strnstfrom3sub = lfwsplit3title (strmy4left)
      if (numnsfrom3sub>1024) then
        numsperrorko = 25 -- invalid title (empty "[[Category:]]" etc) #S25
        break -- give up
      end--if
      boocathere = ((numnsfrom3sub==14) and (string.byte(strmy4left,1,1)~=58))
      if (boocathere and (numtypearr~=1)) then
        numsperrorko = 24 -- #S24 cat insertion on level different from ONE
        break -- give up
      end--if
      if (numtypearr==1) then -- down from ONE to ZERO here
        if (boocathere) then
          if (boowasnested) then
            numsperrorko = 24 -- #S24 link inside cat insertion is bad too
            break -- give up
          else
            if (tabnodupect[strtitfrom3sub]) then
              numdupes = numdupes + 1 -- already in
            else
              tabnodupect[strtitfrom3sub] = true -- store it
              if (numkcount<=11) then
                tabtmpcatnm [numkcount] = strtitfrom3sub -- table ZERO-based
                tabtmpcathi [numkcount] = strmy4rajt -- table ZERO-based
              end--if
              numkcount = numkcount + 1 -- ZERO-based for return & table, NOT loop
            end--if (tabnodupect[strtitfrom3sub]) then
          end--if (boowasnested) else
        else
          strmainout = strmainout .. '[[' .. strflatcoll .. ']]' -- dump all the skipped stuff
        end--if (boocathere) else
        strflatcoll = '' -- decommission after use
        booinsidewili = false -- copy char:s into "strmainout" again
      end--if (numtypearr==1) then
      booskiptwo = true
    end--if

    if ((numbounbb==1) and (numtypearr<=9)) then -- "[["
      if (numtypearr==1) then
        booinsidewili = true -- do NOT copy char:s into "strmainout" for now
        boowasnested = false -- will get true on level 2, false only at ZERO
        strflatcoll = '' -- reset now and begin collecting here, incl "[["
      else
        boowasnested = true
      end--if
      tabcollectlevel[numtypearr] = '' -- reset on every level
      booskiptwo = true
    end--if

    if (booskiptwo) then

      numreadonepos = numreadonepos + 2 -- nothing copied

    else

      if ((numtypearr==22) and ((numchariix==38) or (numchariix==91) or (numchariix==93))) then
        strwancxar = "&#" .. tostring (numchariix) .. ";" -- dec-encode #A22
      else
        strwancxar = string.char(numchariix) -- pass
      end--if
      if (booinsidewili) then
        strflatcoll = strflatcoll .. strwancxar -- only unencoded possible
        tabcollectlevel[numtypearr] = tabcollectlevel[numtypearr] .. strwancxar
      else
        strmainout = strmainout .. strwancxar -- html-dec-encoded for <pre> only
      end--if
      numreadonepos = numreadonepos + 1

    end--if (booskiptwo) else

  end--while -- upper genuine loop

  numcnntcat = 0 -- counts up
  strkattprefix = mw.site.namespaces[14].name -- "Kategori:" on a sv wiki

  while true do -- lower genuine loop over collected cat:s -- postprocess them

    if (numsperrorko>=20) then
      break -- #S20
    end--if

    strtajtl = tabtmpcatnm [numcnntcat] -- table is ZERO-based
    strhiint = tabtmpcathi [numcnntcat] -- table is ZERO-based
    if (type(strtajtl)~='string') then
      break -- done
    end--if
    strvisible = strkattprefix .. ':<br>' .. strtajtl
    if (strhiint~='') then
      strvisible = strvisible .. ' &#124; (<small>' .. strhiint .. '</small>)'
    end--if
    tabtmpcatnm [numcnntcat] = lfikatpaldigu (strkattprefix, strtajtl, strvisible, 2) -- table is ZERO-based
    numcnntcat = numcnntcat + 1

  end--while -- lower genuine loop

  if (numsperrorko>=20) then
    strmainout = '' -- #S20 FATAL, delete possible partial result
    numkcount = 0 -- here too
  else
    if (numkcount==0) then
      strkattfinal = constrca3d -- no cat:s -- this MUST NOT be left empty
    else
      strkattfinal = lfhtabletohtmllista(tabtmpcatnm,true)
    end--if
    if (numkcount>=2) then -- no extra boast for ONE
      strkattfinal = '<small>' .. constrca4d .. ' ' .. tostring (numkcount) .. ' ' .. constrca5d .. ' :<br></small>' .. strkattfinal
    end--if
  end--if

  tabsplout[0] = strkattfinal -- catlist (cannot be empty unless we are FATAL)
  tabsplout[1] = strmainout -- main/noncat (can be empty)
  tabsplout[2] = strallbypass -- [[Category:Track-CO2]][[Category:Track-KCN]]
  tabsplout[3] = numsperrorko -- split status / error code
  tabsplout[4] = numkcount -- number of cat insertions
  tabsplout[5] = numdupes -- number of dupes
  tabsplout[6] = numbypass

  return tabsplout

end--function lfysplitkat

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

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

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

function exporttable.ek (arxframent)

  -- special type "args" AKA "arx"

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

  -- tab

  local tabinparmap = {}    -- mapping of incoming target parameter, nested
  local tabutparmap = {}    -- mapping of output from expansion, nested
  local tabkolom = {}       -- [0]...[5] from "sel=" still dupe of individual

  -- str from parameters

  local strmainanon = ''  -- incoming, later [[ ]] removed
  local strsel = ''       -- 7 char:s ctl string
  local strtit = ''       -- title chain to replace values in "contabcolumns"
  local strwar = ''
  local strmod = ''       -- name of function, "1" prohibited
  local strrem = ''
  local strref = ''
  local strreg = ''

  -- after split into two for two purposes

  local strbigexpander = ''  -- for "arxframent:preprocess"
  local strcallforshow = ''  -- for showing in column "call"

  -- str from expansion and split

  local strzzfullrs = ''  -- full result of expansion before split
  local strzzcatlst = ''  -- catlist part after split
  local strzznoncat = ''  -- noncat part after split
  local strzzbypass = ''  -- bypass part raw chained cat insertions or empty

  -- str misc

  local strtmplnwp = ''     -- target, always with prefix unless "zer=1"
  local strfullono = ''     -- fullpagename from MediaWiki
  local strvorurefojn = ''  -- from "strref" & "strreg" with some text & HTML

  local strtymp = ''

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

  -- num from parameters

  local numspec = 0       -- ZERO not special | 7 ":top" | 8 res | 9 ":end"
  local numxcolumns = 0   -- number of columns requested (1...6, ZERO invalid)
  local numtabtype = 0    -- from "tab=" 0...5
  local numborwidt = 0    -- from "bor=" 5...80 ("0.05em"..."0.80em")

  -- num from anon param

  local numwhitebeg = 0   -- 10 or 32 for whitespace at begin after {{
  local numwhiteend = 0   -- 10 or 32 for whitespace at end before }}

  -- num from expansion and split

  local numpexpasta = 0   -- expand sta (0 no att | 70 OK ...)
  local numzfullrs = 0    -- len of full result of expansion before split
  local numzsplitsta = 20 -- preASSume split status #S20 (ZERO 2...6 20...40)
  local numzjumlahkt = 0  -- number of categories (all non-dupes counted)
  local numzjumlahdp = 0  -- number of dupes
  local numznoncat = 0    -- len of noncat part after split

  -- num status

  local numerr = 0        -- ZERO OK | 1 ?? | 2 anon | 3 adv anon | 4 "sel="

  -- boo from "sel="

  local boocall = false        -- column "call"
  local boosmall = false       -- small text, from "2" in "sel=" colu "call"
  local booparsed = false      -- column "parsed"
  local boorem = false         -- column "rem" (see also "strrem")
  local boocat = false         -- column "cat"
  local boonohints = false     -- omit hints, from "2" in "sel=" colu "cat"     !!!FIXME!!!
  local booback = false        -- column "back"
  local boodebug = false       -- column "debug"
  local booyescolour = false   -- have "2" in the digit for "debug"
  local boomapinstead = false  -- have "3" in the digit for "debug"

  -- boo from single parameters

  local boozer = false         -- have "zer=1"
  local boo3nsm12 = false      -- have "nsm=1" or "nsm=2" and process {({ })}
  local boo3nsm2m = false      -- have "nsm=2"
  local bootag = false         -- have "tag=1"
  local boohlt = false         -- have "hlt=1"
  local booncf = false         -- have "ncf=1"

  -- boo subsequent from parameters

  local bootitrow = false      -- title row only for ":top" & table type 3
  local boousetit = false      -- titles only for ":top" & table types 3 & 5
  local boodoexpand = false    -- request from "parsed" or "cat" or "debug"

  -- boo misc

  local boosamepage = false    -- keep false for special mode "numspec" too

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

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

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

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

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

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

  if (numerr==0) then
    contaberaroj  = lfhfillsurrstrtab (contaberaroj, constrkoll, constrpriv)
    contabxxplain = lfhfillsurrstrtab (contabxxplain, constrkoll, constrpriv)
    constrbalik   = lfhfillsurrstrtab (constrbalik, nil, constrpriv)
    constrredir   = lfhfillsurrstrtab (constrredir, nil, constrpriv)
  end--if

  ---- 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 "constrkoll" and "contaberaroj"

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

  if (numerr==1) then -- !!!FIXME!!! seizing pagename below
    strtymp = '#E01 Internal error in module "pate".'
    strviserr = constrlaxhu .. constrelabg .. strtymp .. constrelaen .. constrlaxhu
  end--if

  ---- SEIZE TWO UNDESIRABLE NAMED PARAMETERS ----

  if (type(arxsomons['trackingcategorieshiglow'])=='string') then
    numerr = 43 -- #E43 NOT appreciated like this (use "hlt=1")
  end--if
  if (type(arxsomons['nocat'])=='string') then
    numerr = 44 -- #E44 NOT appreciated like this (use "ncf=1") or avoid
  end--if

  ---- SEIZE ONE ANONYMOUS AND OBLIGATORY PARAMETER ----

  -- ":top" or ":end" or raw (no wall) or protected (at least one wall)
  -- #E03 param absent or wrong length
  -- #E04 more advanced faults (namely [[ | ]] [ ] see section below)
  -- assign "numspec" to 0,7,(8),9
  -- assign "strmainanon" (preliminary content, see below)

  strmainanon = '' -- preASSume guilt
  if (numerr==0) then
    do -- scope
      local vartutpum = 0
      local numlunng = 0
      vartutpum = arxsomons[1] -- required
      if (type(vartutpum)=='string') then
        numlunng = string.len (vartutpum)
        if ((numlunng>0) and (numlunng<10001)) then
          strmainanon = lfgtrimwhite (vartutpum) -- wiki does NOT strip anon
        end--if
      end--if
    end--do scope
  end--if (numerr==0) then
  if ((numerr==0) and (strmainanon=='')) then
    numerr = 3 -- #E03
  end--if

  if (numerr==0) then
    if (strmainanon==':top') then
      numspec = 7 -- top with title row
      strmainanon = '' -- now dead
    end--if
    if (strmainanon==':end') then
      numspec = 9 -- end
      strmainanon = '' -- now dead
    end--if
  end--if (numerr==0) then

  ---- SEIZE UP TO 14 NAMED PARAMETERS ----

  -- tab= (*), sel=, tit=, war=, bor=
  -- mod= (*), zer= (*), nsm= (*), tag= (*), rem= (*)
  -- ref= (*), reg= (*), hlt= (*), ncf= (*)
  -- * 7 give string, two of them "strsel" "strtit" pluprocessed
  --   later, one of them "tit=" additionally gives boolean
  -- * 2 give integer ("tab=" and "bor=")
  -- * 1 is tristate and gives 2 booleans ("nsm=")
  -- * 4 are boolean and give boolean ("zer=1" "tag=1" "hlt=1" "ncf=1")
  -- those marked with (*) are prohibited for ":top"
  -- all are prohibited for ":end"

  -- this must follow the seizure of the anonymous parameter due to
  -- "numspec" that we query here, and this must precede pluprocessing
  -- of the anonymous parameter (call string) due to "zer=1"

  if (numerr==0) then
    do -- scope

      local var38pom = 0
      local var38valu = 0
      local num38stat = 0
      local numluung = 0 -- !!!FIXME!!! remove

      numtabtype = 2 -- preASSume default
      var38pom = arxsomons['tab'] -- 1 digit, can be "0"..."5", default is "2"
      num38stat, var38valu = lfichkparam (var38pom, 1, 0, 5)
      if (num38stat==1) then
        numerr = 8 -- #E08 "tab=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "tab=" prohi for ":top"
            break
          end--if
          numtabtype = var38valu
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['sel'] -- 6 + 1 control string column control
      num38stat, var38valu = lfichkparam (var38pom, 0, 7, 7)
      if (num38stat==1) then
        numerr = 21 -- #E21 "sel=" bad -- more checks below
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          strsel = var38valu -- there are 2 defaults, bother about them later
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['tit'] -- length 1...200 and 1...6 title strings
      num38stat, var38valu = lfichkparam (var38pom, 0, 1, 200)
      if (num38stat==1) then
        numerr = 23 -- #E23 "tit=" bad -- more checks below
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (lfipillegal(var38valu,true,false,true,1,true,true,true)) then
            numerr = 23 -- #E23 "tit=" bad -- <...> legal [[...]] illegal
            break
          end--if
          strtit = var38valu -- default empty here & more in "contabcolumns"
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      strwar = '60A0A0' -- preASSume default bluegreen
      var38pom = arxsomons['war'] -- table line color, always 6 hex digits
      num38stat, var38valu = lfichkparam (var38pom, 6, 0, 0)
      if (num38stat==1) then
        numerr = 25 -- #E25 "war=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          strwar = var38valu -- strictly validated now
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      numborwidt = 20 -- preASSume default "0.20em"
      var38pom = arxsomons['bor'] -- TWO digits, legal range "05" ... "80"
      num38stat, var38valu = lfichkparam (var38pom, 2, 5, 80)
      if (num38stat==1) then
        numerr = 26 -- #E26 "bor=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          numborwidt = var38valu
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['mod'] -- name of LUA function
      num38stat, var38valu = lfichkparam (var38pom, 0, 1, 60)
      if (num38stat==1) then
        numerr = 28 -- #E28 "mod=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "mod=" prohi for ":top"
            break
          end--if
          if (var38valu=='1') then -- this is a string, NOT number
            numerr = 28 -- #E28 "mod=" bad, need name of function, not "1"
            break
          end--if
          strmod = var38valu
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['zer'] -- 1 digit, boolean
      num38stat, var38valu = lfichkparam (var38pom, 1, 0, 1)
      if (num38stat==1) then
        numerr = 29 -- #E29 "zer=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "zer=" prohi for ":top"
            break
          end--if
          boozer = (var38valu==1)
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['nsm'] -- 1 digit, tristate
      num38stat, var38valu = lfichkparam (var38pom, 1, 0, 2)
      if (num38stat==1) then
        numerr = 31 -- #E31 "nsm=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "nsm=" prohi for ":top"
            break
          end--if
          boo3nsm2m = (var38valu==2)
          boo3nsm12 = boo3nsm2m or (var38valu==1)
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons['tag'] -- 1 digit, boolean
      num38stat, var38valu = lfichkparam (var38pom, 1, 0, 1)
      if (num38stat==1) then
        numerr = 32 -- #E32 "tag=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "tag=" prohi for ":top"
            break
          end--if
          bootag = (var38valu==1)
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      strrem = constrempt -- preASSume placeholder
      var38pom = arxsomons['rem'] -- length 1...1000, prohibited for special
      num38stat, var38valu = lfichkparam (var38pom, 0, 1, 1000)
      if (num38stat==1) then
        numerr = 34 -- #E34 "rem=" bad -- more checks below
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "rem=" prohi for ":top"
            break
          end--if
          if (lfipillegal(var38valu,true,false,true,0,true,true,true)) then
            numerr = 34 -- #E34 "rem=" bad -- <...> legal [[...]] legal
            break
          end--if
          strrem = var38valu -- default see above
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

      var38pom = arxsomons["ref"] -- length 1...20, prohibited for special  !!!FIXME!!!
      if (type(var38pom)=='string') then
        numluung = string.len (var38pom)
        if ((numluung>=1) and (numluung<=20) and (numspec==0)) then
          strref = var38pom
        else
          numerr = 7 -- #E07 "ref=" prohi for ":top"
          numerr = 35 -- #E35 "ref=" bad
        end--if
      end--if
      var38pom = arxsomons["reg"] -- length 1...20, prohibited for special  !!!FIXME!!!
      if (type(var38pom)=='string') then
        numluung = string.len (var38pom)
        if ((numluung>=1) and (numluung<=20) and (numspec==0) and (strref~='') and (var38pom~=strref)) then
          strreg = var38pom
        else
          numerr = 7 -- #E07 "reg=" prohi for ":top"
          numerr = 35 -- #E35 "reg=" bad
        end--if
      end--if

      var38pom = arxsomons["hlt"] -- length 1 only, prohibited for special  !!!FIXME!!!
      if (type(var38pom)=='string') then
        if ((string.len(var38pom)==1) and (numspec==0)) then -- boolean
          boohlt = (var38pom=="1")
        else
          numerr = 7 -- #E07 "hlt=" prohi for ":top"
          numerr = 36 -- #E36 "hlt=" bad
        end--if
      end--if

      var38pom = arxsomons['ncf'] -- 1 digit, boolean
      num38stat, var38valu = lfichkparam (var38pom, 1, 0, 1)
      if (num38stat==1) then
        numerr = 37 -- #E37 "ncf=" bad
      end--if
      if (num38stat==2) then
        while true do -- fake loop
          if (numspec==9) then
            numerr = 6 -- #E06 no named param legal for ":end"
            break
          end--if
          if (numspec~=0) then
            numerr = 7 -- #E07 "ncf=" prohi for ":top"
            break
          end--if
          booncf = (var38valu==1)
          break -- finally to join mark
        end--while -- fake loop -- join mark
      end--if

    end--do scope
  end--if (numerr==0) then

  ---- PICK ONE OF 2 DEFAULTS ----

  -- this must follow seizure of anon (-> "numspec") and
  -- named ("numtabtype", "strsel")

  -- do this for special work ":top" too

  if ((numerr==0) and (numspec~=9) and (strsel=='')) then
    if (numtabtype==0) then
      strsel = '010-000' -- show only parsed result
    else
      strsel = '111-110' -- show all except debug
    end--if
  end--if

  ---- PLUPROCESS CONTROL STRING TO 6 BOOLEANS ----

  -- this must follow assigning default value to "sel="

  -- do this for special work ":top" (numspec=7) too

  -- assign 6 + 4 booleans as well as tabkolom
  -- assign "numxcolumns" to 1...6 (ZERO is illegal)
  -- assign "boodoexpand"

  if ((numerr==0) and (numspec~=9)) then
    do -- scope
      local numsilur = 0
      while true do -- fake loop
        if (numspec==0) then
          numsilur = lfivaliumdctlstr ('211-213',strsel) -- tri and four legal
        else
          numsilur = lfivaliumdctlstr ('111-111',strsel) -- only binary legal
        end--if
        if (numsilur~=255) then
          break -- less than 255 is bad above, 777 would be good below
        end--if
        numsilur = string.byte (strsel,1,1) -- tristate "call"
        boocall = (numsilur~=48)
        boosmall = (numsilur==50)
        tabkolom[0] = boocall
        numxcolumns = numxcolumns + contabboo2int[boocall]
        numsilur = string.byte (strsel,2,2)
        booparsed = (numsilur==49)
        tabkolom[1] = booparsed
        numxcolumns = numxcolumns + contabboo2int[booparsed]
        numsilur = string.byte (strsel,3,3)
        boorem = (numsilur==49)
        tabkolom[2] = boorem
        numxcolumns = numxcolumns + contabboo2int[boorem]
        numsilur = string.byte (strsel,5,5) -- tristate "cat"
        boocat = (numsilur~=48)
        boonohints = (numsilur==50)
        tabkolom[3] = boocat
        numxcolumns = numxcolumns + contabboo2int[boocat]
        numsilur = string.byte (strsel,6,6)
        booback = (numsilur==49)
        tabkolom[4] = booback
        numxcolumns = numxcolumns + contabboo2int[booback]
        numsilur = string.byte (strsel,7,7) -- fourstate "debug"
        boodebug = (numsilur~=48)
        booyescolour = (numsilur==50) -- not useful for ":top"
        boomapinstead = (numsilur==51) -- not useful for ":top"
        tabkolom[5] = boodebug
        numxcolumns = numxcolumns + contabboo2int[boodebug]
        if (numxcolumns==0) then -- empty table not legal
          break -- 777 would be good below, 48 or 49 is bad too
        end--if
        boodoexpand = booparsed or boocat or boodebug
        numsilur = 777 -- magic for "all OK"
        break -- finally to join mark
      end--while -- fake loop -- join mark
      if (numsilur~=777) then
        numerr = 21 -- #E21 "sel=" bad
      end--if
    end--do scope
  end--if ((numerr==0) and (numspec~=9)) then

  ---- CATCH SOME TOXIC MIXTURES ----

  if ((numerr==0) and (numxcolumns>=2) and (numtabtype==0)) then
    numerr = 40 -- #E40 "sel=" conflicts with "tab="
  end--if

  if ((numerr==0) and (strmod~='') and boozer) then
    numerr = 41 -- #E41 "mod=" and "zer=1" must NOT be used together
  end--if

  ---- MINIMALLY PLUPROCESS THE HOLY CALL STRING FOR RAW MODE ----

  -- minimal content is "[[{({a})}]]" (call) or "[[a]]" (no call,
  -- degenerated but valid)

  if ((numerr==0) and (numspec==0) and boo3nsm2m) then -- "nsm=2"
    while true do -- fake loop
      if (string.len(strmainanon)<5) then
        numerr = 4 -- #E04
        break -- to join mark
      end--if
      if ((string.sub(strmainanon,1,2)~='[[') or (string.sub(strmainanon,-2,-1)~=']]')) then
        numerr = 4 -- #E04
        break -- to join mark
      end--if
      strmainanon = lfgtrimwhite(string.sub(strmainanon,3,-3)) -- strip [[ ]] + trim white
      break -- finally to join mark
    end--while -- fake loop -- join mark
  end--if

  ---- PLUPROCESS THE HOLY CALL STRING FOR SINGLE TARGET ----

  -- separate bare name from complete template call inside
  -- unorthodox brackets, and bother with namespace prefixes

  -- not needed for "special" mode ":top" & ":end"
  -- not needed for the raw mode

  -- incoming "strmainanon", length is 1...10'000, we do NOT alter it
  -- in any way except strip off [[ ]] and adjacent whitespace if present

  -- this depends on "numspec" and "zer=1"

  -- to assess the string as complete template call:
  -- * 3 discoveries [[ | ]] must be consistently true
  -- * length must be over 7 -- minimal content is "[[a|b]]"

  -- output:
  -- * complete template call "strmainanon" -- possible prefix for ns 10 is
  --   not added and not stripped either, other prefix possible, used for
  --   both expansion and display
  -- * prefixed name "strtmplnwp" -- always has a ns prefix (unless "zer=1")

  -- order of work:
  -- * do some checks on "strmainanon" and assign scoped
  --   "boohavparip", risk of #E04
  -- * if param then strip off [[ ]] + ws from "strmainanon" and do
  --   more checks, split off parameter chain + ws, result is
  --   "strcommon", risk of #E04
  -- * else (if no param) no strip no split but perform other checks,
  --   "strcommon" copied from "strmainanon", and "strmainanon"
  --   unchanged, risk of #E04
  -- * check whether the title is safe, risk of #E05
  -- * split off ns prefix and assign "strtmplnwp" re-adding it

  -- for both expansion and display we will later possibly add ":" and
  -- always add {{ }} , but there are some intermediate steps for expansion
  -- thus we MUST NOT do it here, and before that we will fix nesting for both

  if ((numerr==0) and (numspec==0) and (not boo3nsm2m)) then
    do -- scope

      local varfindwall = 0
      local strcommon = ''
      local strunfu7ll = ''
      local strnom7spaco = ''
      local numthisleen = 0
      local numnonsens = 0
      local boohavbigwall = false
      local boorectabegin = false
      local boorectaakhir = false
      local boohavparip = false  -- have param inside anon param thus [[ | ]]

      while true do -- fake loop

        numthisleen = string.len(strmainanon)
        numnonsens = string.byte(strmainanon,1,1) -- string cannot be empty
        if ((numnonsens==58) or (numnonsens==93) or (numnonsens==123) or (numnonsens==124) or (numnonsens==125)) then
          numerr = 4 -- #E04
          break -- to join mark -- invalid char at begin :]{|}
        end--if
        if (strmainanon=='[') then
          numerr = 4 -- #E04
          break -- to join mark -- invalid one-char name "["
        end--if
        if (numthisleen>=2) then -- chk consistency
          boohavbigwall = (string.find(strmainanon,'|',1,true)~=nil)
          boorectabegin = (string.sub(strmainanon,1,2)=='[[')
          boorectaakhir = (string.sub(strmainanon,-2,-1)==']]')
          if ((boohavbigwall~=boorectabegin) or (boohavbigwall~=boorectaakhir) or (boorectabegin~=boorectaakhir)) then
            numerr = 4 -- #E04
            break -- to join mark -- inconsistent discoveries
          end--if
          boohavparip = boohavbigwall
          if (boohavparip and (numthisleen<7)) then
            numerr = 4 -- #E04
            break -- to join mark -- below minimal content "[[a|b]]"
          end--if
        end--if (numthisleen>=2) then

        if (boohavparip) then -- length at least 7 guaranteed
          strmainanon = string.sub(strmainanon,3,-3) -- strip [[ ]] no ws yet
          numwhitebeg = string.byte(strmainanon,1,1)   -- & will later recover
          numwhiteend = string.byte(strmainanon,-1,-1) -- & ws to sane degree
          strmainanon = lfgtrimwhite(strmainanon) -- !!!CRUCIAL!!! trim white
          if (string.len(strmainanon)<3) then
            numerr = 4 -- #E04
            break -- to join mark -- below minimal content "a|b"
          end--if
          if ((string.byte(strmainanon,1,1)==124) or (string.byte(strmainanon,-1,-1)==124)) then
            numerr = 4 -- #E04
            break -- to join mark -- wall misplaced
          end--if
          varfindwall = string.find(strmainanon,'|',1,true) -- must find it
          strcommon = lfgtrimwhite(string.sub(strmainanon,1,(varfindwall-1))) -- isolate name
        else
          if ((string.find(strmainanon,'[',1,true)) or (string.find(strmainanon,']',1,true))) then
            numerr = 4 -- #E04
            break -- to join mark -- illegal [ ] in raw template name
          end--if
          strcommon = strmainanon
        end--if (boohavparip) else

        if (not lfwistitlesafe (strcommon)) then
          numerr = 5 -- #E05 invalid title
          break
        end--if

        numnonsens, strunfu7ll, strnom7spaco = lfwsplit3title (strcommon)
        if (numnonsens>200) then
          numerr = 5 -- #E05 invalid title
          break
        end--if
        if ((numnonsens~=0) and boozer) then
          numerr = 29 -- #E29 bad use of "zer=1"
          break
        end--if
        if ((numnonsens==0) and (not boozer)) then -- add prefix for ns 10
          strcommon = mw.site.namespaces[10].name .. ':' .. strcommon
          numnonsens, strunfu7ll, strnom7spaco = lfwsplit3title (strcommon)
          if (numnonsens==0) then -- hopefully impossible
            numerr = 5 -- #E05 invalid title
            break
          end--if
        end--if
        if (boozer) then
          strtmplnwp = strunfu7ll -- would have prefix but there is none
        else
          strtmplnwp = strnom7spaco .. ':' .. strunfu7ll -- add prefix
        end--if

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

    end--do scope
  end--if ((numerr==0) and (numspec==0) and (not boo3nsm2m)) then

  ---- SEIZE FULLPAGENAME AND COMPARE ----

  -- not needed for "special" mode ":top" & ":end"

  -- note that "prefixedText" contains translated ns prefix, thus
  -- "strtmplnwp" must use same pattern

  -- flag "boosamepage" affects the structure of the "call"
  -- cell to avoid self-link

  if ((numerr==0) and (numspec==0)) then
    strfullono = mw.title.getCurrentTitle().prefixedText
    boosamepage = (strfullono==strtmplnwp) -- both are standardized ;-)
  end--if

  ---- PREPARE TITLES ----

  -- titles are needed for ":top" (also HTML row) and table
  -- type 3 (hor) (also HTML row) and type 5 (ver) (but no HTML row)

  -- split "tit=" in "strtit" at "@@" and merge into
  -- "qtabmy6columns" using defaults from "contabcolumns"

  -- do this for special work ":top" too

  if (numerr==0) then
    bootitrow = (numspec==7) or (numtabtype==3) -- ":top" or compete hor table
    boousetit = bootitrow or (numtabtype==5) -- titles yes but no row for 5
  end--if

  if (boousetit) then
    qtabmy6columns = lfhatmergetab (strtit, tabkolom, contabcolumns) -- "strtit" can be empty
    if (qtabmy6columns[9]) then
      numerr = 23 -- #E23 "tit=" bad
    end--if
  end--if

  ---- SPLIT AND PREPARE FOR EXPANSION AND SHOWING ----

  -- * incoming target parameter "strmainanon"
  --   * [[ and ]] removed
  --   * possible prefix for ns 10 is not added and not stripped
  --     either, other prefix possible
  -- * also exists prefixed name "strtmplnwp" -- always has
  --   a ns prefix (unless "zer=1")

  -- * here we maybe restore the ordinary form from
  --   * workarounded inner doublebrackets ie "{({"..."})}"
  --   * workarounded aggressive tags ("nonewiki", variations of
  --     "renef", ...) even if we don't expand
  --   for both target call parameter and "rem="

  -- * here "strmainanon" is split into "strbigexpander" for
  --   "arxframent:preprocess" only, and "strcallforshow", thus
  --   "strmainanon" is decommissioned, but "strtmplnwp" is preserved

  while true do -- fake loop

    if ((numerr~=0) or (numspec~=0)) then
      break
    end--if
    if (boo3nsm12) then
      strmainanon = lfirestorefromtriple (strmainanon) -- "nsm=1" or "nsm=2"
      strrem = lfirestorefromtriple (strrem)
    end--if
    if (bootag) then
      strmainanon = lfirestorefromnetag (strmainanon) -- "tag=1"
      strrem = lfirestorefromnetag (strrem)
    end--if
    tabinparmap = lfhparsemap (strmainanon) -- BIG MAP HERE                     !!!FIXME!!! detect {{ }} too ??
    if (tabinparmap[0][4] or tabinparmap[0][5]) then
     numerr = 46 -- #E46 strip NOT appreciated
     break
    end--if
    numerr, strbigexpander, strcallforshow = lfhsplithidden (strmainanon)

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

  strmainanon = '' -- & now dead for both success and error
  tabinparmap = {} -- &

  if ((numerr==0) and (numspec==0) and boodoexpand and (not boo3nsm2m)) then
    strcallforshow = lfibrewcurlycall (boozer,strcallforshow,numwhitebeg,numwhiteend) -- maybe ":" SPC LF always {{ }}
    strtymp = lfgdeleteallwhite(strbigexpander)
    if (string.find(strtymp,'|trackingcategorieshiglow=',1,true)) then
     numerr = 43 -- #E43 NOT appreciated
    end--if
    if (string.find(strtymp,'|nocat=',1,true)) then
     numerr = 44 -- #E44 NOT appreciated
    end--if
    if (numerr==0) then
      if (boohlt) then
        strbigexpander = strbigexpander .. '|trackingcategorieshiglow=true' -- add after check
      end--if
      if (booncf) then
        strbigexpander = strbigexpander .. '|nocat=false' -- add after check
      end--if
      strbigexpander = lfibrewcurlycall (boozer,strbigexpander,0,0) -- last step
    end--if (numerr==0) then
  end--if

  ---- CHECK WHETHER THE TARGET PAGE (TEMPLATE) EXISTS AT ALL ----

  -- here YES "msgnw:" !!!FIXME!!!

  if (boodoexpand and (numerr==0) and (numspec==0)) then
    if (boo3nsm2m) then
      numpexpasta = 1 -- #X01 skip check for "nsm=2"
    else
      if (lfwifexisim(strtmplnwp)) then
        numpexpasta = 1 -- #X01 good so far
      else
        numpexpasta = 80 -- #X80 target does NOT exist
      end--if
    end--if
  end--if

  ---- EXPAND THE TEMPLATE AND SPLIT THE OUTPUT IF NEEDED ----

  -- here use "arxframent:preprocess" and NO "msgnw:"

  -- * the {{-}}-syntax assumes ns 10 if no prefix and no colon
  -- * if the split fails and the "debug" column is available then we must NOT
  --   brew any FATAL, instead show the full text plus the status code there

  -- "strzzfullrs" full result of expansion, len is "numzfullrs"
  -- "strzzcatlst" catlist part after split, no len, see "numzjumlahkt"
  -- "strzznoncat" noncat part after split, len is "numznoncat"
  -- "numzsplitsta" split status (ZERO or 2...6 or 20...40)
  -- "numzjumlahkt" number of categories (all non-dupes counted)
  -- "numzjumlahdp"

  if (numpexpasta==1) then -- expand requested and target exists
    do -- scope
      local tabrezu3v = {}
      strzzfullrs = arxframent:preprocess (strbigexpander)
      if ((type(strzzfullrs))=='string') then
        numpexpasta = 70 -- preASSume expand success #X70
        numzfullrs = string.len(strzzfullrs) -- total bloat
        if (numzfullrs==0) then
          numpexpasta = 82 -- #X82 empty string
        end--if
      else
        numpexpasta = 81 -- #X81 expand failure in wiki parser
      end--if ((type(strzzfullrs))=='string') else
      if (numpexpasta==70) then
        tabutparmap = lfhparsemap (strzzfullrs) -- BIG MAP HERE
        tabrezu3v = lfysplitkat (strzzfullrs, tabutparmap, boonohints, boohlt) -- BIG SPLIT HERE
        numzsplitsta = tabrezu3v[3] -- split status
        numzjumlahkt = tabrezu3v[4] -- number of cat:s (all non-dupes counted)
        numzjumlahdp = tabrezu3v[5] -- dupes
        if (numzsplitsta<=6) then -- ZERO OK | 2...6 non-fatal | 20...40 bad
          strzzcatlst = tabrezu3v[0] -- cat:s
          strzznoncat = tabrezu3v[1] -- main/noncat
          strzzbypass = tabrezu3v[2] -- empty string if not used
          numznoncat = string.len(strzznoncat) -- len of main/noncat part
        end--if
      end--if (numpexpasta==70) then
    end--do scope
  end--if (numpexpasta==1) then

  ---- ASSIGN ---

  -- insert 2 digits then 6 digits, whereas margin is fixed
  -- <table style="margin:0.6em;border:0.20em solid #60A0A0;border-collapse:collapse;">
  -- <td style="border:0.20em solid #60A0A0;padding:0.5em;text-align:center;">

  -- "qstrtdbegin" and "qstrtdendus" used in "lficondicelrow"

  qstrtabegin = contabtabeg[0] .. lfnumto2digit(numborwidt) .. contabtabeg[1] .. strwar .. contabtabeg[2]
  qstrtdbegin = contabtdbeg[0] .. lfnumto2digit(numborwidt) .. contabtdbeg[1] .. strwar .. contabtdbeg[2]
  qstrtdendus = '</td>'
  qstrtaendus = '</table>'

  ---- BOTHER WITH REFERENCES ----

  -- "strvorurefojn" is later dumbly attached to "strzznoncat"
  -- and the result lands in the "parsed" cell

  -- result from "lfweatreferences" has a "<br>" at begin

  if ((numerr==0) and (strref~='')) then
    strvorurefojn = '<br>' .. lfweatreferences (arxframent, strref)
    if (strreg~='') then
      strvorurefojn = strvorurefojn .. lfweatreferences (arxframent, strreg)
    end--if
  end--if

  ---- BREW TITLE ROW FOR SPECIAL 7 OR TYPE 3 BUT NOT TYPE 5 ----

  if ((numerr==0) and bootitrow) then
    strtymp = '' -- important to start with empty
    if (boocall) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[0] .. qstrtdendus
    end--if
    if (booparsed) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[1] .. qstrtdendus
    end--if
    if (boorem) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[2] .. qstrtdendus
    end--if
    if (boocat) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[3] .. qstrtdendus
    end--if
    if (booback) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[4] .. qstrtdendus
    end--if
    if (boodebug) then
      strtymp = strtymp .. qstrtdbegin .. qtabmy6columns[5] .. qstrtdendus
    end--if
    if (numtabtype==3) then
      strtymp = strtymp .. '</tr><tr>' -- separate rows now, outer <tr> later
    end--if
    strvisgud = strtymp
  end--if ((numerr==0) and bootitrow) then

  ---- BREW THE RESULT ORDINARY ----

  -- "call", "parsed", "rem", "cat", "back", "debug"

  -- 0 raw | 1 bunch of cells | 2 (defa) row | 3 compl hori 1+1 rows
  -- 4 compl hori 1 row | 5 compl vert 1...6 rows

  -- note that in output from a module {{ }} is NO LONGER processed
  -- by wiki parser but [[ ]] still is

  if ((numerr==0) and (numspec==0)) then
    do -- scope
      local strkombo = ''
      local strt7mp = ''
      local strt8mp = ''
      local strrezord = '' -- important to start with empty
      local boogoodresult = false
      strkombo = lficombostatus (numpexpasta,numzsplitsta) -- empty string on success
      boogoodresult = (strkombo=='')
      if (boocall) then
        strt8mp = lfidecencodbr (strcallforshow,true) -- see "Encoding of text"
        if (boosamepage or boo3nsm2m) then
          strt7mp = strt8mp -- avoid selflink or broken link from raw wikitext
        else
          strt7mp = '[[' .. strtmplnwp .. '|' .. strt8mp .. ']]' -- "}}" is visible but "]]" is NOT
        end--if
        strt7mp = lfipretable (strt7mp,contabboo2siz[boosmall]) -- inner table with font size
        strrezord = strrezord .. lficondicelrow (strt7mp,numtabtype,0)
      end--if (boocall) then
      if (booparsed) then
        if (boogoodresult) then
          strt7mp = strzznoncat .. strvorurefojn -- both can be empty, no #X82
          if (numtabtype~=0) then
            strt7mp = lfilefqdivqlefqtxt(string.char(10) .. strt7mp) -- add LF due wiki parser, outside left inside left full width
          end--if
        else
          strt7mp = lfiexplain (strkombo)
        end--if
        strrezord = strrezord .. lficondicelrow (strt7mp,numtabtype,1)
      end--if (booparsed) then
      if (boorem) then
        strrezord = strrezord .. lficondicelrow (strrem,numtabtype,2)
      end--if (boorem) then
      if (boocat) then
        if (boogoodresult) then
          strt7mp = strzzcatlst -- already formatted
        else
          strt7mp = strkombo -- do NOT explain again, keep code only
        end--if
        strrezord = strrezord .. lficondicelrow (strt7mp,numtabtype,3)
      end--if (boocat) then
      if (booback) then
        if (boo3nsm2m) then
          strt7mp = constrempt -- placeholder for "nsm=2"
        else
          strt8mp = 'target=' .. mw.uri.encode (strtmplnwp, "WIKI")  -- no quest "?" and no and "&" here
          strt7mp =            lfwbrew3url (arxframent, constrprli, strt8mp, constrbalik, true) .. '<br><br>' -- all
          strt8mp = strt8mp .. '&hidetrans=1&hidelinks=1'
          strt7mp = strt7mp .. lfwbrew3url (arxframent, constrprli, strt8mp, constrredir, true) -- redirects
          strt7mp = '<small>' .. strt7mp .. '</small>'
        end--if
        strrezord = strrezord .. lficondicelrow (strt7mp,numtabtype,4)
      end--if (booback) then
      if (boodebug) then
        strt7mp = 'Same page:&nbsp;' .. tostring(boosamepage) .. '<br>' -- always
        strt7mp = strt7mp .. 'Expand status:&nbsp;' .. lfiexpandstatus(numpexpasta) .. '<br>' -- always
        if (numpexpasta==70) then
          strt7mp = strt7mp .. 'Total out size:&nbsp;' .. tostring(numzfullrs) .. '<br>' -- only on expand success #X70
          strt7mp = strt7mp .. lfhreportfindings (tabutparmap[0]) -- only on expand success #X70
          strt7mp = strt7mp .. 'Split status:&nbsp;' .. lfisplitstatus(numzsplitsta) .. '<br>' -- only on expand success #X70
          if (numzsplitsta<20) then
            strt7mp = strt7mp .. 'Number of cat insertions:&nbsp;' .. tostring(numzjumlahkt) .. '<br>' -- only on
            strt7mp = strt7mp .. 'Number of cat dupes:&nbsp;' .. tostring(numzjumlahdp) .. '<br>' -- split success
            strt7mp = strt7mp .. 'Decategorized size:&nbsp;' .. tostring(numznoncat) .. '<br>' -- less than #S20
          end--if
          if (boomapinstead) then
            strt8mp = lfhtablewithmap(strzzfullrs,tabutparmap) -- only on expand success #X70
          else
            strt8mp = lfiultencode(strzzfullrs,48,booyescolour,false) -- only on expand success #X70
            strt8mp = lficenqtabqlefqtxt(strt8mp) -- outsid cent insid left
          end--if
          strt7mp = strt7mp .. '<br>' .. strt8mp
        end--if
        strrezord = strrezord .. lficondicelrow (strt7mp,numtabtype,5) -- always
      end--if (boodebug) then
      strvisgud = strvisgud .. strrezord
    end--do scope
  end--if ((numerr==0) and (numspec==0)) then

  ---- FINALIZE ROW OR COMPLETE TABLE IF WE HAVE SUCH ----

  -- 0 raw | 1 bunch of cells | 2 (defa) row | 3 compl hori 1+1 rows
  -- 4 compl hori 1 row | 5 compl vert 1...6 rows

  if (numerr==0) then
    if ((numspec==7) or (numtabtype==2) or (numtabtype==3) or (numtabtype==4)) then
      strvisgud = '<tr>' .. strvisgud .. '</tr>' -- finalize row
    end--if
    if ((numspec==0) and (numtabtype>=3)) then
      strvisgud = qstrtabegin .. strvisgud .. qstrtaendus -- finalize table
    end--if
    if (numspec==7) then
      strvisgud = qstrtabegin .. strvisgud -- begin table only, have one row
    end--if
    if (numspec==9) then
      strvisgud = qstrtaendus -- terminate table only, no other content
    end--if
  end--if

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

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

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

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

  if (numerr==0) then
    strret = strvisgud
  else
    strret = strviserr
  end--if
  return strret

end--function

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

return exporttable