User:PerfektesChaos/js/WikiSyntaxTextMod/dH.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/// PerfektesChaos/js/WikiSyntaxTextMod/dH.js
/// 2020-02-05 PerfektesChaos@de.wikipedia
//  WikiSyntaxTextMod:  Wiki syntax: Analysis of link -- web and wiki
/// Fingerprint: #0#0#
/// @license: CC-by-sa/4.0 GPLv3
/// <nowiki>
/* global mw:true, mediaWiki:false                                     */
/* jshint forin:false,
          bitwise:true, curly:true, eqeqeq:true, latedef:true,
          laxbreak:true,
          nocomma:true, strict:true, undef:true, unused:true           */


if ( typeof mediaWiki  !==  "object" ) {   // disconnected
   mw  =  { libs:   { WikiSyntaxTextMod:  { }
                    },
            log:    function () {"use strict";}
          };
}
( function ( mw ) {
   "use strict";
   var version  =  -7.23,
       sign     =  "WikiSyntaxTextMod",
       sub      =  "H",
       rls, self, WSTM;
   if ( typeof mw.loader  ===  "object" ) {
      rls   =  { };
      self  =  "user:PerfektesChaos/" + sign + "/" + sub;
      rls[ self ] = "loading";
      mw.loader.state( rls );
   }
   if ( typeof mw.libs[ sign ]  !==  "object" ) {   // isolated
      mw.libs[ sign ]  =  { };
   }
   WSTM  =  mw.libs[ sign ];
   if ( typeof WSTM.w  !==  "object" ) {
      WSTM.w  =  { link: { }  };
   }
   if ( typeof WSTM.w.link  !==  "object" ) {
      WSTM.w.link  =  { };
   }
   WSTM.w.link.vsn   =  version;
   WSTM.w.link.self  =  self;
   if ( typeof WSTM.bb  !==  "object" ) {
      WSTM.bb  =  { };
   }
   if ( typeof WSTM.debugging  !==  "object" ) {
      WSTM.debugging  =  { };
   }
} ( mw ) );



/*
Requires: JavaScript 1.3
          (String.charCodeAt String.fromCharCode String.replace)
          JavaScript 1.5  RegExp non-capturing parenthese
 */



//-----------------------------------------------------------------------



mw.libs.WikiSyntaxTextMod.bb.bbH  =  function ( WSTM ) {
   // Building block and run environment support
   // 2012-05-18 PerfektesChaos@de.wikipedia
   "use strict";
   if ( typeof WSTM.util  !==  "object" ) {
      WSTM.util  =  { };
   }


   if ( typeof WSTM.util.fiatObjects  !==  "function" ) {
      WSTM.util.fiatObjects  =  function ( adult, activate, assign ) {
         // Ensure existence of at least empty object
         // Precondition:
         //    adult     -- parent object
         //    activate  -- String with name of child object
         //    assign    -- optional object with initial definition
         //                 if containing object components,
         //                 they will be asserted as well
         // Postcondition:
         //    adult has been extended
         // Uses:
         //    .util.fiatObjects()  -- recursive
         // 2012-05-18 PerfektesChaos@de.wikipedia
         var elt,
             obj,
             s;
         if ( typeof adult[ activate ]  !==  "object" ) {
            adult[ activate ]  =  ( assign  ?  assign  :  { } );
         }
         if ( assign ) {
            obj  =  adult[ activate ];
            for ( s in assign ) {
               elt  =  assign[ s ];
               if ( typeof elt  ===  "object" ) {
                  WSTM.util.fiatObjects( obj, s, elt );
               }
            }  //  for s in assign
         }
      };   // .util.fiatObjects()
   }


   WSTM.util.fiatObjects( WSTM,  "debugging",  { loud: false } );


};   // .bb.bbH()
mw.libs.WikiSyntaxTextMod.bb.bbH( mw.libs.WikiSyntaxTextMod );
delete mw.libs.WikiSyntaxTextMod.bb.bbH;




//-----------------------------------------------------------------------



mw.libs.WikiSyntaxTextMod.bb.link  =  function (WSTM) {
   // Analysis of link -- web and wiki
   // Uses:
   //    .util.fiatObjects()
   // 2012-11-11 PerfektesChaos@de.wikipedia
   "use strict";
   WSTM.util.fiatObjects( WSTM,  "w",
                          { link:  { namespace: {  detect: {},
                                                   write:  {}  },
                                     projects:  { },
                                     protocol:  { },
                                     re:        { },
                                     replace:   { },
                                     web:       { },
                                     wiki:      { }
                                   }
                         } );
   var WLINK      =  WSTM.w.link,
       NAMESPACE  =  WLINK.namespace;
   WLINK.maxTitle     =  200;
   WLINK.maxURL       =  150;
   WLINK.maxWikilink  =  200;
   WLINK.nesting      =   50;



   WLINK.fence  =  function ( address, access, alone ) {
      // Detect termination of any link (URL or wiki)
      // Precondition:
      //    address  -- string with link and aftermath
      //    access   -- link type   0: unknown   1: URL   2: wikilink
      //    alone    -- true: detect target only   false: keep title
      // Postcondition:
      //    Returns false       entire string appears to be link target
      //            number > 0  index of termination before string end
      // Requires: JavaScript 1.3   charCodeAt()
      // 2011-03-02 PerfektesChaos@de.wikipedia
      var r  =  false,
          c,
          i,
          m,
          s,
          scheme;
      if ( address.length > 0 ) {
         m  =  access;
         s  =  address;
         if ( alone ) {
            if ( m === 0 ) {
               m  =  2;
               i  =  address.indexOf( "://" );
               if ( i > 0 ) {   // URL
                  if ( i < 6 ) {
                     scheme  =  address.substr( 0, i );
                     if ( scheme === "ftp"  ||
                          scheme === "http"  ||
                          scheme === "https" ) {
                        m  =  1;
                     }
                  }
               }
            }   // determine link type
            i  =  address.indexOf( ( m === 1  ?  " "  :  "|" ) );
            if ( i > 0 ) {
               r  =  i;
               s  =  address.substr( 0, r );
            }
         }   // target only
         i  =  s.indexOf( "\n" );
         if ( i > 0 ) {
            r  =  i;
            s  =  s.substr(0, r);
         }
         i  =  s.indexOf( "]" );
         if ( i > 0 ) {
            r  =  i;
            s  =  s.substr( 0, r );
         }
         i  =  s.indexOf( "<" );
         if ( i > 0 ) {
            c = s.charCodeAt( i + 1 );
            if ( c === 47 ) {   // '/'
               c  =  s.charCodeAt( i + 2 );
            }
            if ( c > 96  &&  c < 123 ) {   // 'a' ... 'z'
               r  =  i;
            }
         }
      }   // content
      return  r;
   };   // .w.link.fence()



   WLINK.fenced  =  function ( adjust, access, above, adhere ) {
      // Find '[' in WikiTom, analyze and process links
      // Precondition:
      //    adjust  -- WikiTom top element
      //    access  -- location object
      //               .i  position to start searching for "["
      //               .k  sibling number
      //    above   -- top level
      //    adhere  -- true: freeze link targets
      // Uses:
      //    >< .w.link.nesting
      //    .o.WikiTom().find()
      //    .w.link.fenced()   -- recursive
      //    .adjust.focus()
      //    mw.log()
      //    .w.link.format()
      // 2012-05-24 PerfektesChaos@de.wikipedia
      var lock  =  adhere,
          open  =  access,
          deep;
      do {
         open  =  adjust.find( "[",
                               open.i,
                               open.k,
                               true,
                               false,
                               false );
         if ( open ) {
            deep  =  open.child;
            if ( deep ) {
               if ( this.nesting > 0 ) {
                  this.nesting--;
                  this.fenced( deep.o, deep, false, lock );
                  open.i  =  0;
                  open.k++;
                  this.nesting++;
               } else if ( ! this.nesting ) {
                  mw.log( WSTM.debugging,
                          ".w.link.fenced()  nesting level exhausted  c="
                          + open.i,
                          2,
                          adjust.focus( open.k ) );
                  open.i++;
               }
            } else {
               open  =  this.format( adjust, open, false, lock, false );
               if ( open.target ) {
                  lock  =  true;
               }
            }
         }   // open
      } while ( open );   // do
   };   // .w.link.fenced()



   WLINK.filter  =  function (adjust, all) {
      // Remove undesired chars from wikilink and URL
      // Precondition:
      //    adjust  -- string with link target
      //    all     -- true: titled wikilink or File location
      // Postcondition:
      //    Returns  false   if nothing to do,
      //             string  adjusted identifier
      //    RegExp was used.
      // Uses:
      //    .str.trimL()
      // Requires: JavaScript 1.3   fromCharCode()
      // 2011-07-20 PerfektesChaos@de.wikipedia
      var scan  =  WSTM.str.trimL(adjust, true),
          shy   =  String.fromCharCode(173),
          r,
          re;   // &shy;
      if (scan.indexOf(shy) >= 0) {
         re  =  new RegExp(shy, "g");
         if (re.test(scan)) {
            scan  =  adjust.replace(re, "");
         }   // remove shy
      }   // shy
      if (scan.indexOf("&") >= 0) {
         if (scan.indexOf("&#x") >= 0) {
            re  =  new RegExp("&#x(AD|2028);", "g");   // shy EOL
            if (re.test(scan)) {
               scan  =  scan.replace(re, "");
            }   // remove
/*
            if (scan.indexOf("&#x200") >= 0) {
               var got;
               re   =  new RegExp("&#x(200[ABC]);", "g");
               got  =  re.exec(scan);
               // 200A   8203  ZERO WIDTH SPACE
               // 200B   8204  ZERO WIDTH NON-JOINER
               // 200C   8205  ZERO WIDTH JOINER
               if (got !== null) {
                  scan  =  scan.replace(re,
                             String.fromCharCode(parseInt(got[1], 16)));
               }   // replace
            }   // &#x200
*/
         }   // &#x
         if (all) {
            if (scan.indexOf("&nbsp;") >= 0) {
               re  =  new RegExp("&nbsp;", "g");
               if (re.test(scan)) {
                  scan  =  scan.replace(re, " ");
               }   // remove
            }   // &nbsp;
         }   // titled wikilink or File location
      }   // &
      r  =  (scan === adjust  ?  false  :  scan);
      return  r;
   };   // .w.link.filter()



   WLINK.fire  =  function ( adjust, above, adhere, arg ) {
      // Format and process any internal and external link
      // Precondition:
      //    adjust  -- WikiTom top element
      //    above   -- top level
      //    adhere  -- true: freeze link targets in this context
      //    arg     -- template parameter
      // Postcondition:
      //    Nodes are modified where suitable.
      //    RegExp was used.
      // Uses:
      //    >< .w.link.re.head
      //    >< .w.link.web.re
      //    >< .o.Wikilink.instance
      //    .w.link.fenced()
      //    .w.link.web.free()
      // Requires: JavaScript 1.5   RegExp non-capturing parenthese
      // 2012-05-18 PerfektesChaos@de.wikipedia
      if ( ! this.re.head ) {
         this.re.head  =  new RegExp( "^( +)?"
                                      + "(?:(\\[)"
                                         + "|"
                                         + "((?:(?:ht|f)tps?:)?//))",
                                      "i");
         this.web.re   =  new RegExp( "((^|[^:])"
                                     + "(\\b(?:https?|ftp):)?)//",
                                      "i");
      }
      if ( ! WSTM.o.Wikilink.instance ) {
         WSTM.o.Wikilink.instance  =  new WSTM.o.Wikilink();
      }
      this.fenced( adjust,
                   { i: 0,  k: 0 },
                   above,
                   adhere );   // '['
      this.web.free( adjust,
                     { i: 0,  k: 0 },
                     true,
                     adhere,
                     arg );   // "http://"
   };   // .w.link.fire()



   WLINK.format  =  function (adjust, about, above, adhere) {
      // Format found '[' in WikiTom, analyze and process link
      // Precondition:
      //    adjust  -- WikiTom element
      //    about   -- location object
      //               .i   string position of beginning "["
      //               .k   sibling number of adjust
      //    above   -- top level
      //    adhere  -- true: freeze link targets
      // Postcondition:
      //    Returns array with start location for next search
      //            .i       string position of termination
      //            .k       sibling number of termination
      //            .target  WikiTom, if category or interwiki
      // Uses:
      //    >  .o.Wikilink.instance
      //    >  .w.link.re.head
      //    >  .w.link.maxWikilink
      //    >  .w.link.maxTitle
      //    >  .o.Wikilink.ModeIw
      //    >  .o.Wikilink.ModeFile
      //    >  .o.WikiTom.LinkFile
      //    >  .o.Wikilink.ModeCat
      //    >  .o.WikiTom.LinkCategory
      //    >  .o.WikiTom.Sortkey
      //    >  .o.WikiTom.LinkInterWiki
      //    >  .o.WikiTom.LinkWiki
      //     < .o.WikiTom().mode
      //     < .o.WikiTom().lookup
      //    .o.WikiTom().fetch()
      //    .o.WikiTom().focus()
      //    .o.WikiTom().flip()
      //    .errors.found()
      //    .o.wikilink::
      //          .set()
      //          .getChange()
      //          .getError()
      //          .getUserModified()
      //          .getType()
      //          .getTargetLength()
      //          .getIncrement()
      //    .o.WikiTom().folder()
      // 2018-11-25 PerfektesChaos@de.wikipedia
      var j      =  about.i,
          m      =  1,
          mode   =  0,
          obj    =  new WSTM.o.Weblink(),
          r      =  { i: j + 1,  k: about.k },
          wlink  =  WSTM.o.Wikilink.instance,
          got,
          i,
          n,
          s;
      if (adjust.fetch(r.k, r.i, true)  ===  91) {   // '['
         mode  =  10;   // "[["
      } else {
         s  =  adjust.fetch(r.k, r.i, false);
         if (s) {
            got  =  this.re.head.exec(s);
            if (got) {
               if (! got.index) {   // ^ matches multiline
                  if (got[1]) {   // bad format: spaces between brackets
                     j  +=  got[1].length;
                  }
                  if (got[2]) {
                     mode  =  11;   // "[ [target"
                  } else {
                     mode  =  20;   // "[//"   or   "[https://" etc.
                  }
               }
            } else {
               got  =  /^([^\]\[|\n]+)([^\]\[\n]+)?\](.)/.exec(s);
               if (got) {
                  if (! got.index) {   // ^ matches multiline
                     if (got[3] === "]") {
                        if (got[1].length < this.maxWikilink) {
                           mode  =  12;   // bad: second bracket missing
                           if (got[2]) {
                              if (got[2].length > this.maxTitle) {
                                 mode  =  0;
                              } else {
                                 j--;
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
      switch (mode) {
         case 10 :
         case 11 :
         case 12 :
            wlink.set(adjust,
                      { i: j,  k: r.k,  lack: (mode===12) },
                      adhere);
            if (wlink.getChange()) {
               n  =  wlink.getRemoveFrom();
               s  =  wlink.getTextReplace();
               adjust.flip(r.k,
                           j + n,
                           wlink.getRemoveTo() - n,
                           s);
               if (wlink.getBracketShift()) {
                  j++;
               }
               if (wlink.getError()  ||  wlink.getUserModified()) {
                  WSTM.mod.lazy  =  false;
               }
            }
            if (adjust.parent) {
               if (wlink.getType() === WSTM.o.Wikilink.ModeIw
                   &&  ! above) {
                  s  =  adjust.fetch(r.k, j, false);
                  WSTM.errors.found("wikilinkInterLangDeep",
                                    false,
                                    s.substr(0, 50));
                  mode  =  0;
               }   // interwiki
            }
            if (adhere) {
               n  =  wlink.getTargetLength();
               if (n) {
                  j    +=  2;
                  got   =  adjust.folder(j,  r.k,  j + n,  r.k);
                  if (got) {
                     got.lookup  =  false;
                     switch (wlink.getType()) {
                        case WSTM.o.Wikilink.ModeFile :
                           got.mode  =  WSTM.o.WikiTom.LinkFile;
                           break;
                        case WSTM.o.Wikilink.ModeCat :
                           got.mode    =  WSTM.o.WikiTom.LinkCategory;
                           got.leader  =  wlink.getLeader();
                           r.target    =  got;
                           n           =  wlink.getSortkey();
                           if (n) {
                              r.k++;
                              got         =  adjust.folder(0,
                                                           r.k + 1,
                                                           n + 1,
                                                           r.k + 1);
                              got.mode    =  WSTM.o.WikiTom.Sortkey;
                              got.lookup  =  false;
                           }
                           break;
                        case WSTM.o.Wikilink.ModeIw :
                           got.mode    =  WSTM.o.WikiTom.LinkInterWiki;
                           got.leader  =  wlink.getLeader();
                           r.target    =  got;
                           break;
                        default:
                           got.mode  =  WSTM.o.WikiTom.LinkWiki;
                           break;
                     }   // switch wlink.mode
                     r.k  +=  2;
                     j     =  0;
                     m     =  0;
                  } else {
                     m  =  wlink.getIncrement();
                  }
               }
            } else {
               m  =  2;
            }
            if (mode > 10) {
               n  =  50;
               if (r.k > 2) {
                  s  =  adjust.fetch( r.k-2, 0, false ) +
                        adjust.fetch( r.k-1, 0, false );
                  i  =  s.lastIndexOf("|");
                  if  (i >= 0) {
                     s  =  s.substr(i+1);
                  }
                  i  =  s.lastIndexOf("[[");
                  if  (i >= 0) {
                     s  =  s.substr(i+2);
                  }
                  if (s.lastIndexOf("]") < s.lastIndexOf("[")) {
                     n  =  0;
                  }
               }
               if (n) {
                  s  =  adjust.fetch(r.k, j, false);
                  WSTM.errors.found("wikilinkBracketsAhead",
                                    false,
                                    s.substr(0, n));
               }
            }
            break;
         case 20 :
            r  =  obj.format(adjust,
                             { i: j,  j: 0,  k: r.k },
                             1,
                             false);
            if (adhere) {
               r  =  obj.freeze(r);
            }
            break;
      }   // switch mode
      if (mode !== 20) {
         r.i  =  j + m;
      }
      return  r;
   };   // .w.link.format()



   WLINK.linked  =  function (about) {
      // Check whether WikiTom is some link target
      // Precondition:
      //    about  -- WikiTom element
      // Postcondition:
      //    Returns true if about is wikilink
      // Uses:
      //    >  .o.WikiTom().mode
      //    >  .o.WikiTom.LinkWiki
      //    >  .o.WikiTom.LinkWeb
      // 2012-04-22 PerfektesChaos@de.wikipedia
      return  (about.mode >= WSTM.o.WikiTom.LinkWiki  &&
               about.mode <= WSTM.o.WikiTom.LinkWeb);
   };   // .w.link.linked()



   NAMESPACE.collection  =  {  "4": "Project",
                               "8": "MediaWiki",
                              "12": "Help"
   };   // .w.link.namespace.collection    2012-10-19
   NAMESPACE.nsMedia     =    -2;
   NAMESPACE.nsSpecial   =    -1;
   NAMESPACE.nsUser      =     2;
   NAMESPACE.nsFile      =     6;
   NAMESPACE.nsTemplate  =    10;
   NAMESPACE.nsCategory  =    14;
   NAMESPACE.collection[NAMESPACE.nsMedia]     =  "Media";
   NAMESPACE.collection[NAMESPACE.nsSpecial]   =  "Special";
   NAMESPACE.collection[NAMESPACE.nsUser]      =  "User";
   NAMESPACE.collection[NAMESPACE.nsFile]      =  "File";
   NAMESPACE.collection[NAMESPACE.nsTemplate]  =  "Template";
   NAMESPACE.collection[NAMESPACE.nsCategory]  =  "Category";
   NAMESPACE.pagesSpecial                      =  [
      // .w.link.namespace.pagesSpecial    2013-11-27
       "AbuseFilter",
       "AbuseLog",
       "AncientPages",
       "ArticleFeedback",
       "AutoLogin",
       "Blankpage",
       "Block",
       "BlockList",
       "Blockme",
       "Book",
       "BookSources",
       "BrokenRedirects",
       "Categories",
       "CategoryTree",
       "Cite",
       "ComparePages",
       "Contributions",
       "DeadendPages",
       "Disambiguations",   // Pages linking to disambiguation pages
       "DoubleRedirects",
       "EditWatchlist",
       "EmailUser",
       "ExpandTemplates",
    // "Export",   // mul
       "FewestRevisions",
       "FileDuplicateSearch",
       "Filepath",
       "Gadgets",
       "GlobalUsage",
       "Hieroglyphs",
       "Import",
       "Interwiki",
       "LinkSearch",
       "ListAdmins",
       "ListBots",
       "ListFiles",
       "ListGrouprights",
       "ListRedirects",
       "ListUsers",
       "Log",
       "LonelyPages",
       "LongPages",
       "Massmessage",
       "MergeAccount",
       "MergeHistory",
       "MIMEsearch",
       "MobileFeedback",
       "MobileOptions",
       "MostUsedTemplates",
       "Movepage",
       "MyContributions",
       "MyPage",
       "MyTalk",
       "MyUploads",
       "NewImages",
       "NewPages",
       "Notifications",
       "Nuke",
       "Oversight",
       "PagesWithProp",
       "PasswordReset",
       "PermanentLink",
       "PrefSwitch",
       "Preferences",
       "Prefixindex",
       "ProtectedPages",
       "ProtectedTitles",
       "RandomPage",
       "RandomRedirect",
       "RecentChanges",
       "RecentChangeslinked",
       "RemoveGlobalBlock",
       "Renameuser",
       "RevisionDelete",
       "Search",
       "SecurePoll",
       "SiteMatrix",
       "SpecialPages",
       "Tags",
       "Unblock",
       "UncategorizedCategories",
       "UncategorizedFiles",
       "UncategorizedPages",
       "UncategorizedTemplates",
       "UnusedCategories",
       "UnusedFiles",
       "UnusedTemplates",
       "Upload",
       "UploadStash",
       "UserLogin",
       "UserLogout",
       "UserRights",
    // "Version",   // mul
       "WantedCategories",
       "WantedFiles",
       "WantedPages",
       "WantedTemplates",
       "Watchlist",
       "Whatlinkshere",
       "WithoutInterwiki" ];



   WLINK.namespace.factory  =  function (apply, achieve) {
      // Initialize namespace keyword translation
      // Precondition:
      //    apply    -- 0: read/write,  -1: read,  1: write
      //    achieve  -- false: any;  or string with ID (read only)
      // Uses:
      //    >  .w.link.namespace.collection
      //    >  .lang.translate.read
      //    >  .lang.translate.d
      //    >  .lang.translate.write
      //    >< .w.link.namespace.detect
      //    >< .w.link.namespace.write
      //    .lang.translate.feed()
      // 2013-03-15 PerfektesChaos@de.wikipedia
      var i,
          n,
          seek,
          scan,
          space,
          target,
          trsl;
      if (apply <= 0) {
         scan    =  "";
         target  =  (achieve ? achieve : WSTM.lang.translate.read);
         for (space in this.collection) {
            seek  =  this.collection[space];
            scan  =  scan + "#" + space + "|";
            scan  =  WSTM.lang.translate.feed(scan,
                                              seek + ":",
                                              seek,
                                              target,
                                              false);
            if (seek === "File") {
               scan  =  WSTM.lang.translate.feed(scan,
                                                 "Image:",
                                                 "Image",
                                                 target,
                                                 false);
            }
         }   // for space in .collection
         this.detect[ (achieve ? achieve : "*") ]  =  scan.toLowerCase();
      }   // read
      if (apply >= 0) {
         target  =  { };
         n       =  WSTM.lang.translate.write.length;
         for (space in this.collection) {
            seek  =  this.collection[space];
            trsl  =  WSTM.lang.translate.d[ seek + ":" ];
            if (trsl) {
               for (i = 0;  i < n;  i++) {
                  scan  =  trsl[ WSTM.lang.translate.write[i] ];
                  if ( scan ) {
                     if ( typeof scan  ===  "object" ) {
                        scan  =  scan[0];
                     }
                     switch ( typeof scan ) {
                        case "object" :
                           scan  =  WSTM.lang.translate.fiat(scan);
                           break;
                        case "boolean" :
                           scan  =  seek;
                           break;
                     }   // switch typeof scan
                     target[ seek ]  =  scan;
                     break;   // for i
                  }
               }   // for i
            }   // translation possible
         }   // for space in .collection
         this.write[ "*" ]  =  target;
      }   // write
   };   // .w.link.namespace.factory()



   WLINK.namespace.feed  =  function (assigned, avoid, array) {
      // Populate Array with additional translation items
      // Precondition:
      //    assigned  -- unit with namespace translations, or empty
      //    avoid     -- generic item, not to be collected
      //    array     -- Array to be extended (by push)
      // Postcondition:
      //    Returns  modified array
      // Uses:
      //    .lang.translate.fiat()
      //    .util.isElement()
      // 2012-09-22 PerfektesChaos@de.wikipedia
      var r  =  array,
          e,
          i,
          n,
          s;
      if ( assigned ) {
         if ( typeof assigned  ===  "object" ) {
            e  =  assigned;
            n  =  e.length;
         } else {
            e  =  [ assigned ];
            n  =  1;
         }
         for (i = 0;  i < n;  i++) {
            s  =  e[i];
            if (s) {
               s  =  WSTM.lang.translate.fiat(s);
               if (s !== avoid) {
                  if (!  WSTM.util.isElement(r, s)) {
                     r.push(s);
                  }
               }
            }
         }   // for i
      }
      return  r;
   };   // .w.link.namespace.feed()



   WLINK.namespace.females  =  function (ask) {
      // Preserve gender variant of user namespace keyword
      // Precondition:
      //    ask  -- user namespace keyword (downcased)
      //    Not in external link nor export context.
      // Postcondition:
      //    Returns  code   .nsUser  or  decimal variant
      // Uses:
      //    >  .link.namespace.sUser
      //    >  .lang.translate.d
      //    >  .g.wDBname
      //    >  .g.projLang
      //    >  .link.namespace.nsUser
      //    >< .link.namespace.sUserL
      //    >< .link.namespace.users
      //    >< .link.namespace.collection
      //    .w.link.namespace.feed()
      //    .str.fromNum()
      // 2012-09-22 PerfektesChaos@de.wikipedia
      var r  =  this.nsUser,
          i,
          n,
          q;
      if (! this.sUserL) {
         this.sUserL  =  true;
         this.sUser   =  this.write["*"];
         if (this.sUser) {
            this.sUser  =  this.sUser.User;
            if (this.sUser) {
               this.sUserL  =  this.sUser.toLowerCase();
            }
         }
      }
      if (ask !== this.sUserL) {
         if (! this.users) {
            this.users  =  [ ];
            q           =  WSTM.lang.translate.d["User:"];
            if (q) {
               this.users  =  this.feed(q[WSTM.g.wDBname],
                                        this.sUser,
                                        this.users);
               this.users  =  this.feed(q[WSTM.g.projLang],
                                        this.sUser,
                                        this.users);
            }
            n  =  this.users.length;
            for (i = 0;  i < n;  i++) {
               q  =  this.nsUser   +   0.1  *  (i + 1);
               q  =  WSTM.str.fromNum(q);
               this.collection[ q ]  =  this.users[i];
            }   // for i
         }
         for (q in this.collection) {
            if (this.collection[q].toLowerCase() === ask) {
               r  =  parseFloat(q, 10);
               break;   // for q
            }
         }   // for q in .collection
      }
      return  r;
   };   // .w.link.namespace.females()



   WLINK.namespace.fetch  =  function (assigned, abroad, about) {
      // Retrieve assigned appropriate namespace keyword
      // Precondition:
      //    assigned  -- keyword code (number)
      //    abroad    -- project identifier, if any;  or false (local)
      //    about     -- page name, if present
      // Postcondition:
      //    Returns  adjcent keyword,  or  English
      // Uses:
      //    >  .w.link.namespace.collection
      //    >  .w.link.namespace.write
      //    .w.link.namespace.find()
      // 2012-10-01 PerfektesChaos@de.wikipedia
      var r      =  false,
          space  =  this.collection[assigned],
          w;
      if (space) {
         if (assigned === this.nsSpecial) {
            r  =  (this.find(about)  ?  "Special"  :  false);
         }
         if (! r) {
            if (! abroad) {
               w  =  this.write["*"];
               if (w) {
                  r  =  w[space];
               }
            }
            if (! r) {
               r  =  space;
            }
         }
      }
      return  r;
   };   // .w.link.namespace.fetch()



   WLINK.namespace.find  =  function (ask) {
      // Find canonical special page name
      // Precondition:
      //    ask  -- page title, might start with standard keyword
      // Postcondition:
      //    Returns modified ask, if canonical; or false
      // Uses:
      //    >  .w.link.namespace.pagesSpecial
      //    >< .w.link.namespace.reSpecial
      // 2012-11-06 PerfektesChaos@de.wikipedia
      var r  =  false,
          s  =  ask,
          i,
          j,
          n;
      if (s) {
         if (! this.reSpecial) {
            this.reSpecial  =  new RegExp("^([^/#]+)[/#]", "");
         }
         j  =  this.reSpecial.exec(s);
         if (j) {
            j  =  j[1].length;
            s  =  ask.substr(0, j);
         }
         s  =  s.toLowerCase();
         n  =  this.pagesSpecial.length;
         if (n) {   // .length is available
            for (i = 0;  i < n;  i++) {
               if (this.pagesSpecial[i].toLowerCase() === s) {
                  r  =  this.pagesSpecial[i];
                  if (j) {
                     r  =  r + ask.substr(j);
                  }
                  break;   // for i
               }
            }   // for i
         }
      }
      return  r;
   };   // .w.link.namespace.find()



   WLINK.namespace.furnish  =  function (ahead, abroad, adjacent) {
      // Retrieve keyword code if "File" or "Category" or localized
      // Precondition:
      //    ahead     -- string  keyword only, no ":"
      //    abroad    -- string  other language, or false
      //    adjacent  -- string  other project, or false
      // Postcondition:
      //    Returns  code   .nsFile .nsCategory etc.
      //             false, if not detected
      // Uses:
      //    >  .w.link.namespace.detect
      //    >  .w.link.namespace.nsUser
      //    >  .w.lang.write
      //    >  .w.link.namespace.nsMedia
      //    >  .w.link.namespace.nsSpecial
      //    >  .w.link.namespace.nsFile
      //    >  .w.link.namespace.nsTemplate
      //    >  .w.link.namespace.nsCategory
      //    .str.trimL()
      //    .w.link.namespace.factory()
      //    .w.link.namespace.females()
      // 2019-08-01 PerfektesChaos@de.wikipedia
      var r      =  false,
          scope  =  "*",
          story  =  WSTM.str.trimL(ahead.toLowerCase(), true),
          got,
          re;
      if (abroad) {
         if (! this.detect[abroad]) {
            this.factory(-1, abroad);
         }
         scope  =  abroad;
      }
      scope  =  this.detect[scope];
      if (scope) {
         if (scope.indexOf("|" + story + "|")  >  0) {
            re   =  "#(-?[0-9]+)\\|([^#]+\\|)*" + story + "\\|";
            re   =  new RegExp(re, "");
            got  =  re.exec(scope);
            if (got) {
               r  =  parseInt(got[1], 10);
               switch (r) {
                  case this.nsUser :
                     if (! abroad  &&  ! adjacent  &&
                         ! WSTM.lang.write) {
                        r  =  this.females(story);
                     }
                     break;
                  case this.nsMedia :
                  case this.nsSpecial :
                  case this.nsFile :
                  case this.nsTemplate :
                  case this.nsCategory :
                     break;
                  default:
                     // not Project: !
                     r  =  false;
               }   //
            }
         }
      }
      return  r;
   };   // .w.link.namespace.furnish()



   WLINK.projects.factory  =  function () {
      // Make RegExp alternative string for major sister projects
      // Postcondition:
      //    Returns  string with no counted brackets
      // 2016-05-31 PerfektesChaos@de.wikipedia
      return  "mediawiki"
              + "|wik"
              +     "(?:i"
              +       "(?:books"
              +         "|data"
              +         "|media"
              +         "|news"
              +         "|pedia"
              +         "|quote"
              +         "|source"
              +         "|species"
              +         "|tech"
              +         "|versity"
              +         "|voyage)"
              +       "|tionary)";
   };   // .w.link.projects.factory()



   WLINK.projects.find  =  function (apply, achieve, about) {
      // Find
      // Precondition:
      //    apply    -- Array[2] with domain parts
      //                [0] 2nd level domain
      //                [1] language (subdomain)
      //    achieve  -- path; or false
      //    about    -- linktext, following ' '  --  or false
      // Postcondition:
      //    Returns  String with URL or Array[3]
      //             [0] project type
      //             [1] language
      //             [2] title
      // Uses:
      //    >  .w.link.protocol.secure
      //    .w.link.projects.upload()
      // Requires: JavaScript 1.3   charCodeAt()
      // 2012-11-15 PerfektesChaos@de.wikipedia
      var r    =  [ apply[2], apply[1], false ],
          got;
      if (achieve) {
         r[2]  =  achieve;
         switch (r[0]) {
            case "mediawiki" :
               r[1]  =  "";
               break;
            case "wikimedia" :
               if (r[1].length < 4) {
                  r  =  false;
               } else if (achieve.charCodeAt(0) === 119) {   // 'w'
                  switch (r[1]) {
                     case "commons" :
                     case "meta" :
                        r[0]  =  r[1];
                        r[1]  =  "";
                        break;
                     case "upload" :
                        got  =  this.upload(achieve, about);
                        if (got) {
                           r  =  got;
                        }
                        break;
                  }   // switch r[1]
               }
               break;
            case "wikisource" :
               if (! r[1]) {
                  r[0]  =  "";
                  r[1]  =  "OldWikisource";
               }
               break;
            case "wikivoyage" :
               if (! r[1]) {
                  got  =  /^([a-z][a-z])\//.exec(r[2]);
                  if (got) {
                     r[1]  =  r[2].substr(0, 2);
                     r[2]  =  "wiki"  +  r[2].substr(2);
                  } else {
                     r[1]  =  "";
                  }
               }
               break;
            default :
               if (r[1] === "www") {
                   r  =  "//www." + r[0] + ".org" + "/"
                         +  (achieve ? achieve : "");
                   if (WLINK.protocol.secure.indexOf("|" + r[0] + "|")
                       >=  0) {
                      r  =  "https:" + r;
                   }
                } else if (! r[1]) {   // undefined
                   r[1]  =  "";
                }
         }   // switch r[0]
      }
      return  r;
   };   // .w.link.projects.find()



   WLINK.projects.friend  =  function (affiliate, assign) {
      // Is the current wiki project matched, or abbreviated
      // Precondition:
      //    affiliate  -- project name, also abbreviated
      //    assign     -- return also reformatted namespace
      // Postcondition:
      //    Returns false     unknown project or language or space
      //            array[2]  project identified
      //                      [0]  mode
      //                           1      affiliate already abbreviation
      //                           2      full name, abbreviated
      //                           3      commons  meta  mediawiki
      //                           false  namespace if assign
      //                      [1]  false  if current wiki project matches
      //                           string abbreviated name or
      //                                  reformatted namespace if assign
      // Uses:
      //    >  .g.projType
      // 2016-05-31 PerfektesChaos@de.wikipedia
      var r       =  false,
          m       =  -99,
          sister  =  affiliate.toLowerCase(),
          scope;
      if (sister === WSTM.g.projType) {
         sister  =  false;
         m       =  2;
      } else if (sister.length < 8) {   // abbreviated
         scope  =  false;
         if (sister.length === 1) {
            sister  =  affiliate;
            // ":sv:S:t Eriksplan (tunnelbanestation)"
         }
         switch (sister) {
            case "commons" :
               scope  =  "commons";
               m      =  3;
               break;
            case "b" :
               scope  =  "wikibooks";
               break;
            case "d" :
               scope  =  "wikidata";
               break;
            case "n" :
               scope  =  "wikinews";
               break;
            case "m" :
               scope   =  "meta";   // meta.wikimedia.org
               sister  =  "meta";
               m       =  3;
               break;
            case "mw" :
               scope  =  "mediawiki";   // mediawiki.org
               m      =  3;
               break;
            case "q" :
               scope  =  "wikiquote";
               break;
            case "s" :
               scope  =  "wikisource";
               break;
            case "v" :
               scope  =  "wikiversity";
               break;
            case "voy" :
               scope  =  "wikivoyage";
               break;
            case "w" :
               scope  =  "wikipedia";
               break;
            case "wikt" :
               scope  =  "wiktionary";
               break;
         }   // switch sister
         if (scope  &&  m < 0) {
            m  =  1;
         }
         if (scope === WSTM.g.projType) {
            sister  =  false;
         } else if (sister === WSTM.g.projType) {
            sister  =  false;
            m       =  2;
         }
      }
      if (sister) {
         if (m < 0) {
            m  =  2;
            switch (sister) {
               case "wikipedia" :
                  sister  =  "w";
                  break;
               case "wikibooks" :
               case "wikidata" :
               case "wikinews" :
               case "wikiquote" :
               case "wikisource" :
               case "wikiversity" :
                  sister  =  sister.substr(4, 1);
                  break;
               case "species" :
               case "wikispecies" :
                  sister  =  "species";
                  break;
               case "wikitech" :
                  sister  =  "wikitech";
                  break;
               case "wikivoyage" :
                  sister  =  "voy";
                  break;
               case "wiktionary" :
                  sister  =  "wikt";
                  break;
               case "meta" :
                  sister  =  "meta";
                  m       =  3;
                  break;
               default:
                  m  =  -1;
                  break;
            }   // switch sister
         }   // find abbreviation
         if (m > 0) {
            r  =  [m, sister];
         }   // identified
      } else {
         r  =  [m, false];
         if (assign) {   // namespace reformatting?
            if (m === 2) {
               if (affiliate.toLowerCase() === WSTM.g.projType) {
                  r  =  false;
               } else {   // reformatting
                  r  =  [ false, WSTM.g.projType ];
               }   // space
            }
         }
      }
      return  r;
   };   // .w.link.projects.friend()



   WLINK.projects.upload  =  function (achieve, about) {
      // Reformat any http link to a wiki upload as a wikilink
      // Precondition:
      //    achieve  -- path, following '//upload.wikimedia.org/'
      //    about    -- linktitle, following ' '  --  or false
      // Postcondition:
      //    Returns  false    if not to be formatted as wikilink
      //             array[3] [0] "" or prefix string terminated with ':'
      //                      [1] target of wikilink
      //                      [2] title of wikilink
      //    RegExp was used.
      // Uses:
      //    >  .g.projType
      //    >  .g.projLang
      //    >  .w.link.mediatypes
      //    >< .g.projUploadPath
      //    >< .w.link.namespace.sFile
      //    >< .g.re.stripFileExt
      //    .w.link.projects.friend()
      //    .w.link.wiki.target()
      //    .w.link.namespace.fetch()
      // Requires: JavaScript 1.3   charCodeAt()
      // 2012-11-15 PerfektesChaos@de.wikipedia
      var r      =  false,
          swift  =  false,
          space  =  false,
          got,
          n,
          re;
      if (achieve.substr(0, 18)  ===  "wikipedia/commons/") {
         swift  =  achieve.substr(18);
      } else {
         if ( typeof WSTM.g.projUploadPath  !==  "string" ) {
            WSTM.g.projUploadPath  =  WSTM.g.projType + "/" +
                                      WSTM.g.projLang + "/";
         }
         n  =  WSTM.g.projUploadPath.length;
         if (achieve.substr(0, n)  ===  WSTM.g.projUploadPath) {
            swift  =  achieve.substr(n);
         } else {
            re   =  /^(([a-z]+)\/([a-z]+)\/)(.+)$/;
            got  =  re.exec(achieve);
            if (got) {
               space  =  this.friend(got[2], false);
               if (space) {
                  space  =  space[1];
                  if (! space) {
                     space  =  "";
                  }
                  space  =  space + ":" + got[3] + ":File:";
                  swift  =  got[4];
               }
            }
         }
      }
      if (swift) {
         if (swift.charCodeAt(1) === 47  &&   // '/'
             swift.charCodeAt(4) === 47  &&   // '/'
             swift.charCodeAt(0) === swift.charCodeAt(2)) {
            swift  =  swift.substr(5);
            r  =  WLINK.wiki.target(swift);
            if (r) {
               swift  =  r;
            }
            if (! space) {
               if (! WLINK.namespace.sFile) {
                  WLINK.namespace.sFile  =
                            WLINK.namespace.fetch(WLINK.namespace.nsFile,
                                                  false);
               }
               space  =  ":" + WLINK.namespace.sFile + ":";
            }
            r  =  new Array(3);
            r[0]  =  space;
            r[1]  =  swift;
            if (about) {
               r[2]  =  about;
            } else {
               // ->  WSTM.w.img.file
               if ( typeof WSTM.g.re.stripFileExt  !==  "object" ) {
                  re  =  " *\\." + WLINK.mediatypes + "$";
                  WSTM.g.re.stripFileExt  =  new RegExp(re);
               }
               r[2]  =  swift.replace(WSTM.g.re.stripFileExt, "");
            }
         }
      }
      return  r;
   };   // .w.link.wiki.projects.upload()



   WLINK.re.factory  =  function ( ancient ) {
      // Initialize global variables for WMF URL
      // Precondition:
      //    ancient  -- true:  secure.wikimedia.org
      //                false: unified http or https since fall 2011
      // Uses:
      //    >  .w.link.protocol.secure
      //    >  .w.link.protocol.relative
      //    >  .w.link.langs
      //     < .w.link.re.secure
      //     < .w.link.re.domain
      //    .w.link.projects.factory()
      // 2019-08-20 PerfektesChaos@de.wikipedia
      var reProj  =  "(commons"
                     + WLINK.protocol.secure
                     + "meta"
                     + WLINK.protocol.relative
                     + WLINK.langs
                     + ")",
          reSite  =  "(" + WLINK.projects.factory() + ")",
          source;
      if (ancient) {
         source       =  "^" + reSite + "/" + reProj + "/";
         this.secure  =  new RegExp( source, "" );
      } else {
         source       =  "^(?:" + reProj + "\\.)?"
                          + "(?:m\\.)?"
                          + reSite
                          + "\\.org";
         this.domain  =  new RegExp( source, "i" );
      }
   };   // .w.link.re.factory



   WLINK.replace.factory  =  function (apply) {
      // Validate user defined link replacement request
      // Precondition:
      //    apply  -- .raw   array with user defined link replacements
      //                     Each element is an array with two elements
      //                     Both elements are either
      //                     string  with
      //                             [0] link full regexp
      //                             [1] replacement
      //                     Array  with 3/4 elements,
      //                             each pair strings regexp/replace
      //                            [0] prolog RE string / replacement
      //                                or  false / false
      //                            [1] link full RE string / replacement
      //                            [2] epilog RE string / replacement
      //                                or  false / false
      //                            [3] true: search case sensitive
      //                                (apply[][0])
      //                                true: unlink
      //                                (apply[][1])
      //              .name  name of user defined request variable
      // Postcondition:
      //    Returns  apply.parsed
      //     < apply.parsed  array with validated elements.
      //                     Both elements are arrays of length 3.
      // Uses:
      //    .util.isArray()
      //    .main.fault()
      // TODO:
      //    Split by recognized namespace
      // 2013-12-14 PerfektesChaos@de.wikipedia
      var r  =  [ ],
          s  =  " user definition element #",
          u  =  apply.raw,
          n  =  u.length,
          a,
          e,
          f,
          i,
          j,
          k,
          m,
          t;
      for (i = 0;  i < n;  i++) {
         a  =  u[i];
         if ( typeof a  !==  "object" ) {
            a  =  false;
         }
         if ( ! WSTM.util.isArray(a)) {
            WSTM.main.fault("Invalid Syntax in" + s + i,
                            apply.name);
            a  =  [false, false];
         }
         f  =  a[0];
         t  =  a[1];
         m  =  typeof t;
         if ( typeof f  ===  "string"   &&
             ( m === "string"  ||  m === "function" ) ) {
            f  =  [false, f, false, false];
            t  =  [false, t, false, false];
         } else if (WSTM.util.isArray(f) && WSTM.util.isArray(t)) {
            m  =  f.length;
            k  =  t.length;
            if ((m === 3  ||  m === 4)   &&   (k === 3  ||  k === 4)) {
               if (m === 3) {
                  f  =  [f[0], f[1], f[2], false];
               }
               for (j = 0;  j < 3;  j++) {
                  e  =  f[j];
                  if ( typeof e  ===  "string" ) {
                     if (e.length === 0  &&  j === 1) {
                        f  =  false;
                        break;   // for j
                     }
                  } else if (e) {   // invalid type
                     f  =  false;
                     break;   // for j
                  } else if (j === 1) {   // link regexp false
                     f  =  false;
                     break;   // for j
                  }
               }   // for j
               for (j = 0;  j < 3;  j++) {
                  e  =  t[j];
                  if (e) {
                     m  =  typeof e;
                     if (m !== "string"  &&  m !== "function") {
                        f  =  false;
                        WSTM.main.fault("Invalid" + s + i,
                                        apply.name);
                     }
                  }
               }   // for j
            } else {
               f  =  false;
            }
         } else {   // invalid format
            f  =  false;
         }   // string or array
         if (f) {
            m  =  (f[3] ? "i" : "");
            for (j = 0;  j < 3;  j++) {
               e  =  f[j];
               if ( typeof e  ===  "string" ) {
                  switch (j) {
                  case 0:   // prolog
                     e  =  e + "\f";
                     break;
                  case 1:   // link
                     e  =  "^" + e + "$";
                     break;
                  case 2:   // epilog
                     e  =  "\f" + e;
                     break;
                  }   // j
                  try {
                     f[j]  =  new RegExp(e, m);
                  } catch (err) {
                     WSTM.main.fault("Invalid" + s + i
                                     + "\nUser link RegExp\n" + err
                                     + "\n>>>" + e + "<<<\n" + f,
                                     apply.name);
                     f  =  false;
                  }
               }
            }   // for j
            if (f) {
               r.push([ [f[0], f[1], f[2], f[3]],
                        [t[0], t[1], t[2], t[3]] ]);
            }
         }   // valid
      }   // for i
      r             =  (r.length ? r : false);
      apply.parsed  =  (r.length ? r : false);
      return  r;
   };   // .w.link.replace.factory()



   WLINK.replace.flip  =  function (apply, adjust, ahead, after, about) {
      // Perform user defined link replacement
      // Precondition:
      //    apply   -- array with user defined link replacements
      //               Each element is an array with two elements
      //               Both elements are
      //               array  with 3 elements, each pairwise scan/replace
      //                      [0] prolog regexp string / replacement
      //                          or  false / false
      //                      [1] link full regexp / replacement
      //                      [2] epilog regexp / replacement
      //                          or  false / false
      //               Array is validated
      //    adjust  -- current link target
      //    ahead   -- prolog, may be false
      //    after   -- epilog, may be false
      //    about   -- informative hint for user defined functions
      // Postcondition:
      //    Returns  false     if not changed,
      //             string    adjusted link specification only
      //             array[3]  prolog and/or epilog modified also
      // Uses:
      //     < .mod.lazy
      //    .str.trimL()
      //    .w.link.fence()
      // 2013-12-01 PerfektesChaos@de.wikipedia
      var r  =  false,
          n  =  apply.length,
          q  =  [(ahead  ?  ahead + "\f"  :  "\f"),
                 adjust,
                 (after  ?  "\f" + after  :  "\f")],
          a,
          e,
          f,
          i,
          t;
      for (i = 0;  i < n;  i++) {
         a  =  apply[i];
         f  =  a[0];
         e  =  f[1];
         r  =  e.test(q[1]);
         if (r) {   // target match
            e  =  f[0];
            if (e) {   // prolog present
               r  =  e.test(q[0]);   //   e.test(ahead);
            }
            if (r) {   // target and prolog match
               e  =  f[2];
               if (e) {   // epilog present
                  r  =  e.test(q[2]);   //   e.test(after);
               }
            }
         }
         if (r) {   // every request matching
            t  =  a[1];
            e  =  typeof t[ 1 ];
            if ( e === "string" ) {
               q[1]  =  q[1].replace(f[1], t[1]);
            } else if ( e === "function" ) {
               q[1]  =  q[1].replace(f[1],
                                     t[1](about, i, 0, q[1]));
            }
            if (q[0] && f[0]) {
               e  =  typeof t[ 0 ];
               if ( e === "string" ) {
                  q[0]  =  q[0].replace(f[0],  t[0] + "\f");
               } else if ( e === "function" ) {
                  q[0]  =  q[0].replace(f[0],
                                        t[0](about, i, -1, q[0]) + "\f");
               }
            }
            if (q[2] && f[2]) {
               if ( typeof t[2]  ===  "string" ) {
                  q[2]  =  q[2].replace(f[2],  "\f" + t[2]);
               } else if (e === "function") {
                  q[2]  =  q[2].replace(f[2],
                                        "\f" + t[2](about, i, 1, q[2]));
               }
            }
            if (t[3]) {
               q[3]  =  true;
            }
            r  =  q[1].lastIndexOf("[") + 1;
            if (r > 0) {
               e     =  q[1].substr(0, r)  +  "\f";
               q[1]  =  q[1].substr(r);
               if (q[0]) {
                  q[0]  =  q[0].substr(0,  q[0].length - 1)   +   e;
               } else {
                  q[0]  =  e;
               }
            }
            r  =  WLINK.fence(q[1], 0, true);
            if (r) {
               e     =  "\f"
                        +  WSTM.str.trimL(q[1].substr(r), false);
               q[1]  =  q[1].substr(0, r);
               if (q[2]) {
                  q[2]  =  e + q[2].substr(1);
               } else {
                  q[2]  =  e;
               }
            }
            r  =  false;
         }   // match
      }   // for i
      if (q[0]  ===  (ahead  ?  ahead + "\f"  :  "\f")) {
         q[0]  =  false;
      }
      if (q[2]  ===  (after  ?  "\f" + after  :  "\f")) {
         q[2]  =  false;
      }
      if (q[0] || q[2]) {
         if (q[0]) {
            q[0]  =  q[0].substr(0,  q[0].length - 1);
         }
         if (q[2]) {
            q[2]  =  q[2].substr(1);
         }
         r  =  q;
      } else if (q[1] !== adjust) {
         r  =  q[1];
      }
      if (r) {
         WSTM.mod.lazy  =  false;
      }
      return  r;
   };   // .w.link.replace.flip()



   WLINK.replace.flipper  =  function (adjust, ahead, after, area) {
      // Perform user defined link replacement request
      // DEPRECATED
      // Precondition:
      //    adjust  -- current link target
      //    ahead   -- prolog, may be false
      //    after   -- epilog, may be false
      //    area    -- canonical namespace name, may be false
      //               "File", "Template"
      //    Replacement Array is parsed and validated
      // Postcondition:
      //    Returns  false     if not changed,
      //             string    adjusted link specification only
      //             array[3]  prolog and/or epilog modified also
      // Uses:
      //    >  .mod.wikilink
      //    >  .w.link.namespace.write
      //    .w.link.replace.flip()
      // 2013-03-27 PerfektesChaos@de.wikipedia
      var r  =  this.flip(WSTM.mod.wikilink, adjust, ahead, after,
                          "link4"),
          s;
      if (! r  &&  area) {
         s  =  WLINK.namespace.write["*"][area] + ":";
         if (adjust.indexOf(s) === 0) {
            r  =  this.flip(WSTM.mod.wikilink,
                            s + adjust,
                            ahead,
                            after,
                            "link:" + area + "4");
         }
         if (! r  &&  ! ahead  &&  area === "Template") {
            r  =  this.flip(WSTM.mod.wikilink,
                            adjust,
                            "{{",
                            after,
                            "template4");
         }
      }
      return  r;
   };   // .w.link.replace.flipper()



   WLINK.web.fetch  =  function ( analyze ) {
      // URL parsing until "//"
      // Precondition:
      //    analyze  -- object
      //                >  .limited
      //                >  .source
      //                >  .index
      //                >< .multiple
      //                 < .mark
      //                 < .met
      //                 < .scheme
      //                 < .lowScheme
      //                 < .lackScheme
      // Uses:
      //    .str.isBlank()
      //    .str.isLetter()
      //    .hooks.fire()
      // Requires: JavaScript 1.3   charCodeAt()
      // 2015-12-22 PerfektesChaos@de.wikipedia
      var c, i, k, s;
      analyze.met     =  analyze.index;
      analyze.scheme  =  false;
      if ( analyze.limited ) {   // '[' pointing
         analyze.mark  =  1;
         k             =  analyze.source.indexOf( "//", analyze.index );
         if ( k > 0 ) {
            for ( i = analyze.met + 1;  i < k;  i++ ) {
               c  =  analyze.source.charCodeAt(i);
               if ( WSTM.str.isBlank( c, true ) ) {
                  analyze.mark++;
               } else if ( c === 91 ) {   // '['
                  analyze.multiple++;
                  analyze.mark++;
               } else {
                  analyze.scheme  =  analyze.source.substr( i,  k - i );
                  break;   // for i
               }
            }   // for i
         }
      } else if ( analyze.met ) {
         analyze.mark      =  0;
         analyze.multiple  =  0;
         c                 =  analyze.source.charCodeAt( analyze.met );
         if ( c === 58 ) {   // ':'
            k  =  analyze.met - 1;
         } else {
            k  =  0;
         }
         if ( k ) {   // '://' pointing
            for ( i = k;  i >= 0;  i-- ) {
               c  =  analyze.source.charCodeAt( i );
               if ( ! WSTM.str.isLetter(c) ) {
                  i++;
                  break;   // for i
               }
            }   // for i--
            i  =  (i > 0  ?  i  :  0);
            if ( analyze.met > i ) {
               k               =  analyze.met + 1;
               analyze.scheme  =  analyze.source.substring( i, k );
               analyze.met     =  i;
            }
         }
         if ( analyze.met ) {
            for ( i = analyze.met - 1;  i >= 0;  i-- ) {
               c  =  analyze.source.charCodeAt( i );
               if ( c === 91 ) {   // '['
                  analyze.mark  =  1;
                  analyze.met--;
                  analyze.multiple++;
               } else if ( !  WSTM.str.isBlank( c, true ) ) {
                  break;   // for i
               }
            }   // for i--
         }
      }
      if ( analyze.scheme ) {
         s                  =  analyze.scheme.toLowerCase();
         analyze.lowScheme  =  ( analyze.scheme !== s );
         if ( analyze.lowScheme ) {
            analyze.scheme  =  s;
         }
         analyze.lackScheme  =  false;
      } else {
         analyze.lackScheme  =  WSTM.hooks.fire("https");
         if ( analyze.lackScheme ) {
            analyze.scheme  =  "https";
         }
         analyze.lowScheme  =  false;
      }
   };   // .w.link.web.fetch()



   WLINK.web.free  =  function (adjust, access, above, adhere, arg) {
      // Format and process any unbracketed URL, protocol required
      // Precondition:
      //    adjust  -- WikiTom top element
      //    access  -- location object
      //               .i  position to start searching for "://"
      //               .k  sibling number
      //    above   -- top level
      //    adhere  -- true: freeze link targets in this context
      //    arg     -- template parameter
      // Postcondition:
      //    Nodes are modified where suitable.
      //    RegExp was used.
      // Uses:
      //    >  .w.link.web.re
      //     < .mod.lazy
      //    .o.WikiTom().find()
      //    .w.link.web.free()  -- recursive
      //    .o.WikiTom().format()
      //    .o.WikiTom().freeze()
      // 2013-06-16 PerfektesChaos@de.wikipedia
      var got  =  access,
          obj  =  new WSTM.o.Weblink(),
          deep,
          pre;              // recent ROI start
      do {
         pre  =  { i: got.i,  k: got.k };
         got  =  adjust.find("://", got.i, got.k, true, false, false);
         if (got) {
            deep  =  got.child;
            if (deep) {
               this.free(deep.o, deep, true, adhere, arg);   // self
               got.i  =  0;
               got.k++;
            } else if (got.i < 3) {
               got.i  +=  6;
            } else {
               got  =  obj.format(adjust,
                                  { i: got.i,  j: 0,  k: got.k },
                                  0,
                                  arg);
               if (adhere) {
                  got  =  obj.freeze(got);
               }
            }
         }
      } while (got);
   };   // .w.link.web.free()



   WLINK.wiki.decode  =  function (adjust, article, after, alone,assume){
      // Standardize wiki identifier part, decode URL
      // Precondition:
      //    adjust   -- string with identifier (article or anchor)
      //    article  -- true if identifier is article, not anchor
      //    after    -- false: trailing space not permitted and to remove
      //    alone    -- true if entire link -- false if titled
      //    assume   -- true: space for underscore -- false: keep '_'
      // Postcondition:
      //    Returns  false   if nothing to do,
      //             string  adjusted identifier
      //    RegExp was used.
      // Uses:
      //    >  .lang.ltr
      //    >< .w.link.wiki.reTitleSpace
      //    >< .w.link.wiki.reTitleSpaces
      //    .str.setString()
      //    .str.setChar()
      //    .str.trimL()
      //    .str.trimR()
      //    .str.substrEnd()
      //    .lang.forward()
      //    .str.decodeOctet()
      // Requires: JavaScript 1.3   charCodeAt()   fromCharCode(Unicode)
      // 2015-11-05 PerfektesChaos@de.wikipedia
      var c       =  false,
          learnt  =  false,
          match   =  0,
          stuff   =  adjust,
          suffix  =  false,
          i,
          k1, k2, k3,
          n,
          qc, qn,
          s;
      if ( ! alone  &&  stuff.indexOf( "&" ) >= 0 ) {   // titled link
         if ( typeof this.reTitleSpace  !==  "object" ) {
            this.reTitleSpace  =  "(&nbsp;|&thinsp;|&#8201;|&#8239;)";
            this.reTitleSpace  =  new RegExp(this.reTitleSpace, "g");
         }
         n      =  stuff.length;
         stuff  =  stuff.replace(this.reTitleSpace, " ");
         if (stuff.length < n) {
            learnt  =  true;
         }
      }   // ! alone
      if (assume) {
         for (i = stuff.length - 1;  i >= 0;  i--) {
            if (stuff.charCodeAt(i) === 95) {   // '_'
               stuff   =  WSTM.str.setChar(stuff, 32, i);   // ' '
               learnt  =  true;
            }   // replace underscore
         }   // for i
      }
      n      =  stuff.length;
      stuff  =  WSTM.str.trimL(stuff, true);
      if (stuff.length < n) {
         learnt  =  true;
         n       =  stuff.length;
      }   // ltrim
      stuff  =  WSTM.str.trimR(stuff, false, false, false);
      if (WSTM.str.substrEnd(stuff, 5)  ===  "&lrm;") {
//    if (stuff.slice(-5) === "&lrm;") {
         stuff  =  stuff.slice(0, -5);
      }
      if (stuff.length < n) {
         learnt  =  true;
         if (after) {
            suffix  =  " ";
            n       =  n - stuff.length - 1;
            while (n) {
               suffix  +=  " ";
               n--;
            }   // while n
         }
      }   // rtrim
      i  =  stuff.indexOf("  ");
      while (i >= 0) {
         stuff   =  WSTM.str.setString(stuff, i, 2, " ");
         i       =  stuff.indexOf("  ");
         learnt  =  true;
      }   // while
      if (stuff.charCodeAt(0) === 38) {    // &
         if (stuff.substr(3, 2) === "m;") {
            WSTM.lang.forward();
            s  =  stuff.substr(1, 2);
            if ((   WSTM.lang.ltr  &&  s === "lr")   ||
                ( ! WSTM.lang.ltr  &&  s === "rl")) {
               stuff   =  stuff.substr(5);
               learnt  =  true;
            }   // heading superfluous char
         }   // m;
      }   // &
      if (WSTM.str.substrEnd(stuff, 2)  ===  "m;") {
//    if (stuff.slice(-2) === "m;") {
            s  =  WSTM.str.substrEnd(stuff, 5, 3);
//          s  =  stuff.slice(-5, -2);
         WSTM.lang.forward();
         if ((   WSTM.lang.ltr  &&  s === "&lr")   ||
             ( ! WSTM.lang.ltr  &&  s === "&rl")) {
            stuff   =  stuff.substr(0,  stuff.length - 5);
            learnt  =  true;
         }   // trailing superfluous char
      }   // m;
      if (suffix) {
         stuff  =  stuff + suffix;
      }   // ! after
      if (article) {
         qc  =  "%";
         qn  =  37;
      } else {
         qc  =  ".";
         qn  =  46;
      }
      match  =  stuff.indexOf(qc, match);
      while (match >= 0) {
         n   =  3;
         k1  =  WSTM.str.decodeOctet(stuff,  match + 1);
         if (k1 < 32) {   // invalid
         } else if (k1 <  48) {   // single ASCII
            c  =  k1;
         } else if (k1 <  58) {   // invalid
         } else if (k1 <  65) {   // single ASCII
            c  =  k1;
         } else if (k1 <  91) {   // invalid
         } else if (k1 <  97) {   // single ASCII
            c  =  k1;
         } else if (k1 < 123) {   // invalid
         } else if (k1 < 128) {   // single ASCII
            c  =  k1;
         } else if (k1 < 192) {   // invalid
         } else if (k1 < 240) {   // UTF-8
            k2  =  (stuff.charCodeAt(match + 3)  ===  qn);
            if (k2) {
               k2  =  WSTM.str.decodeOctet(stuff,  match + 4);
            }
            if (k2) {
               n  =  6;
               if (k1 < 224) {   // byte pair
                  if (k2 > 127  &&  k2 < 192) {
                     c  =  (k1 - 192)  *  64   +   k2   -   128;
                  }   // k2 valid
               } else {   // byte triplet
                  k3  =  (stuff.charCodeAt(match + 6)  ===  qn);
                  if (k3) {
                     k3  =  WSTM.str.decodeOctet(stuff,  match + 7);
                  }
                  if (k3) {
                     n  =  9;
                     if (k3 > 127  &&  k3 < 192) {
                        c  =  (((k1 - 224)  *  64   +   k2   -   128)
                               *    64)
                              +     k3    -    128;
                     }   // k3 valid
                  }   // URL-encoded byte #3
               }
            }   // URL-encoded byte #2
         }   // first byte
         switch (c) {   // required escapes
            case  32 : // ' '
               if (! article) {   // in anchors
                  c  =  false;   // ".20" not to be replaced, '_' used
               }
               break;
            case  35 : // #
               if (article) {
                  break;
               }   // fall through
            case  38 : // &
            case  91 : // [
            case  93 : // ]
            case 124 : // |
               c  =  false;
               break;
         }   // switch c
         if (c) {
            c       =  String.fromCharCode(c);
            stuff   =  WSTM.str.setString(stuff, match, n, c);
            learnt  =  true;
            n       =  1;
            c       =  false;
         }   // decode
         match  =  stuff.indexOf(qc,  match + 1);
      }   // while URL-encoded char
      if (stuff.indexOf("  ") > 0) {
         if ( typeof this.reTitleSpaces  !==  "object" ) {
            this.reTitleSpaces  =  new RegExp( " +", "g" );
         }
         stuff   =  stuff.replace(this.reTitleSpaces, " ");
         learnt  =  true;
      }   // ! alone
      return  (learnt  ?  stuff  :  false);
   };   // .w.link.wiki.decode()



   WLINK.wiki.file  =  function (adjust) {
      // Standardize presumable file link
      // Precondition:
      //    Text has been read until end
      // Uses:
      //    >  .w.link.namespace.nsFile
      //    >< .w.link.namespace.sFile
      //    .w.link.wiki.decode()
      //    .w.link.namespace.furnish()
      //    .w.link.namespace.fetch()
      // 2012-09-21 PerfektesChaos@de.wikipedia
      var r  =  this.decode(adjust, true, false, true, true),
          k;
      if (! r) {
         r  =  adjust;
      }
      k  =  r.indexOf(":");
      if (k > 1) {
         if (WLINK.namespace.furnish(r.substr(0, k),  false,  false)
             ===  WLINK.namespace.nsFile) {
            if (! WLINK.namespace.sFile) {
               WLINK.namespace.sFile  =
                    WLINK.namespace.fetch(WLINK.namespace.nsFile, false);
            }
            r  =  WLINK.namespace.sFile + r.substr(k);
         }
      }
      return  r;
   };   // .w.link.wiki.file()



   WLINK.wiki.finalize  =  function () {
      // Finalize category and interwiki link structure
      // Precondition:
      //    Text has been read until end
      // Uses:
      //    >< .w.encountered.cats
      //    >< .w.encountered.iwiki
      //    .errors.found()
      // 2012-04-27 PerfektesChaos@de.wikipedia
      var r    =  false,
          got  =  false,
          say  =  false,
          i,
          n;
      switch (r) {
         case WSTM.o.WikiTom.LinkCategory :
            got  =  WSTM.w.encountered.cats;
            break;
         case WSTM.o.WikiTom.LinkInterWiki :
            got  =  WSTM.w.encountered.iwiki;
            break;
      }   // switch r
      if (say) {
         if (got) {
            n  =  got.length;
            for (i = 1;  i < n;  i++) {
               if (got[i] === r.source) {
                  WSTM.errors.found("???.w.link.wiki.finalize()",
                                    false,
                                    r.source);
               }
            }   // for i
         }
      }
   };   // .w.link.wiki.finalize()



   WLINK.wiki.flat  =  function ( access, area ) {
      // Normalize wikilink target string
      // Precondition:
      //    about  -- string with wikilink target
      //    area   -- optional number with default namespace
      // Postcondition:
      //    Returns  string with wikilink target
      // Uses:
      //    >  .mod.wikilink
      //    .w.link.wiki.target()
      //    .w.link.namespace.furnish()
      //    .w.link.namespace.fetch()
      //    .w.link.replace.flip()
      //    .errors.found()
      // 2019-08-15 PerfektesChaos@de.wikipedia
      var r  =  access,
          i, lead, ns, scan, shift;
      /*
        Kommentar elminieren; später hinten dran hängen
      */
      if ( r.indexOf( "|" )  <  0   &&
           r.indexOf( "]" )  <  0 ) {
         r  =  this.target( r, true );
         i  =  r.indexOf( ":" );
         if ( ! i ) {
            lead  =  true;
            r     =  r.substr( 1 );
            i     =  r.indexOf( ":" );
         }
         if ( i > 1 ) {
            ns  =  WLINK.namespace.furnish( r.substr( 0,  i - 1 ) );
            if ( ns ) {
               r  =  r.substr( i + 1 );
               if ( ns === area ) {
                  lead  =  false;
               } else {
                  r  =  WLINK.namespace.fetch( ns ) +  ":" + r;
               }
            }
         }
         if ( WSTM.mod.wikilink ) {
            if ( ns === area ) {
               scan  =  WLINK.namespace.fetch( ns ) +  ":" + r;
               i     =  scan.length;
               scan  =  scan +  ":" + r;
            } else {
               scan  =  r;
            }
            shift  =  WLINK.replace.flip( WSTM.mod.wikilink,
                                          scan,
                                          false,
                                          false,
                                          "linkPar" );
            if ( shift !== scan ) {
               if ( ns === area ) {
                  r  =  shift.substr( i + 1 );
               } else {
                  r  =  shift;
               }
            }
          }
         if ( lead ) {
            r  =  ":" + r;
         }
      } else {
         WSTM.errors.found( "badPageName", false, r );
      }
   };   // .w.link.wiki.flat()



   WLINK.wiki.flush  =  function (apply) {
      // Remove any wikilink from WikiTom
      // Precondition:
      //    apply  -- WikiTom, might contain wikilinks as children
      // Postcondition:
      //    Returns  false  if nothing to do,  else true if modified
      // Uses:
      //    >  .o.WikiTom.LinkWikiPipe
      //    >  .o.WikiTom.TextOnly
      //    .w.link.linked()
      //    .o.WikiTom().fetch()
      //    .str.substrEnd()
      //    .o.WikiTom().find()
      //    .o.WikiTom().focus()
      //    .o.WikiTom().fresh()
      //    .o.WikiTom().flush()
      // 2015-08-25 PerfektesChaos@de.wikipedia
      var e  =  apply.children,
          r  =  false,
          i,
          p,
          q,
          n,
          s,
          t;
      if (e) {
         n  =  e.length;
         if (n > 1) {
            for (i = n - 2;  i >= 0;  i--) {
               if (this.linked(e[i])) {
                  p  =  apply.fetch(i - 1);
                  if (WSTM.str.substrEnd(p, 2)  ===  "[[") {
//                if (p.slice(-2) === "[[") {
                     r  =  true;
                     q  =  e[i + 1];
                     if (q.mode === WSTM.o.WikiTom.LinkWikiPipe) {
                        t  =  apply.find("]]",
                                         0,
                                         i + 2,
                                         true,
                                         false,
                                         false);
                        if (t) {
                           s  =  apply.fetch(t.k);
                           s  =  s.substr(0, t.i)  +  s.substr(t.i + 2);
                           t  =  apply.focus(t.k).fresh(s);
                           apply.flush(i + 1);
                        }
                     } else {
                        s  =  q.toString();
                        if (s.substr(0, 2)  ===  "]]") {
                           s  =  e[i].toString()  +  s.substr(2);
                           q.fresh(s);
                        }
                     }
                     apply.flush(i);
                     i--;
                     s  =  p.toString();
                     n  =  s.length - 2;
                     if (n) {
                        e[i].fresh(s.substr(0, n));
                     } else {
                        apply.flush(i);
                     }
                  }
               }
            }   // for i
         }
      }
      if (e.length === 1) {
         p  =  e[0];
         if (p.mode <= WSTM.o.WikiTom.TextOnly) {
            if (! p.children) {
               s           =  p.source;
               apply.mode  =  p.mode;
               delete apply.children;
               apply.fresh(s);
            }
         }
      }
      return  r;
   };   // .w.link.wiki.flush()



   WLINK.wiki.fore  =  function (aftermath, align) {
      // Find length of titled wikilink aftermath, if any
      // Precondition:
      //    aftermath  -- string with follower of a titled wikilink
      //    align      -- first character in aftermath to consider
      // Postcondition:
      //    Returns  false   if nothing to do,
      //             number  of characters to join with wikilink title
      //                     (terminated by interpunction etc.)
      // Uses:
      //    .str.isLetter()
      // Requires: JavaScript 1.3   charCodeAt()
      // 2013-06-24 PerfektesChaos@de.wikipedia
      var r,
          c,
          i,
          n  =  aftermath.length;
      for (i = align;  i < n;  i++) {
         c  =  aftermath.charCodeAt(i);
         if (WSTM.str.isLetter(c)) {
            c  =  String.fromCharCode(c);
            if (c.toLowerCase() !== c) {
               break;   // for i
            }
            //  Messages*.php  "Linktrail"    /^([äöüßa-z]+)(.*)$/
         } else {
            break;   // for i
         }
      }   // for i
      r  =  (i > align   ?   i - align   :   false);
      return  r;
   };   // .w.link.wiki.fore()



   WLINK.wiki.further  =  function (about) {
      // Handle special category or interwiki link
      // Precondition:
      //    about  -- link information
      //              >  .mode
      //              >  .source
      // Postcondition:
      //    Returns true iff first occurence of this type
      // Uses:
      //    >  .o.WikiTom.LinkCategory
      //    >  .o.WikiTom.LinkInterWiki
      //    >< .w.encountered.cats
      //    >< .w.encountered.iwiki
      // 2012-04-26 PerfektesChaos@de.wikipedia
      var r  =  false,
          s;
      switch (about.mode) {
         case WSTM.o.WikiTom.LinkCategory :
            s  =  "cats";
            break;
         case WSTM.o.WikiTom.LinkInterWiki :
            s  =  "iwiki";
            break;
      }   // switch mode
      if (s) {
         if (WSTM.w.encountered[s]) {
            WSTM.w.encountered[s].push(about.source);
         } else {
            WSTM.w.encountered[s]  =  [ about.source ];
            r  =  true;
         }
      }
      return  r;
   };   // .w.link.wiki.further()



   WLINK.wiki.iwMap  =  function (adjust) {
      // Check possible interwiki whether it is mapped to URL
      //    [[meta:Interwiki map]]
      // Precondition:
      //    adjust  -- possible mapped interwiki, leading ' ' permitted
      // Postcondition:
      //    Returns adjusted string, if mapped, or false
      //    RegExp was used.
      // Uses:
      //    >  .w.link.wiki.iwURL
      //    >  .w.link.wiki.iwFamily;
      //    >< .w.link.wiki.iwikiMap
      //    >< .w.link.wiki.re_iwikiMap
      // 2014-09-08 PerfektesChaos@de.wikipedia
      var r    =  false,
          re,
          got;
      if ( ! this.re_iwikiMap ) {
         this.iwikiMap     =  this.iwURL + this.iwFamily;
         re                =  "^ *(" + this.iwikiMap.substr(1) + ":";
         this.re_iwikiMap  =  new RegExp(re, "i");
      }
      got  =  this.re_iwikiMap.exec(adjust + ":");
      if (got) {
         re   =  new RegExp("\\|(" + got[1] + ")\\|",  "i");
         got  =  re.exec(this.iwikiMap);
         if (got) {
            r  =  got[1];
         }
      }   // interwiki map
      return  r;
   };   // .w.link.wiki.iwMap()



   WLINK.wiki.linked  =  function ( about ) {
      // Check whether WikiTom is wikilink target
      // Precondition:
      //    about  -- WikiTom element
      // Postcondition:
      //    Returns true if about is wikilink
      // Uses:
      //    >  .o.WikiTom().mode
      //    >  .o.WikiTom.LinkWiki
      //    >  .o.WikiTom.LinkExtWiki
      // 2012-04-18 PerfektesChaos@de.wikipedia
      return  ( about.mode >= WSTM.o.WikiTom.LinkWiki  &&
                about.mode <= WSTM.o.WikiTom.LinkExtWiki );
   };   // .w.link.wiki.linked()



   WLINK.wiki.remove  =  function (adjust) {
      // Remove any wikilink from content string
      // Precondition:
      //    adjust  -- string, might contain wikilinks
      // Postcondition:
      //    Returns  false   if nothing to do
      //             string  with removed wikilinks
      //    RegExp was used.
      // Uses:
      //    .w.link.wiki.target()
      //    .str.trimL()
      // Requires: JavaScript 1.3   charCodeAt()
      // Remark:   Unused but neat
      // 2011-01-26 PerfektesChaos@de.wikipedia
      var i     =  adjust.indexOf("[["),
          r     =  false,
          fa,
          j,
          n,
          re,
          s,
          scan,
          show;
      if (i >= 0) {
         scan  =  adjust;
         r  =  "";
         while (i >= 0) {
            r     =  r + scan.substr(0, i);
            scan  =  scan.substr(i + 2);
            n     =  scan.indexOf("\n");
            if (n > 0) {
               s  =  scan.substr(0, n);
            } else {
               s  =  scan;
            }
            n  =  s.indexOf("]]");
            if (n > 0) {
               s  =  s.substr(0, n);
               j  =  s.indexOf("|");
               if (j < 0) {
                  show  =  this.target(s, true);
                  r     =  r  +  (show ? show : s);
               } else {
                  s  =  WSTM.str.trimL(s.substr(j + 1),  false);
                  if (s.charCodeAt(0) === 124) {   // '|'
                     re  =  new RegExp("^(:?[a-zA-Z]+:)?" +
                                        "([^|(,]+).*\\|",
                                       "");
                     fa  =  re.exec(scan);
                     if (fa !== null) {
                        show  =  this.target(fa[2], true);
                        s     =  (show ? show : fa[2]);
                     }
                  }
                  r  =  r + s;
               }
               scan  =  scan.substr(n+2);
            } else {
               r     =  r + "[[";
            }
            i  =  scan.indexOf("[[");
         }   // while i
         r  =  r + scan;
      }   // something to do
      return  r;
   };   // .w.link.wiki.remove()



   WLINK.wiki.target  =  function ( adjust, alone ) {
      // Standardize target identifier in the wiki world
      // Precondition:
      //    adjust  -- string with link specification
      //    alone   -- true if entire link -- false if titled
      // Postcondition:
      //    Returns  false   if nothing to do,
      //             string  adjusted link specification
      // Uses:
      //    >< .w.link.re.dirent
      //    .hooks.fire()
      //    .w.link.wiki.decode()
      // 2020-02-05 PerfektesChaos@de.wikipedia
      var joint  =  adjust.indexOf( "#" ),
          r      =  false,
          stuff  =  adjust,
          sub    =  false,
          s;
      if ( joint >= 0 ) {   // fragment
         sub  =  stuff.substr( joint + 1 );
         if ( joint === 0 ) {
            stuff  =  "";
         } else {
            stuff  =  stuff.substr( 0, joint );
         }
         if ( sub.indexOf( ":" )  >  0 ) {   // anchor template?
            s  =  WSTM.hooks.fire( "fragment", sub );
            if ( s ) {
               sub  =  s;
               r    =  true;
            }
         }
         s  =  this.decode( sub, false, false, alone, true );
         if ( s ) {
            if ( s === "" ) {
               sub  =  false;
            } else {
               sub  =  s;
            }
            r  =  true;
         } else if ( sub === "" ) {
            sub  =  false;
            r    =  true;
         }
      }   // anchor
      s  =  this.decode( stuff, true, sub, alone, true );
      if ( s ) {
         stuff  =  s;
         r      =  true;
      }
      if ( stuff.indexOf( "&" )  >=  0 ) {
         if ( typeof WLINK.re.dirent  !==  "object" ) {
            WLINK.re.dirent  =  new RegExp( "&(?:lrm|rlm);", "g" );
         }
         s  =  stuff.replace( WLINK.re.dirent, "" );
         if ( s !== stuff ) {
            stuff  =  s;
            r      =  true;
         }
      }
      if ( sub ) {
         stuff  =  stuff + "#" + sub;
      }
      r  =  ( r ? stuff : false );
      return  r;
   };   // .w.link.wiki.target()



   WLINK.wiki.url  =  function (access, address, achieve, about) {
      // Reformat any http link to a wiki project as a wikilink or else
      // Precondition:
      //    access   -- length of protocol part   2: relative
      //                                          7: http
      //                                          8: https
      //    address  -- domain and subdomains, maybe any wiki project
      //                downcased
      //    achieve  -- path, if any, following '/'  --  or false
      //    about    -- linktext, following ' '  --  or false
      // Postcondition:
      //    Returns  false    if not to be formatted as wikilink
      //             array[3] [0] "" or prefix string terminated with ':'
      //                      [1] target of wikilink
      //                      [2] title of wikilink
      //             string   reformatted URL
      //    RegExp was used.
      // Uses:
      //    >  .w.link.re.secure
      //    >  .g.projLang
      //    >  .w.link.re.domain
      //    >  .g.wNsNumber
      //    >  .w.link.protocol.secure
      //    >  .w.link.protocol.relative
      //    >  .g.projType
      //    >  .w.link.namespace.nsFile
      //    >  .w.link.namespace.nsCategory
      //    >< .w.link.re.titleID
      //    >< .w.link.re.uselang
      //    >< .w.link.re.urlpar
      //     < .mod.lazy
      //    .str.substrEnd()
      //    .w.link.re.factory()
      //    .w.link.projects.upload()
      //    .w.link.wiki.target()
      //    .w.link.projects.friend()
      //    .w.link.namespace.furnish()
      //    .str.trimL()
      //    .str.camelCasing()
      //    .w.link.namespace.fetch()
      // Requires: JavaScript 1.3   charCodeAt()
      // 2019-10-01 PerfektesChaos@de.wikipedia
      var life    =  true,
          r       =  false,
          domain,
          got,
          j,
          key,
          layer,
          learn,
          mode,
          n,
          pars,
          s,
          scope,
          show,
          sister,
          slang,
          stuff;
      if (WSTM.str.substrEnd(address, 4)  ===  ".org") {
//    if (address.slice(-4) === ".org") {
         domain  =  address.substr(access);
         layer   =  false;
         if (access === 8) {   // https
            layer  =  (domain === "secure.wikimedia.org");
            if (layer) {
               r  =  [false, false, false];
               if (achieve) {
                  if (! WLINK.re.secure) {
                     WLINK.re.factory(true);
                  }
                  got  =  WLINK.re.secure.exec(achieve.toLowerCase());
                  if (got) {
                     if (got[4] === "commons") {
                        r[0]  =  "commons";
                        r[1]  =  "";
                     } else {
                        r[0]  =  got[1];   // reSite
                        r[1]  =  got[4];   // reProj
                     }
                     if ( achieve.charCodeAt(0) === 119) {   // 'w'
                        r[2]  =  achieve.substr(got[0].length);
                     } else {
                        r[2]  =  achieve;
                     }
                     r[2]  =  achieve.substr(got[0].length);
                  } else {
                     r  =  false;
                  }
               } else {
                  r[0]  =  "wikimedia";
                  r[1]  =  WSTM.g.projLang;
               }
            }   // https://secure.wikimedia.org until summer 2011
         }   // https
         if (! layer) {   // protocol relative / http or https 2011-10...
            if (! WLINK.re.domain) {
               WLINK.re.factory(false);
            }
            domain  =  WLINK.re.domain.exec(domain);
            if (domain) {
               r  =  [domain[2], domain[1], false];
               if (achieve) {
                  r[2]  =  achieve;
                  if (r[0] === "wikimedia") {
                     if (r[1].length < 4) {
                        r  =  false;
                     } else if (achieve.charCodeAt(0) === 119) {   // 'w'
                        switch (r[1]) {
                           case "commons" :
                           case "meta" :
                              r[0]  =  r[1];
                              r[1]  =  "";
                              break;
                           case "species" :
                              r[0]  =  "wikispecies";
                              r[1]  =  "";
                              break;
                           case "upload" :
                              mode  =  WLINK.projects.upload(achieve,
                                                             about);
                              if (mode) {
                                 r     =  mode;
                                 life  =  false;
                              }
                              break;
                        }   // switch r[1]
                     }
// case "mediawiki" :
                  } else if (r[0] === "mediawiki") {
                     r[1]  =  "";
// case "wikisource" :
                  } else if (r[0] === "wikisource") {
                     if (! r[1]) {
                        r[0]  =  "";
                        r[1]  =  "OldWikisource";
                     }
// case "wikivoyage" :
                  } else if (r[0] === "wikivoyage") {
                     if (! r[1]  ||  r[1] === "www") {
                        got   =  /^([a-z][a-z])\//.exec(r[2]);
                        if (got) {
                           r[1]  =  r[2].substr(0, 2);
                           r[2]  =  "wiki"  +  r[2].substr(2);
                        } else {
                           r[1]  =  "";
                        }
                     }
// default :
                  } else if (r[1] === "www") {
                     r  =  "//www." + r[0] + ".org" + "/"
                           +  (achieve ? achieve : "");
                     if (WLINK.protocol.secure.indexOf("|" + r[0] + "|")
                         >=  0) {
                        r  =  "https:" + r;
                     }
                     life  =  false;
                  } else if (! r[1]) {   // undefined
                     r[1]  =  "";
                  }
               }   // path
            }   // .link.projects.find()
         }
      }   // .org
      if (r && life) {   // It's a wiki.
         learn  =  false;
         if (r[2]) {
            stuff  =  r[2];
            life   =  (stuff.charCodeAt(1) === 47);   // '/' ("w/")
            if (life) {
               learn  =  (stuff.substr(2, 11)  ===  "wiki.phtml?");
               if (learn) {   // ~2004
                  stuff   =  stuff.substr(13);
                  r[2]    =  "w/index.php" + stuff;
               } else if (stuff.substr(2, 10)  ===  "index.php?") {
                  stuff  =  stuff.substr(12);
               } else {
                  life  =  false;
               }
            }
            if (r[2].indexOf("uselang=") >= 0  &&  ! WSTM.g.wNsNumber) {
               if ( typeof WLINK.re.uselang  !==  "object" ) {
                  WLINK.re.uselang  =  "([?&])uselang=[-a-z]+(&?)";
                  WLINK.re.uselang  =  new RegExp(WLINK.re.uselang, "i");
               }
               got  =  WLINK.re.uselang.exec(r[2]);
               if (got) {
                  if (got[1] === "?"  &&  ! got[2]) {
                     got[1]  =  "";
                  }
                  r[2]   =  r[2].replace(WLINK.re.uselang, got[1]);
                  stuff  =  r[2];
               }
            }
            if ( life ) {
               if ( stuff.indexOf( "title=" )  >=  0   ||
                    stuff.indexOf( "oldid=" )  >=  0 ) {
                  pars  =  { };
                  if ( typeof WLINK.re.titleID  !==  "object" ) {
                     WLINK.re.titleID  =  "^(" +
                                            "(.*&)" +
                                            "(title|oldid)=" +
                                            "([^&]+)&)";
                     WLINK.re.titleID  =  new RegExp( WLINK.re.titleID );
                  }
                  s    =  "&" + stuff + "&";
                  got  =  WLINK.re.titleID.exec( s );
                  if ( got ) {
                     pars[ got[ 3 ] ]  =  got[ 4 ];
                     s                 =  s.substr( got[ 1 ].length );
                     got  =  WLINK.re.titleID.exec( s );
                     if ( got ) {
                        pars[ got[ 3 ] ]  =  got[ 4 ];
                        s                 =  s.substr( got[ 1 ].length );
                     }
                  }
                  if (s === "&") {
                     learn  =  true;
                     life   =  false;
                     if (pars.oldid) {
                        r[2]  =  "Special:PermanentLink/" + pars.oldid;
                        if (pars.title) {
                           r[2]  =  r[2]  + "?title=" + pars.title;
                        }
                     } else {
                        r[2]  =  pars.title;
                     }
                  }
               } else {
                  life  =  false;
               }
               if (! learn  &&  access !== 2) {
                  learn  =  true;
               }
            }
            if (!  (life || learn)) {
               learn  =  (stuff.substr(0, 5)  ===  "wiki/");
               if (learn) {
                  r[2]  =  stuff.substr(5);
               }
               if (! r[2]) {   // on every project in any language
                  r[2]  =  "Main Page";   // default
               }
            }
         }
         if (life) {
            s  =  ".org/";
            if (r[2]) {
               s  =  s + r[2];
            }
            if (r[1] === "") {
               if (r[0] === "wikimedia") {
                  r  =  r[0] + ".wikimedia" + s;
               } else if (r[0] === "mediawiki") {
                  r  =  "www.mediawiki" + s;
               } else {
                  r  =  false;
               }
            } else if (r[1]) {
               r  =  r[1] + "." + r[0] + s;
            } else {
               r  =  r[0] + s;
            }
            if (r) {
               r  =  "//" + r;
            }
         } else if (r) {
            mode  =  access;
            if ( typeof WLINK.re.urlpar  !==  "object" ) {
               WLINK.re.urlpar  =  ".+\\?[a-z]+=(.?)";
               WLINK.re.urlpar  =  new RegExp( WLINK.re.urlpar, "i" );
            }
            got  =  WLINK.re.urlpar.exec(r[2]);
            if (got) {
            } else if (r[0] === "wikimedia") {
               s  =  "|" + r[1] + "|";
               if (WLINK.protocol.secure.indexOf(s) >= 0) {
                  if (mode === 8) {
                     r  =  false;
                  } else {
                     s  =  r[2];
                     r  =  "https://" + r[1] + ".wikimedia.org";
                     if (s) {
                        r  =  r + "/" + s;
                     }
                  }
                  mode  =  false;
               }
            } else if (r[0] === "commons") {
               mode  =  false;
            } else if (r[0] === "mediawiki") {
               mode  =  false;
               r[0]  =  "mw";
            } else if (r[0] === "meta") {
               mode  =  false;
            } else if (r[1] === "OldWikisource") {
               mode  =  false;
            }
            if (mode === 2) {
               r  =  false;
            } else if (mode) {
               if (r[0] === "wikimedia") {
                  s  =  "|" + r[1] + "|";
                  if (WLINK.protocol.relative.indexOf(s)  >=  0) {
                     s  =  r[2];
                     r  =  "//" + r[1] + ".wikimedia.org";
                     if (s) {
                        r  =  r + "/" + s;
                     }
                  }
               }
            }
            if ( typeof r  ===  "object" ) {
               if ( learn ) {
                   s  =  this.target(r[2], true);
                   if (s) {
                      r[2]  =  s;
                   }   // decoded
               } else if (r[1]) {
                   r  =  "//" + r[1] + "." + r[0] + ".org/" + r[2];
               }
            }
         }
         if ( typeof r  ===  "object" ) {
            show    =  about;
            sister  =  false;
            slang   =  false;
            stuff   =  r[2];
            j       =  stuff.indexOf(":");
            if (r[0]) {
               sister  =  r[0];
               s       =  WLINK.projects.friend(sister, false);
               if ( s ) {
                  if ( s[ 0 ] !== 1  &&
                       WSTM.g.projType === "wikipedia" ) {
                     slang  =  ":en:";
                  }
                  sister =  s[1];
                  if (sister) {
                     r[0]  =  sister + ":";
                  } else {
                     r[0]  =  "";
                  }   // prefix required
               }   // project identified
            }   // sister
            if (r[1]) {
               slang  =  r[1];
               if (slang === WSTM.g.projLang) {
                  slang  =  false;
               } else if (sister) {
                  r[0]  =  r[0] + slang + ":";
               } else if (slang === "OldWikisource") {
                  r[0]  =  "OldWikisource:";
               } else {
                  r[0]  =  ":" + slang + ":";
               }
            }
            n  =  stuff.indexOf(":");
            if (n > 2) {   // maybe File: etc.
               scope  =  stuff.substr(0, n);
               key    =  WLINK.namespace.furnish(scope, slang, sister);
               if (key) {
                  stuff  =  WSTM.str.trimL(stuff.substr(n + 1),  true);
                  if (! show) {
                     show  =  stuff;
                  }
                  stuff  =  WSTM.str.camelCasing(stuff);
                  s      =  WLINK.namespace.fetch(key, slang);
                  if (s) {
                     scope  =  s;
                  }
                  stuff  =  scope + ":" + stuff;
                  if ((key === NAMESPACE.nsFile  ||
                       key === NAMESPACE.nsCategory)
                      &&   r[0] === "") {
                     stuff  =  ":" + stuff;
                  }   // own DB itself
               }
            }
            r[1]  =  stuff;
            n     =  stuff.indexOf("|");
            if (show) {
               s  =  WSTM.str.trimL(show,  true);
            } else {
               WSTM.mod.lazy  =  false;
            }
            if (n < 0) {
               r[2]  =  (show  ?  s  :  stuff);
            } else {
               WSTM.mod.lazy  =  false;
               r[1]           =  stuff.substr(0, n);
               stuff          =  WSTM.str.trimL(stuff.substr(n + 1),
                                                true);
               if (show) {
                  if (stuff.length > 0) {
                     stuff  =  stuff + " " + s;
                  } else {
                     stuff  =  s;
                  }
               }
               r[2]  =  stuff;
            }
            n  =  r[2].indexOf("?title=");
            if (n > 0) {
               r[2]  =  r[2].substr(n + 7);
            }
         }   // replace target by wikilink
      }   // replace
      return  r;
   };   // .w.link.wiki.url()



};   // .bb.link()
mw.libs.WikiSyntaxTextMod.bb.link(mw.libs.WikiSyntaxTextMod);
delete mw.libs.WikiSyntaxTextMod.bb.link;



//-----------------------------------------------------------------------



( function ( WSTM ) {
   "use strict";
   var sub      =  "H",
       self     =  WSTM.w.link.self,
       version  =  WSTM.w.link.vsn,
       rls;
   if ( typeof WSTM.main  ===  "object"
        &&     WSTM.main   &&
        typeof WSTM.main.wait  ===  "function" ) {
      // Start on import: callback to waiting ...
      WSTM.main.wait( sub, version );
   } else if ( typeof mw.loader  ===  "object"   &&
               typeof mw.hook  !==  "undefined" ) {
      rls = { };
      rls[ self ] = "ready";
      mw.loader.state( rls );
      mw.hook( "WikiSyntaxTextMod/" + sub + ".ready" )
        .fire( [ sub, version ] );
   }
} ( mw.libs.WikiSyntaxTextMod ) );



// Emacs
// Local Variables:
// coding: utf-8-dos
// fill-column: 80
// End:

/// EOF </nowiki>   WikiSyntaxTextMod/dH.js