User:PerfektesChaos/js/jsonXMLutils/d.js

From Wikipedia, the free encyclopedia
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.
/// jsonXMLutils.js
//  Help on pages with JSON or XML code
/// 2018-08-26 PerfektesChaos@de.wikipedia
//  ResourceLoader: compatible;
//    dependencies: user, mediawiki.util
//  Documentation:  [[w:en:User:PerfektesChaos/js/jsonXMLutils]]
/// Fingerprint:    #0#0#
/// <nowiki>
/*global window:false, JSON:false                                      */
/* jshint forin:false,
          bitwise:true, curly:true, eqeqeq:true, latedef:true,
          laxbreak:true,
          nocomma:true, strict:true, undef:true, unused:true           */
( function ( mw, $ ) {
   "use strict";
   var Version   = -1.8,
       Sign      = "jsonXMLutils",
       Shop      = "w:en:User:PerfektesChaos/js/",
       Spooling  = "//upload.wikimedia.org/wikipedia/commons"
                   + "/d/de/Ajax-loader.gif",   // throbber URL
       Editor    = {  },
       Hilite    = {  },
       I18N      = {  },
       TData     = {  },
       TDJPP     = {  },
       TDtxt     = {  },
       Env, JXU;



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



   TData.vorlagenmeister  =  "|dewiki|dewikivoyage|huwiki|";
   TDJPP.templateJSON     =  "|dewiki|";



   Hilite.dewiki  =  function ( assign ) {
      // Adapt highlighted source code to local wiki project
      // Precondition:
      //    assign  -- string, with language code as of GeSHi
      //    mediawiki.util loaded
      // Uses:
      //    >  Hilite.$highlight
      //    mw.util.addCSS()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var style, $box;
      if ( assign === "xml" ) {
         style =   ".source-xml {"
                 +     "font-size: 90%;"
                 + "}";
         $box  = $( "#intro-xml-page" );
         mw.util.addCSS( style );
         $box.detach();
         $box.css( { "margin-bottom": "1em",
                     "margin-top":    "1em",
                     "position":      "relative",
                     "top":           "auto"
                   } );
         Hilite.$highlight.eq( 0 ).before( $box );
         $( "#mw-content-text" ).empty();
      }
   };   // Hilite.dewiki()



   TData.dewiki  =  function ( action, adjust ) {
      // Adapt created XML code to local wiki project
      // Precondition:
      //    action  -- string, with "2xml"
      //    adjust  -- string, with JSON source
      // Uses:
      //    >  Env
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var s = Env.wgTitle.replace( /\/.*$/, "" );
      return '<?mediawiki "{{XML-Warnung|' + s + '}}"?>\n' + adjust;
   };   // TDJPP.dewiki()



   TData.huwiki  =  function ( action, adjust ) {
      // Adapt created XML code to local wiki project
      // Precondition:
      //    action  -- string, with "2xml"
      //    adjust  -- string, with JSON source
      // Uses:
      //    >  Env
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var s = Env.wgTitle.replace( /\/.*$/, "" );
      return '<?mediawiki "{{XML|' + s + '}}"?>\n' + adjust;
   };   // TDJPP.huwiki()



   TDJPP.otherLanguages = [ "simple", "tokipona" ];



   I18N.texts = {
      btn_Submit:        { en: "Submit",
                           de: "Anwenden" },
      btn_TD_0:          { en: "TemplateData generation",
                           de: "TemplateData generieren" },
      btn_TD_00:         { en: "TemplateData generator",
                           de: "TemplateData Generator" },
      btn_TD_PP:         { en: "TemplateData pretty print",
                           de: "TemplateData formatieren" },
      btn_TD2XML:        { en: "TemplateData to XML",
                           de: "TemplateData zu XML" },
      btn_XML2TD:        { en: "XML to TemplateData",
                           de: "XML zu TemplateData" },
      PP_DescInvalid:    { en: "ERROR:"
                               + " description text is not a string",
                           de: "FEHLER: Beschreibungstext"
                               + " ist keine Zeichenkette" },
      PP_EqualSign:      { en: "STRANGE!"
                               + " Equal sign '=' in parameter name."
                               + " This will not work on transclusion.",
                           de: "WIRR!"
                               + " Gleichheitszeichen '='"
                               + " im Parameternamen."
                               + " Das wird bei der Einbindung"
                               + " nicht funktionieren." },
      PP_H2:             { en: "Recommendations and errors",
                           de: "Empfehlungen und Fehlermeldungen" },
      PP_InvalidType:    { en: "ERROR: Invalid type of a component",
                           de: "FEHLER: Unerlaubter Datentyp"
                               + " einer Komponente." },
      PP_LabelLong:      { en: "label seems to be rather long",
                           de: "<code>label</code>"
                               + " scheint recht lang zu sein." },
      PP_LanguageCode:   { en: "WARNING: Invalid language code",
                           de: "WARNUNG: Fehlerhafter Sprachcode" },
      PP_Lethal:         { en: "ERROR: Severe problem detected",
                           de: "FEHLER: Erhebliches Problem gefunden" },
      PP_Lethals:        { en: "ERROR: Severe problems detected",
                           de: "FEHLER: Erhebliche Probleme gefunden" },
      PP_MandParams:     { en: "ERROR: mandatory 'params' missing",
                           de: "FEHLER:"
                               + " Pflichtparameter <code>params</code>"
                               + " nicht gefunden" },
      PP_ParamDup:       { en: "ERROR: Duplicated parameter name",
                           de: "FEHLER: Doppelter Parametername" },
      PP_PipeSymbol:     { en: "STRANGE!"
                               + " Pipe symbol <code>|</code>"
                               + " in parameter name."
                               + " This will not work on transclusion.",
                           de: "WIRR!"
                               + " Pipe-Symbol <code>|</code>"
                               + " im Parameternamen."
                               + " Das wird bei der Einbindung"
                               + " nicht funktionieren." },
      PP_QuotMark:       { en: "STRANGE!"
                               + " Quotation mark <code>\"</code>"
                               + " in parameter name.",
                           de: "WIRR!"
                               + " Begrenzer <code>\"</code>"
                               + " im Parameternamen." },
      PP_RequiredAuto:   { en: "CONFLICT"
                               + " Both 'required' and 'autovalue'.",
                           de: "Konfllkt:"
                               + " Sowohl 'required'"
                               + " als auch 'autovalue'." },
      PP_ShortDesc:      { en: "description quite short",
                           de: "Der Beschreibungstext"
                               + " scheint recht kurz zu sein." },
      PP_TypeDeprecated: { en: "DEPRECATED: Parameter 'type';"
                               + " remove <code>string/</code>",
                           de: "VERALTET (Parameter <code>type</code>):"
                               + " vorangestelltes"
                               + " <code>string/</code> entfernen" },
      PP_TypeUnknown:    { en: "WARNING: Parameter 'type'"
                               + " value not recognized",
                           de: "WARNUNG: Parameter <code>type</code> *"
                               + " Wert nicht erkannt" },
      PP_UnknownItem:    { en: "WARNING: Could not recognize",
                           de: "WARNUNG: Komponente nicht erkannt" }
                };   // 2015-01-11 PerfektesChaos@de.wikipedia



   I18N.translate = { // fallback languages
                      "als" :       "de",
                      "bar" :       "de",
                      "dsb" :       "de",
                      "frr" :       "de",
                      "gsw" :       "de",
                      "hsb" :       "de",
                      "ksh" :       "de",
                      "lb" :        "de",
                      "nds" :       "de",
                      "pdc" :       "de",
                      "pdt" :       "de",
                      "pfl" :       "de",
                      "sli" :       "de",
                      "stq" :       "de",
                      "vmf" :       "de" };



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



   function face() {
      // Initialize environment and application object, if user defined
      // Precondition:
      //    module 'user' loaded
      // Postcondition:
      //
      // Uses:
      //    >  mw.libs
      //    >  Sign
      //    >  Version
      //    >  Shop
      //    >< JXU
      // 2014-12-27 PerfektesChaos@de.wikipedia
      if ( typeof mw.libs[ Sign ]  ===  "object"   &&
           mw.libs[ Sign ]   &&
           ! JXU ) {
         JXU     = mw.libs[ Sign ];
         JXU.vsn = Version;
         JXU.doc = "[[" + Shop + Sign + "]]";
      }
   }   // face()



   function facet( applied ) {
      // Check for user option
      // Precondition:
      //    applied  -- option keyword
      //    module 'user' loaded
      // Postcondition:
      //    Returns value, or undefined
      // Uses:
      //    >  JXU
      //    face()
      // 2014-12-27 PerfektesChaos@de.wikipedia
      var r;   // = undefined
      face();
      if ( JXU   &&   typeof JXU[ applied ]  !==  "undefined" ) {
         r = JXU[ applied ];
      }
      return r;
   }   // facet()



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



   Editor.fiat = function () {
      // Start CodeEditor
      // Uses:
      //    >  Editor.sourcemods
      //    mw.loader.load()
      //    mw.config.set()
      // 2014-12-27 PerfektesChaos@de.wikipedia
      mw.loader.load( Editor.sourcemods + "mode-xml.js" );
      // Editor.finish()    require()
      mw.config.set("wgCodeEditorCurrentLanguage", "xml");
   };   // Editor.fiat()



   Editor.fire = function () {
      // Prepare CodeEditor
      // Uses:
      //     < Editor.sourcebase
      //     < Editor.sourcemods
      //    mw.loader.using()
      //    (Editor.fiat)
      // 2014-12-27 PerfektesChaos@de.wikipedia
      this.sourcebase = "mediawiki/extensions/CodeEditor";
      this.sourcebase = this.sourcebase.replace( /\//g, "%2F" );
      this.sourcebase = "https://git.wikimedia.org/raw/"
                        + this.sourcebase + "/HEAD/";
      this.sourcemods = "modules/ace/";
      this.sourcemods = this.sourcemods.replace( /\//g, "%2F" );
      this.sourcemods = this.sourcebase + this.sourcemods;
      mw.loader.using( [ "ext.codeEditor",
                         "ext.codeEditor.ace",
                         "jquery.ui",
                         "ext.wikiEditor" ],
                       Editor.fiat );
   };   // Editor.fire()



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



   Hilite.fair  =  function ( adjust, assign, after, abort ) {
      // Fetch highlighted source code
      // Precondition:
      //    adjust  -- string, with source code
      //    assign  -- string, with language code as of pygments
      //    after   -- function, called later with the formatted string
      //    abort   -- optional function, called on failure
      //               false to die silently
      //    mediawiki.api loaded
      // Postcondition:
      //    Calls, function with formatted JSON source code
      // Uses:
      //    >< Hilite.api
      //    mw.Api()
      // 2018-08-26 PerfektesChaos@de.wikipedia
      var failed = abort,
          fine   = function ( a ) {
                       if ( typeof a  ===  "object"   &&
                            typeof a.parse  ===  "object"   &&
                            typeof a.parse.text  ===  "object"   &&
                            typeof a.parse.text[ "*" ]  ===  "string" ) {
                          after( a.parse.text[ "*" ] );
                       } else if ( typeof abort  ===  "function" ) {
                          abort( "API: Strange answer" );
                       } else {
                          after( "<pre>" + adjust + "</pre>" );
                       }
                   },
          source = "<syntaxhighlight lang='" + assign + "'"
                   + " id='" + Sign + "-hilite-" + assign + "'>"
                   + adjust + "</syntaxhighlight>";
      if ( failed === false ) {
         failed = function () { };
      } else if ( typeof failed  !==  "function" ) {
         failed = function () { after( "<pre>" + adjust + "</pre>" ); };
      }
      if ( typeof Hilite.api  !==  "object" ) {
         Hilite.api = new mw.Api();
      }
      Hilite.api.post( { action:       "parse",
                         contentmodel: "wikitext",
                         disablepp:    true,
                         prop:         "text",
                         text:         source
                       } )
                .done( fine )
                .fail( failed );
      mw.loader.load( [ "ext.pygments" ] );
   };   // Hilite.fair()



   Hilite.fetch  =  function () {
      // Fetch highlighted XML source code
      // Precondition:
      //    mediawiki.api mediawiki.util loaded
      // Uses:
      //    >  Hilite.source
      //    Hilite.fair()
      //    mw.util.addCSS()
      //    (Hilite.fetched)
      // Remark: Used as event handler -- 'this' is not Hilite
      // 2014-12-30 PerfektesChaos@de.wikipedia
      var style  =   ".mw-code {"
                   +    "padding-top: 0.5em;"
                   + "}"
                   + ".source-xml {"
                   +    "font-size:      90%;"
                   +    "overflow:       auto;"
                   +    "padding-bottom: 1em;"
                   + "}";
      Hilite.fair( Hilite.source, "xml", Hilite.fetched, false );
      mw.util.addCSS( style );
   };   // Hilite.fetch()



   Hilite.fetched  =  function ( arrived ) {
      // Fetched highlighted XML source code after query
      // Precondition:
      //    arrived  -- string, with HTML
      // Uses:
      //    >  Hilite.remove
      //    >  Env
      //     < Hilite.$highlight
      // Remark: Used as event handler -- 'this' is not Hilite
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var i, $h1;
      for ( i = 0;  i < Hilite.remove.length;  i++ ) {
         Hilite.remove[ i ].remove();
      }   // for i
      Hilite.$highlight = $( arrived );
      $h1 = $( "#firstHeading" );
      $h1.after( Hilite.$highlight );
      if ( ! Env.wgDBname ) {
         Env.wgDBname = mw.config.get( "wgDBname" );
      }
      if ( typeof Hilite[ Env.wgDBname ]  ===  "function" ) {
         Hilite[ Env.wgDBname ]( "xml" );
      }
   };   // Hilite.fetched()



   Hilite.fire = function () {
      // Start XML source code highlighting
      // Precondition:
      //    mediawiki.api mediawiki.util loaded
      // Uses:
      //    >  mw.util.$content
      //     < Hilite.remove
      //     < Hilite.source
      //    mw.loader.using()
      //    (Hilite.fetch)
      // Remark: Used as event handler -- 'this' is not Hilite
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var large  = facet( "hiliteLarge" ),
          $paras = mw.util.$content.find( "p,pre" ),
          i, j, s, segment, start, $para;
      Hilite.remove = [ ];
      Hilite.source = false;
      for ( i = 0;  i < $paras.length;  i++ ) {
         $para   = $paras.eq( i );
         segment = $para.text();
         if ( segment ) {
            j = segment.indexOf( "<" );
            if ( ! j ) {
               start = segment;
            } else if ( j > 0 ) {
               start = segment.substr( j );
               s     = segment.substr( 0, j ).replace( /\s+/g, "" );
               j     = s.length;
            }
            if ( ! j ) {
               if ( start.substr( 0, 2 )  !==  "<?"   ||   large ) {
                  if ( Hilite.source ) {
                     Hilite.source = Hilite.source + "\n" + segment;
                  } else {
                     Hilite.source = segment;
                  }
               }
               Hilite.remove.push( $para );
            }
         }
      }   // for i
      if ( Hilite.source ) {
         mw.loader.using( [ "mediawiki.api",
                            "ext.geshi.language.xml",
                            "ext.geshi.local" ],
                          Hilite.fetch );
      }
   };   // Hilite.fire()



   Hilite.flow = function ( $around ) {
      // Create and show throbber
      // Precondition:
      //    $around  -- jQuery container object
      // Postcondition:
      //    throbber visible
      // Uses:
      //    >  Spooling
      // 2015-01-12 PerfektesChaos@de.wikipedia
      var $img;
      if ( ! $around.children().length ) {
         $img = $( "<img />" );
         $img.attr( { src:    Spooling,
                      id:     "ajax_throbber",
                      height: "20",
                      alt:    "Ajax" } );
         $img.css( { "margin-left": "10px" } );
         $around.append( $img );
      }
      $around.show();
   };   // Hilite.flow()



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



   I18N.facility = function ( available ) {
      // Localize in user language, GUI only
      // Precondition:
      //    available  -- translation object
      // Postcondition:
      //    Returns string, or undefined
      // Uses:
      //    >  I18N.translate
      //    >< Env
      //    mw.config.get()
      // 2014-12-16 PerfektesChaos@de.wikipedia
      var i, r, slang;
      if ( ! Env.userLang ) {
         Env.userLang = mw.config.get( "wgUserLanguage" ).toLowerCase();
      }
      slang = Env.userLang;
      if ( typeof available[ slang ]  ===  "string" ) {
         r = available[ slang ];
      } else {
         i = slang.indexOf( "-", 2 );
         if ( i > 0 ) {
            slang = slang.substr( 0, i );
            if ( typeof available[ slang ]  ===  "string" ) {
               r = available[ slang ];
            }
         }
      }
      if ( ! r ) {
         if ( typeof I18N.translate[ slang ]  ===  "string" ) {
            slang = I18N.translate[ slang ];
            if ( typeof available[ slang ]  ===  "string" ) {
               r = available[ slang ];
            }
         }
         if ( ! r ) {
            r = available.en;
         }
      }
      return r;
   };   // I18N.facility()



   I18N.fair = function ( access, adapt, alter ) {
      // Localization
      // Precondition:
      //    access  -- configuration keyword
      //    adapt   -- false, if project (language) dependant
      //               else user language dependant
      //    alter   -- default string (en)
      // Postcondition:
      //    Returns value, or keyword 'access'
      // Uses:
      //    >  JXU
      //    >  I18N.texts
      //    >< Env
      //    face()
      //    I18N.facility()
      //    mw.config.get()
      // 2014-12-20 PerfektesChaos@de.wikipedia
      var el, r, say;
      if ( alter ) {
         r = alter;
      } else {
         r = access;
      }
      face();
      if ( JXU   &&
           typeof JXU.config  ===  "object"   &&
           JXU.config   &&
           typeof JXU.config[ access ]  ===  "object" ) {
         el = JXU.config[ access ];
      }
      if ( ! el ) {
         el = I18N.texts[ access ];
      }
      if ( el ) {
         if ( adapt ) {
            say = I18N.facility( el );
            if ( say ) {
               r = say;
            } else if ( typeof alter  !==  "string" ) {
               r = "???" + access + "???";
            }
         } else {
            if ( ! Env.wgDBname ) {
               Env.wgDBname = mw.config.get( "wgDBname" );
            }
            if ( typeof el[ Env.wgDBname ]  ===  "string" ) {
               r = el[ Env.wgDBname ];
            } else {
               if ( ! Env.wgContentLanguage ) {
                  Env.wgContentLanguage =
                                    mw.config.get( "wgContentLanguage" );
               }
               if ( typeof el[ Env.wgContentLanguage ]  ===  "string" ) {
                  r = el[ Env.wgContentLanguage ];
               }
            }
         }
      } else if ( adapt ) {
         r = "???" + access + "???";
      }
      return r;
   };   // I18N.fair()



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



   TData.fire = function () {
      // Start TemplateData handling
      // Precondition:
      //    mediawiki.api loaded
      // Uses:
      //    >  Env
      //    (TData.feed)
      //    (TData.fault)
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var rq = { action: "templatedata" };
      rq.titles = "Template:" + Env.wgTitle.replace( /\/.*$/, "" );
      TData.api = new mw.Api();
      TData.api.get( rq ).done( TData.feed )
                         .fail( TData.fault );
   };   // TData.fire()



   TData.factory = function () {
      // Create Vorlagenmeister XML from TemplateData JSON
      // Uses:
      //    >  TData.templateData
      //    >  Env
      //    >  TData.$throbber
      //     < TData.lead
      //    TData.feature()
      //    TDJPP.fence()
      //    Hilite.fair()
      //    Hilite.flow()
      //    (TData.fiat)
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var p, params, s, sign, story;
      if ( TData.templateData ) {
         s = ' <TemplateUsage output="collapse">\n'
             + '   <Group>\n';
         if ( typeof TData.templateData.params  ===  "object"   &&
              TData.templateData.params ) {
            params = TData.templateData.params;
            for ( sign in params ) {
               if ( sign   &&
                    typeof params[ sign ]  ===  "object" ) {
                  s     = s + '     <Parameter name="' + sign + '"';
                  p     = params[ sign ];
                  story = TData.feature( p, "label" );
                  if ( story ) {
                     s = s + ' label="'
                           + story.replace( /</g, "&lt;" )
                                  .replace( />/g, "&gt;" )
                           + '"';
                  }
                  if ( typeof p.required  ===  "boolean"   &&
                       p.required ) {
                     s = s + ' null="false"';
                  }
                  if ( typeof p.type  ===  "string" ) {
                     story = TDJPP.fence( p.type, false );
                     if ( story !== "unknown" ) {
                        s = s + ' type="' + story + '"';
                     }
                  }
                  s = s + '>\n';
                  story = TData.feature( p, "description" );
                  if ( story ) {
                     s = s + '       <Help>'
                         + story.replace( /</g, "&#60;" )
                                .replace( />/g, "&#62;" )
                         +  '</Help>\n';
                  }
                  if ( typeof p[ "default" ]  ===  "string" ) {
                     story = p[ "default" ].replace( /^\s+/, "" )
                                           .replace( /\s+$/, "" );
                     if ( story ) {
                        s = s + '       <Default>'
                           + story.replace( /</g, "&lt;" )
                           + '</Default>\n';
                     }
                  }
                  if ( typeof p.autovalue  ===  "string" ) {
                     story = p.autovalue.replace( /^\s+/, "" )
                                        .replace( /\s+$/, "" );
                     if ( story ) {
                        s = s + '       <Autovalue>'
                           + story.replace( /</g, "&lt;" )
                           + '</Autovalue>\n';
                     }
                  }
                  s = s + '     </Parameter>\n';
               }
            }   // for  in .params
         }
         s = s
             + '   </Group>\n'
             + ' </TemplateUsage>';
         if ( ! Env.wgDBname ) {
            Env.wgDBname = mw.config.get( "wgDBname" );
         }
         if ( typeof TData[ Env.wgDBname ]  ===  "function" ) {
            s = TData[ Env.wgDBname ]( "2xml", s );
         }
         s = '<?xml version="1.0" encoding="utf-8"?>\n' + s;
         Hilite.fair( s, "xml", TData.fiat, false );
         Hilite.flow( TData.$throbber );
         TData.lead = true;
      }
   };   // TData.factory()



   TData.fault = function ( arrived ) {
      // Error on ajax query
      // Precondition:
      //    arrived  -- result of ajax query
      // Uses:
      //    window.console.log()
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-11 PerfektesChaos@de.wikipedia
      window.console.log( arrived );
      window.console.dir( arrived );
   };   // TData.fault()



   TData.feature = function ( assembly, access ) {
      // Retrieve best fit of interface in project language
      // Precondition:
      //    assembly  -- parameter
      //    access    -- component name
      // Postcondition:
      //    Returns value, or keyword 'access'
      // Uses:
      //    >< Env
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var r, s, slang, t;
      switch ( typeof assembly[ access ] ) {
         case "object" :
            if ( typeof Env.wgContentLanguage  !==  "string" ) {
               Env.wgContentLanguage =
                                    mw.config.get( "wgContentLanguage" );
            }
            t = assembly[ access ];
            if ( t ) {
               if ( typeof t[ Env.wgContentLanguage ]  ===  "string" ) {
                  r = t[ Env.wgContentLanguage ];
               }
               if ( ! r ) {
                  slang = Env.wgContentLanguage + "-";
                  for ( s in t ) {
                     if ( ! s.indexOf( slang ) ) {
                        r = t[ s ];
                        if ( r ) {
                           break;   // for s in t
                        }
                     }
                  }   // for s in t
               }
            }
            break;
         case "string" :
            r = assembly[ access ];
            break;
      }   // switch typeof component
      if ( r ) {
         r = r.replace( /^\s+/, "" )
              .replace( /\s+$/, "" )
              .replace( /\\/g, "\\" );
      }
      return r;
   };   // TData.feature()



   TData.feed = function ( arrived ) {
      // Ajax query successful
      // Precondition:
      //    arrived  -- JSON result of ajax query
      // Uses:
      //    >  Env
      //    >  TData.vorlagenmeister
      //     < TData.learn
      //     < TData.live
      //     < TData.lmx
      //    TData.fault()
      //    jQuery()
      //    (TData.furnish)
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-19 PerfektesChaos@de.wikipedia
      var page, suitable;
      if ( typeof arrived.pages  ===  "object" ) {
         TData.templateData = false;
         for ( page in arrived.pages ) {
            if ( TData.templateData ) {
               TData.fault( arrived );   // STRANGE. Multiple.
            } else {
               TData.templateData = arrived.pages[ page ];
            }
         }   // for page in arrived.pages
         suitable    = "|edit|submit|";
         TData.learn = ( suitable.indexOf( Env.wgAction )  >=  0 );
         if ( TData.templateData ) {
            TData.live = true;
            if ( ! Env.wgDBname ) {
               Env.wgDBname = mw.config.get( "wgDBname" );
            }
            suitable = "|" + Env.wgDBname + "|";
            TData.lmx = ( TData.vorlagenmeister.indexOf( suitable )
                          >=  0   ||
                          facet( "vorlagenmeister" ) );
         }
         $( TData.furnish );
      } else {
         TData.fault( arrived );
      }
   };   // TData.feed()



   TData.fetch = function () {
      // Retrieve JSON object from edit form
      // Precondition:
      //    DOM.ready
      // Uses:
      //    >  mw.util.$content
      //    TData.$field()
      //    JSON.parse()
      //    TDJPP.flush()
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var $ta = TData.$field(),
          got, r, s;
      if ( $ta ) {
         s   = $ta.val();
         got = /<\s*templatedata\s*>/i.exec( s );
         if ( got ) {
            s   = s.substr( got.index + got[ 0 ].length );
            got = /}\s*<\s*\/\s*templatedata\s*>/i.exec( s );
            if ( got ) {
               s = s.substr( 0,  got.index + 1 );
               try {
                  r = JSON.parse( s );
               } catch ( e ) {
                  TDJPP.flush();
                  r = "<strong style='color:#FF0000'>"
                      + "ERROR in JSON"
                      + "</strong>";
               }
            }
         }
      }
      return r;
   };   // TData.fetch()



   TData.fiat = function ( arrived ) {
      // Show highlighted generated XML code
      // Precondition:
      //    arrived  -- string, with HTML
      // Uses:
      //    >  TData.$throbber
      //    TData.$fold()
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-12 PerfektesChaos@de.wikipedia
      var $highlight = $( arrived ),
          $div       = TData.$fold();
      $div.append( $highlight );
      TData.$throbber.hide();
  };   // TData.fiat()



   TData.$field = function () {
      // Retrieve $textarea object from edit form
      // Precondition:
      //    DOM.ready
      // Postcondition:
      //    Returns jQuery object, with $textarea, or false
      // Uses:
      //    this
      //    >  mw.util.$content
      //    >< TData.$textarea
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var $r = false,
          $form;
      if ( this.$textarea ) {
         $r = this.$textarea;
      } else {
         $form = mw.util.$content.find( "#editform" );
         if ( $form.length ) {
            this.$textarea = $form.find( "#wpTextbox1" );
            if ( this.$textarea.length ) {
               $r = this.$textarea;
            } else {
               this.$textarea = false;
            }
         }
      }
      return $r;
   };   // TData.$field()



   TData.fine = function () {
      // Show pretty printed highlighted JSON
      // Uses:
      //    >  TData.templateData
      //    >  TData.$throbber
      //    >< TData.lead
      //    mw.loader.load()
      //    TData.fetch()
      //    TDJPP.facility()
      //    Hilite.fair()
      //    Hilite.flow()
      //    (TData.finish)
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var td;
      if ( TData.templateData  &&  TData.lead ) {
         td         = TData.templateData;
         TData.lead = false;
      } else if ( TData.learn ) {
         td = TData.fetch();
      }
      if ( td ) {
         Hilite.fair( TDJPP.facility( td, 3 ),
                      "javascript",
                      TData.finish,
                      false );
         Hilite.flow( TData.$throbber );
      }
   };   // TData.fine()



   TData.finish = function ( arrived ) {
      // Show highlighted pretty printed JSON code
      // Precondition:
      //    arrived  -- string, with HTML
      // Uses:
      //    >  TData.$throbber
      //    TData.$fold()
      //    TDJPP.$failures()
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var $highlight = $( arrived ),
          $div       = TData.$fold(),
          $report    = TDJPP.$failures();
      if ( $report ) {
         $div.append( $report );
      }
      $div.append( $highlight );
      TData.$throbber.hide();
   };   // TData.finish()



   TData.$fold = function () {
      // Create or clear code area
      // Postcondition:
      //    Returns empty jQuery code block
      // Uses:
      //    this
      //    >  Sign
      //    >< TData.$div
      // 2015-01-20 PerfektesChaos@de.wikipedia
      if ( typeof this.$div  ===  "object" ) {
         this.$div.empty();
      } else {
         this.$div = $( "<div />" );
         this.$div.attr( { "id":  Sign + "-code" } );
         this.$div.css( { "margin-bottom": "1em",
                          "margin-top":    "1em" } );
         if ( this.learn ) {
            $( "#mw-content-text" ).prepend( this.$div );
         } else {
            $( "#mw-content-text" ).append( this.$div );
         }
      }
      return this.$div;
  };   // TData.$fold()



   TData.$form = function ( apply, $append ) {
      // Create and insert button
      // Precondition:
      //    apply    -- object, with specification
      //    $append  -- jQuery object, to append to
      //    DOM.ready
      // Postcondition:
      //    Returns jQuery button object
      // Uses:
      //    >  Sign
      //    I18N.fair()
      // 2015-01-21 PerfektesChaos@de.wikipedia
      var $r = $( "<button />" );
      $r.attr( { "id":  Sign + "-" + apply.sign } );
      $r.click( apply.click );
      $r.css( { "margin-left": "2em" } );
      if ( apply.i18n ) {
         $r.text( I18N.fair( apply.i18n, true, apply.i18n ) );
      }
      $append.append( $r );
      return $r;
   };   // TData.$form()



   TData.furnish = function () {
      // Equip page with appropriate buttons
      // Precondition:
      //    DOM.ready
      // Uses:
      //    >  TData.live
      //    >  TData.lmx
      //    >  TData.learn
      //    >  Sign
      //    >  TData.templateData
      //     < TData.$throbber
      //     < TData.lead
      //    TData.$form()
      //    (TData.fine)
      //    (TData.factory)
      //    (TData.fire)
      // Remark: Used as event handler -- 'this' is not TData
      // 2015-01-24 PerfektesChaos@de.wikipedia
      var $div = $( "<div />" ),
          button, $root;
      if ( TData.learn ) {
         $root = $( ".tdg-editscreen-main-helplink" );
         if ( ! $root.length ) {
            $root = false;
         }
      }
      if ( ! $root ) {
         $root = $( "#mw-content-text" );
      }
      $div.css( { "margin-bottom": "1em",
                  "margin-top":    "1em" } );
      $root.after( $div );
      if ( TData.live ) {
         button = { click: TData.fine,
                    i18n:  "btn_TD_PP",
                    sign:  "btn_TD_PP"
                  };
         TData.$form( button, $div );
         if ( TData.lmx ) {
            button = { click: TData.factory,
                       i18n:  "btn_TD2XML",
                       sign:  "btn_TD2XML"
                     };
            TData.$form( button, $div );
         }
      }
      button = { click: TDtxt.fire,
                 sign:  "btn_TD_0"
               };
      if ( TData.live ) {
         button.i18n = "btn_TD_00";
      } else {
         button.i18n = "btn_TD_0";
      }
      TData.$form( button, $div );
      TData.$throbber = $( "<span>" );
      $div.append( TData.$throbber );
      TData.lead = true;
  };   // TData.furnish()



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



   TDJPP.facility = function ( apply, adjust ) {
      // Start TemplateData JSON pretty printing
      // Precondition:
      //    apply   -- TemplateData object (JSON)
      //    adjust  -- handling of empty and basic fields
      //               0: as is in object apply
      //               1: ignore empty fields
      //               2: ensure basic fields
      //               3: ensure basic fields, discard other empty fields
      //               4: create all fields, even empty
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    >  Env
      //    >  TDJPP.templateJSON
      //    TDJPP.flush()
      //    TDJPP.description()
      //    TDJPP.params()
      //    TDJPP.sets()
      //    TDJPP.format()
      //    TDJPP.further()
      //    TDJPP.fields()
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var i = 0,
          k = 2,
          m = ( typeof adjust === "number"  ?  adjust  :  0 ),
          r = "",
          s;
      TDJPP.flush();
      s = this.description( apply, k, m );
      if ( s ) {
         r = s;
         i = k;
      }
      s = this.params( apply, k, i, m );
      if ( s ) {
         if ( r ) {
            r = r + ",\n  ";
         }
         r = r + s;
         i = k;
      }
      s = this.sets( apply, k, i, m );
      if ( s ) {
         if ( r ) {
            r = r + ",\n  ";
         }
         r = r + s;
      }
      s = this.format( apply, k, i, m );
      if ( s ) {
         if ( r ) {
            r = r + ",\n  ";
         }
         r = r + s;
      }
      r = "{ " + r + "\n}";
      if ( ! Env.wgDBname ) {
         Env.wgDBname = mw.config.get( "wgDBname" );
      }
      s = "|" + Env.wgDBname + "|";
      if ( typeof this[ Env.wgDBname ]  ===  "function" ) {
         r = this[ Env.wgDBname ]( r );
      } else if ( TDJPP.templateJSON.indexOf( s )  >=  0 ) {
         r = this.further( r );
      // 2018-08-24 PerfektesChaos@de.wikipedia
      }
      this.fields( apply,
                   [ "description", "params", "sets",
                     "maps", "format", "paramOrder", "title" ],
                   "<templatedata>" );
      return r;
   };   // TDJPP.facility()



   TDJPP.bad = [ "DescInvalid",
                 "InvalidType",
                 "MandParams",
                 "ParamDup",
                 "UnknownItem" ];   // Fatal errors
   TDJPP.extension = { "description":       [ true,  40, "ShortDesc" ],
                       "label":             [ false, 25, "LabelLong" ],
                       "param.description": [ true,  5, "ShortDesc" ]
                     };   // string length



   TDJPP.aliases = function ( array, adjust ) {
      // Pretty printing of TemplateData aliases Array
      // Precondition:
      //    array   -- Array of strings
      //    adjust  -- >=4: create empty basic field
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      // 2014-12-27 PerfektesChaos@de.wikipedia
      var r = "",
          i;
      if ( typeof array.length === "number"  &&
           array.length ) {
         for ( i = 0;  i < array.length;  i++ ) {
            if ( i ) {
               r = r + ", ";
            }
            r = r + this.fair( "" + array[ i ] );
         }   // for i
         r = "[ " + r + " ]";
      } else if ( adjust >= 4 ) {
         r = "[ ]";
      }
      return r;
   };   // TDJPP.aliases()



   TDJPP.description = function ( apply, align, adjust ) {
      // Pretty printing of TemplateData description component
      // Precondition:
      //    apply   -- TemplateData object
      //    align   -- number of spaces to indent consecutive lines
      //    adjust  -- >1: create empty basic field
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    TDJPP.folder()
      //    TDJPP.failed()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var s = typeof apply.description,
          r;
      switch ( s ) {
         case "object" :
         case "string" :
            r = this.folder( apply.description,
                             adjust,
                             align + 15,
                             "description" );
            break;
         case "undefined" :
            if ( adjust > 1 ) {
               r = '""';
            }
            break;
         default:
            this.failed( "InvalidType",  ".description: " + s );
            r = '"[' + s + ']"';
      }   // switch typeof description
      if ( r ) {
         r = '"description": ' + r;
      } else {
         r = "";
      }
      return r;
   };   // TDJPP.description()



   TDJPP.param = function ( apply, access, align, after, adjust ) {
      // Pretty printing of single TemplateData parameter component
      // Precondition:
      //    apply   -- TemplateData object
      //    access  -- string, with name of param
      //    align   -- maximum name length
      //               <0: line break
      //    after   -- break and spaces to indent consecutive lines
      //    adjust  -- handling of empty and basic fields
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    TDJPP.failed()
      //    TDJPP.fields()
      //    TDJPP.folder()
      //    TDJPP.fence()
      //    TDJPP.fair()
      //    TDJPP.aliases()
      //    TDJPP.fill()
      // 2016-01-29 PerfektesChaos@de.wikipedia
      var e   =  [ "label", "description", "type",
                   "required", "suggested", "default", "autovalue",
                   "example", "inherits", "deprecated", "aliases" ],
          max = 0,
          r   = "",
          i, n, p, s, slot, v;
      if ( access.indexOf( '"' )  >=  0 ) {
         this.failed( "QuotMark", access );
      } else if ( access.indexOf( "=" )  >=  0 ) {
         this.failed( "EqualSign", access );
      } else if ( access.indexOf( "|" )  >=  0 ) {
         this.failed( "PipeSymbol", access );
      }
      if ( typeof apply.params[ access ]  ===  "object" ) {
         p = apply.params[ access ];
      }
      if ( p ) {
         this.fields( p,  e,  "params:" + access );
         for ( i = 0;  i < e.length;  i++ ) {
            s  = e[ i ];
            if ( typeof p[ s ]  !==  "undefined" ) {
               v = p[ s ];
               if ( v  ||  adjust >= 3 ) {
                  switch ( s ) {
                     case "label" :
                        v = this.folder( v, adjust, after, 0, s );
                        break;
                     case "description" :
                     case "example" :
                        slot = "param." + s;
                        v    = this.folder( v, adjust, after, 0, slot );
                        break;
                     case "type" :
                        v = this.fence( "" + v, true );
                        // fall through
                     case "default" :
                     case "autovalue" :
                     case "inherits" :
                        v = this.fair( "" + v );
                        break;
                     case "required" :
                        if ( v   &&
                             typeof p.autovalue  ===  "string"   &&
                             p.autovalue ) {
                           this.failed( "RequiredAuto", access );
                        }   // fall through
                     case "suggested" :
                        if ( typeof v  ===  "boolean" ) {
                           v = ( v ? "true" : "false" );
                        } else {
                           v = "true";
                        }
                        break;
                     case "deprecated" :
                        if ( typeof v  ===  "boolean" ) {
                           v = ( v ? "true" : "false" );
                        } else {
                           v = "" + v;
                        }
                        break;
                     case "aliases" :
                        if ( typeof v  ===  "object" ) {
                           v = this.aliases( v );
                        } else {
                           v = "[ ]";
                        }
                        break;
                  }   // switch s
               }
            } else {
               v = undefined;
            }
            if ( ! v  &&  adjust >= 2 ) {
               switch ( s ) {
                  case "label" :
                  case "description" :
                     v = '""';
                     break;
                  case "type" :
                     v = '"unknown"';
                     break;
                  case "required" :
                     v = "false";
                     break;
               }   // switch s
            }
            if ( v ) {
               e[ i ] = [ s, v ];
               n = s.length;
               if ( n > max ) {
                  max = n;
               }
            }
         }   // for i
         for ( i = 0;  i < e.length;  i++ ) {
            p = e[ i ];
            if ( typeof p  ===  "object" ) {
               if ( r ) {
                  if ( i ) {
                     r = r + ",";
                  }
                  r = r + after;
                  if ( i ) {
                     r = r + "  ";
                  }
               }
               s = p[ 0 ];
               r = r + this.fair( s )  +  ":"
                     + this.fill( max - s.length + 1 )
                     + p[ 1 ];
            }
         }   // for i
         r = "{ " + r + " }";
         if ( align < 0 ) {
            r = after + r;
         } else {
            r = this.fill( align - access.length + 1 )  +  r;
         }
      } else {
         this.failed( "InvalidType",  "params:" + access );
         r = ' "?? InvalidType ??"';
      }
      r = this.fair( access ) + ":" + r;
      return r;
   };   // TDJPP.param()



   TDJPP.params = function ( apply, align, adjust ) {
      // Pretty printing of TemplateData params component
      // Precondition:
      //    apply   -- TemplateData object
      //    align   -- number of spaces to indent consecutive lines
      //    adjust  -- handling of empty and basic fields
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    $.inArray()
      //    TDJPP.failed()
      //    TDJPP.fill()
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var max = 0,
          r   = "",
          i, k, n, ord, s, shift;
      if ( typeof apply.params  ===  "object"   &&   apply.params ) {
         if ( typeof apply.paramOrder === "object"   &&
              apply.paramOrder   &&
              apply.paramOrder.length ) {
            ord = apply.paramOrder;
         } else {
            ord = [ ];
            for ( s in apply.params ) {
               s = "" + s;
               if ( $.inArray( s, ord )  <  0 ) {
                  ord.push( s );
               } else {
                  this.failed( "ParamDup", s );
               }
            }   // for s in apply.params
         }
         for ( i = 0;  i < ord.length;  i++ ) {
            n = ord[ i ].length;
            if ( n > max ) {
               max = n;
            }
         }   // for i
         n = align;
         if ( max > 3 ) {
            k  = -1;
            n += 6;
            s  = this.fill( align + 3 );
         } else {
            k  = max;
            n += max + 16;
            s  = this.fill( align + 12 );
         }
         shift = "\n" + this.fill( n );
         for ( i = 0;  i < ord.length;  i++ ) {
            if ( i ) {
               r = r + ",\n" + s;
            }
            r = r + this.param( apply, ord[ i ], k, shift, adjust );
         }   // for i
      } else {
         this.failed( "MandParams", "?!?!" );
      }
      r = '"params": {'
          + ( k < 0  ?  "\n     "  :  " " )
          + r + "\n"
          + this.fill( align + 10 )  +  "}";
      return r;
   };   // TDJPP.params()



   TDJPP.sets = function ( apply, align, adjust ) {
      // Pretty printing of TemplateData sets component
      // Precondition:
      //    apply   -- TemplateData object
      //    align   -- number of spaces to indent consecutive lines
      //    adjust  -- 4: create field in any case
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    TDJPP.fair()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var r = "",
          i;
      if ( typeof apply.sets === "object"
           &&     apply.sets  &&
           typeof apply.sets.length === "number"  &&
           apply.sets.length ) {
         for ( i = 0;  i < apply.sets.length;  i++ ) {
            if ( i ) {
               r = r + ", ";
            }
            r = r + this.fair( apply.sets[ i ] );
         }   // for i
         r = '"sets": [ ' + r + " ]";
      } else if ( adjust >= 4 ) {
         r = '"sets": [ ]';
      }
      return r;
   };   // TDJPP.sets()



   TDJPP.format = function ( apply /*, align */ ) {
      // Pretty printing of TemplateData sets component
      // Precondition:
      //    apply   -- TemplateData object
      //    align   -- number of spaces to indent consecutive lines
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    TDJPP.fair()
      // 2016-02-07 PerfektesChaos@de.wikipedia
      var r;
      if ( typeof apply.format === "string"
           &&     apply.format ) {
         if ( /\bblock\b/i.test( apply.format ) ) {
            r = '"block"';
         } else {
            r = '"inline"';
         }
         r = '"format": ' + r;
      } else {
         r = "";
      }
      return r;
   };   // TDJPP.format()



   TDJPP.failed = function ( alert, add ) {
      // Register complaint
      // Precondition:
      //    alert  -- string, with keyword
      //    add    -- string, with additional information
      // Postcondition:
      //    Memorized issue
      // Uses:
      //    this
      //    >< TDJPP.complaints
      // 2014-12-27 PerfektesChaos@de.wikipedia
      var cClass;
      if ( typeof this.complaints  !==  "object" ) {
         this.complaints = { };
      }
      if ( typeof this.complaints[ alert ]  !==  "object" ) {
         this.complaints[ alert ] = { };
      }
      cClass = this.complaints[ alert ];
      if ( typeof cClass[ add ]  ===  "number" ) {
         cClass[ add ] = cClass[ add ]++;
      } else {
         cClass[ add ] = 1;
      }
   };   // TDJPP.failed()



   TDJPP.$failures = function () {
      // Retrieve report
      // Precondition:
      //    TDJPP.facility() has been executed before
      // Postcondition:
      //    Returns jQuery object, or false
      // Uses:
      //    this
      //    >  TDJPP.complaints
      //    >  TDJPP.bad
      //    I18N.fair()
      //    $.inArray()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var m = 0,
          details, n, strange, subject,
          $code, $div, $h2, $li1, $li2, $span, $ul1, $ul2, $r;
      if ( typeof this.complaints  ===  "object" ) {
         $r = $( "<div />" );
         $r.attr( { "id":  Sign + "-JSON-report" } );
         $h2 = $( "<h2 />" );
         $h2.text( I18N.fair( "PP_H2", true ) );
         $r.append( $h2 );
         $ul1 = $( "<ul />" );
         for ( strange in this.complaints ) {
            details = this.complaints[ strange ];
            $li1    = $( "<li />" );
            $span   = $( "<span />" );
            if ( $.inArray( strange, TDJPP.bad )  >=  0 ) {
               m++;
               $span.css( { "color":       "#FF0000",
                            "font-weight": "bold" } );
            }
            $span.html( I18N.fair( "PP_" + strange,  true ) );
            $li1.append( $span );
            $ul2 = $( "<ul />" );
            for ( subject in details ) {
               $li2 = $( "<li />" );
               n    = details[ subject ];
               if ( n > 1 ) {
                  $span = $( "<span />" );
                  $span.css( { "margin-right": "2em" } );
                  $span.text( n + "&times;" );
                  $li2.append( $span );
               }
               $code = $( "<code />" );
               $code.text( subject );
               $li2.append( $code );
               $ul2.append( $li2 );
            }   // for subject in strange
            $li1.append( $ul2 );
            $ul1.append( $li1 );
         }   // for strange in complaints
         if ( m ) {
            $div = $( "<div />" );
            $div.css( { "border":      "#FF0000 3px solid",
                        "color":       "#FF0000",
                        "font-weight": "bold",
                        "padding":     "1em" } );
            $div.text( I18N.fair( "PP_Lethal" + ( m > 1  ?  "s"  :  "" ),
                                  true ) );
            $r.append( $div );
         }
         $r.append( $ul1 );
      } else {
         $r = false;
      }
      return $r;
   };   // TDJPP.$failures()



   TDJPP.fair = function ( adapt, about ) {
      // Escape and trim and quote string
      // Precondition:
      //    adapt  -- string
      //    about  -- string, kind of component
      // Postcondition:
      //    Returns string, with formatted JSON source string
      // Uses:
      //    this
      //    TDJPP.extension
      //    TDJPP.failed()
      // 2015-01-22 PerfektesChaos@de.wikipedia
      var l, m, n, o;
      if ( typeof TDJPP.extension[ about ]  ===  "object" ) {
         n = adapt.length;
         o = TDJPP.extension[ about ];
         m = o[ 1 ];
         if ( o[ 0 ] ) {
            l = ( n < m );
         } else {
            l = ( n > m );
         }
         if ( l ) {
            this.failed( o[ 2 ], adapt );
         }
      }
      return "\"" + adapt.replace( /^\s+/,    "" )
                         .replace( /\s+$/,    "" )
                         .replace( /\n/g,     "\\n" )
                         .replace( /&nbsp;/g, "&#160;" )
                         .replace( /\s+/g,    " " )
                         .replace( /\"/g,     '\\"' ) + "\"";
   };   // TDJPP.fair()



   TDJPP.fence = function ( attribute, alert ) {
      // Analyze type attribute
      // Precondition:
      //    attribute  -- string, with found value
      //    alert      -- true, to issue complaints
      // Postcondition:
      //    Returns normalized string
      // Uses:
      //    TDJPP.failed()
      // 2015-01-15 PerfektesChaos@de.wikipedia
      var r = attribute.replace( /^\s+/, "" )
                       .replace( /\s+$/, "" )
                       .toLowerCase(),
          s = "|boolean|content|date|line|number|string" +
              "|unbalanced-wikitext|unknown" +
              "|wiki-file-name|wiki-page-name|wiki-user-name|";
      if ( ! r.indexOf( "string/" ) ) {
         if ( alert ) {
            this.failed( "TypeDeprecated", r );
         }
         r = r.substr( 7 );
      }
      if ( s.indexOf( "|" + r + "|" )  <  0 ) {
         if ( alert ) {
            this.failed( "TypeUnknown", r );
         }
         r = "unknown";
      }
      return r;
   };   // TDJPP.fence()



   TDJPP.fields = function ( apply, array, about ) {
      // Make sure that object contains no unexpected components
      // Precondition:
      //    apply   -- object to be examined
      //    array   -- Array, with strings of permitted names
      //    about   -- string, informative
      // Postcondition:
      //    Charges filed
      // Uses:
      //    TDJPP.failed()
      //    $.inArray()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var s;
      for ( s in apply ) {
         if ( $.inArray( s, array )  <  0 ) {
            this.failed( "UnknownItem",
                         s + " @ " + about );
         }
      }   // for s in array
   };   // TDJPP.fields()



   TDJPP.fill = function ( amount ) {
      // Number of spaces
      // Precondition:
      //    amount   -- number, with count of characters
      // Postcondition:
      //    Returns string, of requested length
      // 2014-12-27 PerfektesChaos@de.wikipedia
      var r = "",
          i;
      for ( i = 0;  i < amount;  i++ ) {
         r = r + " ";
      }   // for i
      return r;
   };   // TDJPP.fill()



   TDJPP.flush = function () {
      // CLenup, clear recent complaints
      // Postcondition:
      //    complaints cleared
      // Uses:
      //     < TDJPP.complaints
      // 2014-12-27 PerfektesChaos@de.wikipedia
      this.complaints = false;
   };   // TDJPP.flush()



   TDJPP.folder = function ( apply, adjust, align, ahead, about ) {
      // Pretty printing of TemplateData interface component
      // Precondition:
      //    apply   -- JSON interface text component
      //               string, or object, even null
      //    adjust  -- true: create at least empty string
      //    align   -- number of spaces to indent consecutive lines,
      //               or string, beginning with comma
      //    ahead   -- number of spaces to indent component
      //    about   -- string, kind of component
      // Postcondition:
      //    Returns string, with formatted JSON source code
      // Uses:
      //    this
      //    TDJPP.foreign()
      //    TDJPP.fill()
      //    TDJPP.fair()
      // 2015-01-11 PerfektesChaos@de.wikipedia
      var i, poly, r, s;
      switch ( typeof apply ) {
         case "object" :
            if ( apply ) {
               poly = this.foreign( apply, about );
               switch ( typeof poly ) {
                  case "object" :
                     r = "{ ";
                     s = "";
                     for ( i = 0;  i < poly.length;  i++ ) {
                        r = r + s + poly[ i ];
                        if ( ! s ) {
                           if ( typeof align  === "number" ) {
                              s = ",\n" + this.fill( align );
                           } else {
                              s = align;
                           }
                        }
                     }   // for i
                     r = r + " }";
                     break;
                  case "string" :
                     r = poly;
                     break;
               }   // switch typeof poly
            }
            break;
         case "string" :
            r = this.fair( apply, about );
            break;
      }   // switch typeof apply
      if ( ! r ) {
         r = false;
      }
      if ( ! r ) {
         if ( r === ""  &&  adjust ) {
            r = '""';
         } else {
            r = '"' + apply + '"';
         }
      }
      if ( ahead ) {
         r = this.fill( ahead ) + r;
      }
      return r;
   };   // TDJPP.folder()



   TDJPP.foreign = function ( assigned, about ) {
      // Process language related text entry
      // Precondition:
      //    assigned  -- object with   { "lang": "v" }
      //    about     -- string, kind of component
      // Postcondition:
      //    Returns Array, with formatted strings   '"lang": "v"'
      //            or string "v" if only one entry and empty language
      // Uses:
      //    this
      //    >  TDJPP.otherLanguages
      //    TDJPP.failed()
      //    $.inArray()
      //    TDJPP.fair()
      // 2015-01-23 PerfektesChaos@de.wikipedia
      var max = 0,
          r   = [ ],
          re1 = new RegExp( " +\n", "g" ),
          re2 = new RegExp( "  +", "g" ),
          entry, i, n, slang, story, start;
      for ( slang in assigned ) {
         story = assigned[ slang ];
         if ( typeof slang  ===  "string" ) {
            slang = slang.replace( /^\s+/, "" )
                         .replace( /\s+$/, "" )
                         .toLowerCase();
            start = slang;
            if ( ! /^[a-z]{2,8}$/.test( slang )   &&
                 ! /^[a-z]{2,3}-[a-z]{2,9}(?:-[a-z]+)$/.test( slang ) ) {
               this.failed( "LanguageCode", slang );
            } else if ( /^[a-z]{4,8}$/.test( slang ) ) {
               if ( $.inArray( slang, this.otherLanguages )  <  0 ) {
                  this.failed( "LanguageCode", slang );
               }
            }
         } else {
            slang = "" + slang;
            this.failed( "LanguageCode", slang );
         }
         slang = this.fair( slang );
         if ( typeof story  ===  "string" ) {
            story = story.replace( /^\s+/, "" )
                         .replace( /\s+$/, "" )
                         .replace( /\t/g,   " " )
                         .replace( /\r\n/g, "\n" )
                         .replace( re1,     "\n" )
                         .replace( re2,     " " );
         } else {
            story = "" + story;
            this.failed( "DescInvalid", slang );
         }
         n = slang.length;
         if ( n > max ) {
            max = n;
         }
         story = this.fair( story, about );
         r.push( [ n, slang, story ] );
      }   // for slang in assigned
      for ( i = 0;  i < r.length;  i++ ) {
         entry = r[ i ];
         slang = entry[ 1 ] + ": ";
         for ( n = entry[ 0 ];  n < max;  n++ ) {
            slang = slang + " ";
         }   // for n
         r[ i ] = slang + entry[ 2 ];
      }   // for i
      if ( r.length === 1  &&  start === "" ) {
         r = story;
      }
      return r;
   };   // TDJPP.foreign()



   TDJPP.further  =  function ( adjust ) {
      // Adapt JSON source code to advanced wiki project
      // Precondition:
      //    adjust  -- string, with JSON source
      // 2018-08-24 PerfektesChaos@de.wikipedia
      return "{{TemplateData|JSON=\n"
             + adjust + "\n}}";
   };   // TDJPP.further()



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



   TDtxt.fire = function () {
      // Start TemplateData creation handling
      // Precondition:
      //    DOM.ready
      // Uses:
      //    >  TData.learn
      //    >  Sign
      //     < TDtxt.$textarea
      //     < TDtxt.$button
      //    TData.$fold()
      //    TDtxt.fishing()
      //    (TDtxt.finish)
      // Remark: Used as event handler -- 'this' is not TDtxt
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var $div = TData.$fold(),
          s;
      TDtxt.$textarea = $( "<textarea>" );
      TDtxt.$textarea.attr( { rows: 10 } );
      if ( TData.learn ) {
         s = TDtxt.fishing();
         if ( s ) {
            TDtxt.$textarea.val( s );
         }
      }
      $div.append( TDtxt.$textarea );
      TDtxt.$button = $( "<button />" );
      TDtxt.$button.attr( { "id":  Sign + "-submit" } );
      TDtxt.$button.click( TDtxt.finish );
      TDtxt.$button.css( { "margin-bottom": "2em",
                           "margin-top":    "5px",
                           "text-align":    "center" } );
      TDtxt.$button.text( I18N.fair( "btn_Submit", true, "Submit" ) );
      $div.append( TDtxt.$button );
   };   // TDtxt.fire()



   TDtxt.finish = function () {
      // Submit text input
      // Uses:
      //    >  TDtxt.$textarea
      //    >  TDtxt.$button
      //     < TData.$throbber
      //    TDtxt.fishing()
      //    TDJPP.facility()
      //    Hilite.fair()
      //    (TData.finish)
      // Remark: Used as event handler -- 'this' is not TDtxt
      // 2015-01-21 PerfektesChaos@de.wikipedia
      var s = TDtxt.$textarea.val(),
          o = {  params: { }  },
          p = o.params,
          g, i, re;
      TData.$throbber = $( "<span>" );
      TData.$throbber.css( { "margin-left":  "1em",
                             "margin-right": "1em" } );
      TDtxt.$button.after( TData.$throbber );
      Hilite.flow( TData.$throbber );
      TDtxt.$button.attr( { disabled: true } );
      TDtxt.$textarea.attr( { disabled: true } );
      re = "{{{[^}]*}}}";
      re = new RegExp( re );
      if ( re.test( s ) ) {
         s = TDtxt.fishing();
      } else {
         re = "\\{\\{[^}]*\\}\\}";
         re = new RegExp( re, "g" );
         s  = s.replace( re, "" );
      }
      s = s.replace( /\|/g, "\n" );
      g = s.split( "\n" );
      for ( i = 0;  i < g.length;  i++ ) {
         s = g[ i ];
         s = s.replace( /^\s+/, "" )
              .replace( /=.*$/, "" )
              .replace( /\s+$/, "" );
         if ( s ) {
            p[ s ] = { };
         }
      }   // for i
      Hilite.fair( TDJPP.facility( o, 3 ),
                   "javascript",
                   TData.finish,
                   false );
   };   // TDtxt.finish()



   TDtxt.fishing = function () {
      // Guess list of parameters from textarea
      // Precondition:
      //    mediawiki.api loaded
      // Postcondition:
      //    Returns Array, with parameter names, or false
      // Uses:
      //    this
      //    >  TData.learn
      //    TDtxt.field()
      //    $.inArray()
      // 2015-01-20 PerfektesChaos@de.wikipedia
      var $ta = TData.$field(),
          r   = false,
          got, i, k, re, s, story;
      if ( $ta ) {
         story = $ta.val();
         i     = story.indexOf( "{{{" );
         k     = story.lastIndexOf( "}}}" );
         if ( i >= 0  &&   k > i ) {
            story = story.substr( 0,  k + 3 )
                         .substr( i );
            do {
               i = story.lastIndexOf( "{{{" );
               if ( i >= 0 ) {
                  k = story.indexOf( "}}}", i );
                  if ( k < 0 ) {
                     story = story.substr( 0, i );
                  } else {
                     s     = story.substring( i + 3,  k );
                     story = story.substr( 0, i )  +
                             story.substr( k + 3 );
                     k     = s.indexOf( "|" );
                     if ( k >= 0 ) {
                        s = s.substr( 0, k );
                     }
                     s = s.replace( /^\s+/, "" )
                          .replace( /\s+$/, "" );
                     if ( s ) {
                        if ( ! re ) {
                           re = "\\{\\{[^}]*\\}\\}";
                           re = new RegExp( re, "g" );
                        }
                        s  = s.replace( re, "" );
                     }
                     if ( s ) {
                        if ( got ) {
                           if ( $.inArray( s, got ) < 0 ) {
                              got.push( s );
                           }
                        } else {
                           got = [ s ];
                        }
                     }
                  }
               }
            } while ( i >= 0 );
            if ( got ) {
               got.sort();
               r = "";
               for ( i = 0;  i < got.length;  i++ ) {
                  r = r + "|" + got[ i ] + "=\n";
               }   // for i
            }
         }
      }
      return r;
   };   // TDtxt.fishing()



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



   function fire() {
      // Initialize if appropriate
      // Precondition:
      //    mediawiki.util loaded
      // Uses:
      //     < Env
      //    mw.config.get()
      //    mw.util.getParamValue()
      //    Hilite.fire()
      //    Editor.fire()
      //    mw.loader.using()
      //    (TData.fire)
      // 2015-01-11 PerfektesChaos@de.wikipedia
      Env  =  mw.config.get( [ "wgAction",
                               "wgNamespaceNumber",
                               "wgTitle" ] );
      if ( Env.wgNamespaceNumber === 10 ) {
         Env.oldid = mw.util.getParamValue( "oldid" );
         if ( ! Env.oldid ) {
            if ( Env.wgTitle.search( /\/XML$/ )  >  0 ) {
               switch ( Env.wgAction ) {
                  case "view" :
                     Env.diff = mw.util.getParamValue( "diff" );
                     if ( ! Env.diff ) {
                        $( Hilite.fire );
                     }
                     break;
                  case "edit" :
                  case "submit" :
                     Editor.fire();
                     break;
               }   // switch wgAction
            }
            mw.loader.using( [ "mediawiki.api" ],
                             TData.fire );
         }
      }
   }   // fire()


   function first() {
      // Autorun on load
      // Uses:
      //    >  Sign
      //    mw.loader.getState()
      //    mw.loader.state()
      //    mw.loader.using()
      //    (fire)
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var signature = "user.PerfektesChaos." + Sign,
          rls;
      if ( mw.loader.getState( signature )  !==  "ready" ) {
         rls = { };
         rls[ signature ] = "ready";
         mw.loader.state( rls );
         mw.loader.using( [ "mediawiki.util",
                            "user" ],
                          fire );
      }
   }   // first()



   first();
}( window.mediaWiki, window.jQuery ) );
/// EOF </nowiki>   jsonXMLutils.js