/*  PmSyntax: Syntax highlighting for PmWiki markup
    Copyright 2021-2026 Petko Yotov https://www.pmwiki.org/support
    This file is part of PmWiki; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published
    by the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.  See pmwiki.php for full details.

    This file provides Javascript functions for syntax highlighting of
    PmWiki markup, to be used in the PmWiki documentation, and optionally
    in the edit form.

    $Id: pmwiki.syntax.js 5487 2026-06-05 06:49:33Z petko $
*/

(function(__script__){

  const { echo, q, Q, PHSC, dce, rnd, intval,
    average, zpad, ls, getConf, on, off, pd, tap,
    cloneFontMetrics, adj, cl, gcs, gbcr, ready } = PmLib;

  var Config = getConf(__script__);

  Config.rxInputs = new RegExp('^(' + Config.inputs.sort(rsortbylength).join('|') + ')');
  Config.rxConditions = new RegExp('\\b(' + Config.conditions.sort(rsortbylength).join('|') + ')\\b');

  var KeepToken = "\034\034";
  var restoreRX = new RegExp(KeepToken+'(\\d+)'+KeepToken, 'g');
  var special = /[#!*?&+|,()[\]{}\/\^<>=]+|\.\.+|--+|\s-+/g;
  var Kept = new RegExp('^' + KeepToken+'(\\d+)'+KeepToken + '$', '');

  function rsortbylength(a, b) {
    return b.length - a.length;
  }
  function span(cname, text, escaped) {
    if(text==='') return '';
    cname = cname.replace(/^\*/, 'tag ').replace(/(^| )(?=\w)/g, '$1pm');
    let props = { cn: cname};
    if(!escaped) props.tc = text;
    else props.ih = text;
    return dce.span(props).outerHTML;
  }
  var hrx = [ // rule_name, [*=!]classname|function, [container_rx], rx
    ['_begin'],
    ['external', 'external', /%hlt(?: +([-\w+]+))? *% *\[@([\s\S]*?)@\]/g],
    ['external2', 'external', /%hlt(?: +([-\w+]+)) *% *@@ *\[=([\s\S]*?)=\] *@@/g],
    ['pmhlt', 'external', /%(pm)hlt *% *\[@([\s\S]*?)@\]/g],
    ['pmhlt2', 'external', /%(pm)hlt *% *@@ *\[=([\s\S]*?)=\] *@@/g],
    ['preserve', '=escaped', /\[([@=])[\s\S]*?\1\]/g, /^(\[[@=])([\s\S]*)([@=]\])$/],
    ['joinline', '*bullet', /\\+(\n|$)/g],

    // variables
    ['pagevar', 'pagevar', /\{([-\w\/.]+|[*=<>])?(\$\$?)(:)?(\w+)(?:(\|)(.*?))?\}/g],
    ['nestvar', 'pagevar', /\{([-\w\/.\034]+)?(\$\$?)(:)?(\w+)(?:(\|)(.*?))?\}/g],
    ['phpvar',  'var', /\$(pagename|Skin|Author|Version|action|[A-Z]\w*(?:[A-Z][a-z0-9]|[a-z0-9][A-Z])\w*|_(?:[A-Z]+))\b/g],
    ['i18n', 'string', /\$\[.*?\]/g],

    // markup expressions
    ['mx', '!mx', /(\{\([-\w]+)(.*?)(\)\})/g],

    // page text vars, can be empty or multiline
    ['ptv0', '=meta>punct', /(\(:\w[-\w]*)(:)( *:\))/g, /:/],
    ['ptv1', '=meta>punct', /(\(:\w[\w-]*)(:[^\)][\s\S]*?)(:\))/g, /:/],

    // core meta directives
    ['comment', '=comment', /(\(:comment )(.*?)(:\))/gi],
    ['skin',  '*meta', /\(:no(left|right|title|action|(group)?(header|footer)) *:\)/gi ],
    ['meta0', '*meta', /\(:(no)?((link|space)wikiwords|linebreaks|toc) *:\)/gi],
    ['meta1', '*meta', /\(:(else\d*|if\d*|if\d*end|nl) *:\)/gi],
    ['meta2', '=meta', /(\(:(?:title|description|keywords))(.*?)(:\))/gi],
    ['meta3', '=meta>keyword>*attr>*keyword',
      /(\(:(?:(?:else\d*)?if\d*))([^-a-z].*?)(:\))/ig, 
      Config.rxConditions,
      special, /[[\]()]+/g ],
    ['tmpl', '!meta>=keyword', /(\(:template[^\S\r\n]+)(\S.*?)(:\))/g,
      /^([ !]*)(each|first|last|defaults?|requires?|none)/],
    ['rdir', '!meta', /(\(:redirect)(.*?)(:\))/g],

    // urls can have percents so before wikistyle (populated by InterMap)
    ['ttip',  '=escaped', /\[\[.*?\S(?= *(?:\||\]\]))/g,  /(")(.*)(")$/ ], // tooltop
    ['link0', '=escaped', /\[\[.*?\S(?= *(?:\||\]\]))/g,  /(\()(.*?)(\))/g],// hidden
    ['link1', 'bracketlink', /(\[\[ *)(.+?)( *\]\])/g ],
    ['_url'],

    // wikistyles
    ['ws0', '*meta', /%%|^>><</gm],
    ['ws1', '!meta', /(^>> *\w[-\w]*)(.*?)(<<)/gm],
    ['ws2', '!meta', /(%(?:define|apply)=\w+)(.*?)(%)/gi],
    ['ws3', '!meta', /(%\w[-\w]*)(.*?)(%)/g],

    // directives, forms
    ['form', '!directive>keyword', /(\(:input[^\S\r\n]+)(\S.*?)(:\))/g, Config.rxInputs],
    ['dir0', '*directive', /\(:\w[-\w]* *:\)/g], // simple
    ['dir1', '!directive', /(\(:\w[-\w]*)(.*?)(:\))/g], // with attributes

    // inline
    ['link', 'punct', /(\[\[[#!~]?|([#+][^\S\r\n]*)?\]\])/g], // link

    // list item, initial space, indent, linebreak
    ['bullet', '*bullet', /^([^\S\r\n]*([*#]+)[^\S\r\n]*|-+[<>][^\S\r\n]*|[^\S\r\n]+)/mg],

    ['QA', '*heading', /^([QA]:|-{4,})/mg], //Q:/A:, horizontal rule
    ['prop', 'meta',   /^[A-Z][-_a-zA-Z0-9]*:/mgi], // property, or start of line PTV

    // inline punctuation; entity
    ['time',  '=mx>string>var',   /(@)([\d-]{10}T\d\d:\d\d(?::\d\d)?)(Z)/g, /^[\d-]+/, /[\d:]+$/],
    ['punct',  'punct',   /('[\^_+-]|[\^_+-]'|\{[+-]+|[+-]+\}|\[[+-]+|[+-]+\]|@@|'''''|'''|''|->|~~~~?)/g],
    ['entity', 'string',  /&#?\w+;/g],

    // simple tables
    ['tablecapt', '=table', /^(\|\|!)(.+)(!\|\|)$/mg],
    ['tablerwsp1', 'table', /(\+\++)(?=\s*\|\|)/g],
    ['tablerwsp2', 'escaped', /\|\|(__+|\^\^+)(?=\|\|)/g, /([_^]+)/],
    ['tablerow',  '!table', /^\|\|.*\|\|.*$/mg, /((?:\|\|)+)(!?)/g],
    ['tableattr', '!table', /^(\|\|)(.*)($)/mg],

    // wikitrails
    ['trail1', '=url', /(<<?\|)(.*?)(\|>>?)/g],
    ['trail2', '=url', /(\^\|)(.*?)(\|\^)/g],

    ['pipe', 'punct', /\|/g], // inline, after trails

    // may contain inline markup
    ['deflist', '=bullet', /^([:]+)(.*?)([:])/mg],
    ['headingid', 'url', /^!{1,6}##?[a-z_][-a-z0-9_]*/img, /##?[a-z_][-a-z0-9_]*/i],
    ['heading', '=heading', /^(!{1,6})(.*)($)/mg],

    ['cleanup', PHSC, /[<>&]+/g],// raw HTML/XSS
    ['restore', '.restore', restoreRX],
    ['_end']
  ];
  var custom_hrx = {}, sorted_hrx = [];
  var hl = hrx.length;
  for(var i=0; i<hl; i++) {
    custom_hrx[ hrx[i][0] ] = [];
    custom_hrx[ '>'+hrx[i][0] ] = [];
  }

  function PmHi(text) {
    var KPV = [];
    var custfn = { pagevar: pagevar, bracketlink:bracketlink };
    function Restore(all, n) { return KPV[parseInt(n)]; }
    function keep0(text) {
      if(text === '') return '';
      KPV.push(text.replace(restoreRX, Restore));
      return KeepToken+(KPV.length-1)+KeepToken;
    }
    function Keep(text, cname) {
      if(text==='') return '';
      text = span(cname, text);
      return keep0(text);
    }
    function Keep5(parts, cname) {
      var mode = cname.charAt(0);
      var attr = parts[4] || mode == '!' ? true:false;
      var out = '';
      if(parts[0]) out += span('tag', parts[0]);
      if(parts[1]) out += attr ? hattr(parts[1]) : PHSC(parts[1]);
      if(parts[2]) out += span('tag', parts[2]);
      if(parts[3]) out += mode == '!' ? hattr(parts[3]) : PHSC(parts[3]);
      if(parts[4]) out += span('tag', parts[4]);
      if(!out) return '';
      else out = span(cname.slice(1), out, true);
      return keep0(out);
    }
    function hattr(attr) {
      if(! attr) return '';
      attr = attr.toString()
      .replace(/(['"])(.*?)\1/g, function(a){ return Keep(a, 'value'); })
      .replace(/((?:\$:?)?[-\w]+|^)([:=])(\S+)/g, function(a, attr, op, val){
        if(! val.match(Kept)) val = span('value', val);
        if(attr) attr = span('attr', attr);
        return keep0(attr + op + val);
      })
      .replace(/(\()(\w+)/g, function(a, attr, expr){
        return Keep(attr, '*attr')+Keep(expr, 'tag');
      })
      .replace(special, function(a){ return Keep(a, '*attr'); });
      return PHSC(attr);
    }
    function external(lang, code) {
      if(lang.match(/^pm(wiki)?$/i))
        return keep0(PmHi(code));
      if(!lang) { }
      else if(! externalLangs
        || lang == 'plaintext'
        || ! lang.match(externalLangs)
      ) return keep0(PHSC(code));
      try {
        if(lang) {
          var x = hljs.highlight(code, {language:lang, ignoreIllegals:true});
        }
        else var x = hljs.highlightAuto(code);
        return keep0('<code class="hljs language-'+x.language+'" title="'+x.language+'">'
          +x.value+'</code>');
      }
      catch(e) {
        return keep0(PHSC(code));
      }
    }
    function pagevar(m, pn, dd, cn, vn, pipe, df) {
      let out = '{';
      let ustyle = pn && pn.includes('\034')? 'escaped url':'url';
      if(pn) out += Keep(pn, ustyle);
      out += dd;
      if(cn) out += Keep(cn, 'meta')
      out += vn;
      if(pipe) out += Keep(pipe, 'meta')+Keep(df, 'string');
      out += '}';
      return Keep(out, 'var');
    }
    function bracketlink(m, m1, m2, m3) {
      var a = m2.match(/^(.+?)( *\|.+)$/);
      if(a) return m1+Keep(a[1], 'url')+a[2]+m3;
      a = m2.match(/^(.+-> *)(.+)$/);
      if(a) return m1+a[1]+Keep(a[2], 'url')+m3;
      return m1+Keep(m2, 'url')+m3;
    }

    function PmHi1(text, rule) {
      var r = rule[0], s = rule[1];
      if(r == '.restore') return text.replace(s, Restore);
      if(typeof r == 'string' && r.indexOf('external')===0) {
        var b = r.match(/[>]([-\w+]+)/);
        return text.replace(s, function(a, a1, a2){
          var lang = b? b[1] : a1;
          var code = b? a1 : a2;
          if(!code.match(/\S/)) return a;
          if(!lang) lang = '';
          return a.replace(code, external(lang.toLowerCase(), code));
        });
      }
      if(!!rule[2]) {
        var m = (typeof r == 'function') ? false : r.split(/[>]/g);
        if(m && m.length>1) { // parent>nested
          r = m[0];
          return text.replace(s, function(a){
            var b = Array.from(arguments).slice(1, -2);
            var j = b[4]? 3:1;
            var ml = m.length;
            for(var i=1; i<ml; i++) {
              if(rule[i+1]) b[j] = PmHi1(b[j], [m[i], rule[i+1]]);
            }
            return Keep5(b, r);
          });
        }
        else { // one classname, return match only_in_container
          return text.replace(s, function(a){
            return PmHi1(a, [r, rule[2]]);
          });
        }
      }
      if(typeof r == 'function') return text.replace(s, r);
      if(typeof custfn[r] == 'function') return text.replace(s, custfn[r]);
      text = text.replace(s, function(a){
        var b = Array.from(arguments).slice(1, -2);
        if(r.match(/^[=!]/)) return Keep5(b, r);
        else return Keep(a, r);
      });
      return text;
    }
    var sl = sorted_hrx.length;
    for(var i=0; i<sl; i++) {
      var rule = sorted_hrx[i];
      if(rule.length<2)  continue; // _begin, _end
      text = PmHi1(text, rule);
    }
    return text;
  }

  function PmHiEl(el){
    el.innerHTML = PmHi(el.textContent);
    cl.a(el, 'pmhlt');
  }

  function PmHiAll(){
    var hlt = Q('.hlt.pm, .hlt.pmwiki, .highlight.pm, .highlight.pmwiki');
    var hl = hlt.length;
    for(var i=0; i< hl; i++) hlt[i].className = 'pmhlt';
    var pm = Q('table.markup td.markup1 > pre, '
      + '.pmhlt pre, .pmhlt + pre, .pmhlt code, pre.pmhlt, code.pmhlt');
    if(! pm.length) return;
    pm.forEach(PmHiEl);
    tap('.toggle-pmhlt', toggleStyles);
  }

  function toggleStyles(e) {
    pd(e);
    var c1 = 'pmhlt', c2 = 'pmhlt-disabled';
    var x = Q('.'+c1+',.'+c2);
    var xl = x.length;
    for(let y of x) {
      cl.t(y, c1);
      cl.t(y, c2);
    }
  }

  function str2rx(str) {
    if(typeof str.flags == 'string') return str; // regexp
    if(typeof str != 'string') {
      echo("Not a string", str);
      return false;
    }
    var a = str.match(/^\/(.*)\/([gimsyu]*)$/);
    var rx = false
    try {
      if(a) rx = new RegExp(a[1], a[2]);
      else rx = new RegExp(str, 'g');
    }
    catch(e) {
      echo.e('Could not create RegExp.', str);
    }
    if(rx) {
      if(rx.source.match(/\[(\\s\\S)|(\\S\\s)\]/)) {
        PreserveMultilineArray.push(rx.source);
      }
      return rx;
    }
  }

  var PreserveMultilineArray = [], PreserveMultiline = false;
  function sortRX(){
    var cm = window.PmSyntaxCustomMarkup??[]; // may be added by a recipe.js
    var imaps =  [Config.imaps.join('|')+'|Attach:']; // for the documentation

    var custom = Config.custom;
    var ll = custom.length;
    for(var i=0; i<ll; i++) {
      var rule = custom[i];
      if(typeof rule == 'string') rule = rule.trim().split(/\s{2,}/g);
      if(rule[0]=='InterMap') {
        imaps.push(rule[1]);
        continue;
      }
      var rl = rule.length;
      for(var j=2; j<rl; j++) {
        var r = str2rx(rule[j]);
        if(r) rule[j] = r;
      }
      cm.push(rule);
    }

    var uec = '<>"{}|\\\\^`()[\\]\'';
    cm.push(['>_url', 'url', new RegExp(
      '\\b(' +imaps.join('|')+ ')(?:(?!%%)[^\\s'+uec+']*)[^\\s.,?!%'+uec+']?', 'g'
    )]);
    var cml = cm.length;
    for(var i=0; i<cml; i++) {
      var key = cm[i][0].replace(/^</, '');
      if(custom_hrx.hasOwnProperty(key)) custom_hrx[key].push(cm[i].slice(1));
      else echo('No rule name to attach to.', cm[i]);
    }
    sorted_hrx = [];
    var hrxl = hrx.length;
    for(var i=0; i<hrxl; i++) {
      var key = hrx[i][0];
      var keys = [key, '>'+key];
      for(var k=0; k<2; k++) {
        if(k) sorted_hrx.push(hrx[i].slice(1));
        var kk = keys[k];
        var chl = custom_hrx[kk].length;
        for(var j=0; j<chl; j++) {
          sorted_hrx.push(custom_hrx[kk][j]);
        }
      }
    }
    if(PreserveMultilineArray.length) {
      PreserveMultiline = new RegExp('('+PreserveMultilineArray.join('|')+')', 'g');
    }
  }

  function initEditForm() {
    if(!Config || !Config.mode || !Config.mode.match(/^[23]$/)) return;
    var text = q('#wikiedit textarea#text');
    if(text) cl.a(text, 'pmsyntax');
    var datas = Q('textarea[data-pmsyntax]');
    for(var d of datas) cl.a(d, 'pmsyntax');
    var all = Q('textarea.pmsyntax');
    for(var t of all) hltEditForm(t);
  }

  function hltEditForm(text){
    var chk_hlt, hwrap, htext;
    var defaultEnabled = Config.mode == '3'? 1: 0;

    var lastTextParts = [];
    var lastTextContent = false;
    var hltEnabled = false;
    var EnableStopwatch = ls.EnablePmSyntaxStopwatch? true:false;
    var resizeObserver;

    var perf = [];
    function stopwatch(dt) {
      perf.push(dt);
      let rdt = rnd(dt);
      let avg = rnd(average(perf));
      let a10 = rnd(average(perf.slice(-10)));
      if(perf.length%10 == 0)
        echo(`PmSyntax ${rdt}ms@1, ${a10}ms@10, ${avg}ms@${perf.length}.`);
    }

    let scheduled = false;
    function scheduleUpdate() {
      if (scheduled || !hltEnabled) return;
      scheduled = true;
      requestAnimationFrame(updatePre);
    }

    function updatePre(stamp) {
      var tc = text.value;
      if(tc===lastTextContent) {
        scheduled = false;
        return;
      }
      var parts = splitParts(tc);
      if(!parts.length) parts = [''];

      if(lastTextParts.length==0) {
        var out = parts.map(mapHi).join('') + '\n';
        adj.ab(htext, out);
        var juststarted = 1;
      }
      else {
        var diff = diffParts(parts);
        var spans = htext.children;
        for(var i=0; i<diff.count; i++) {
          htext.removeChild(spans[diff.first]);
        }
        if(diff.add) {
          if(spans[diff.first]) adj.bb(spans[diff.first], diff.add);
          else if(spans[diff.first-1]) adj.ae(spans[diff.first-1], diff.add);
          else adj.ab(htext, diff.add);
        }
      }
      lastTextContent = tc;
      lastTextParts = parts;
      textScrolled();
      if(EnableStopwatch && stamp) stopwatch(performance.now() - stamp);
      if(juststarted) perf = [];
      scheduled = false;
    }

    function mapHi(x) {
      if(x!=='' && x.charAt(0) !== '\n') x = PmHi(x);
      return '<span>'+x+'</span>';
    }

    function splitParts(value) {
      var kept = [];
      function keep(a, a1){
        kept.push(restore(a1));
        return '\034\034' + (kept.length - 1) + '\034\034';
      };
      function restore(str) {
        return str.replace(/\034\034(\d+)\034\034/g, function(a, a1){
          return kept[parseInt(a1)];
        });
      }
      // keep escaped blocks
      value = value
        .replace(/(\[([@=])[\s\S]*?\2\]|\(:[-\w]+ *:(?!\))[\s\S]*?:\)|\\\n)/g, keep);
      if(PreserveMultiline) {
        value = value.replace(PreserveMultiline, keep);
      }

      var parts = value.split(/(\n+)/).filter(Boolean);
      for(var i=0; i<parts.length; i++) {
        parts[i] = restore(parts[i]);
      }
      return parts;
    }
    function diffParts(parts) {
      var ll = lastTextParts.slice(0), pp = parts.slice(0), firstchanged = 0;
      while(ll.length && pp.length) {
        if(ll[0] !== pp[0]) break;
        ll.shift(); pp.shift();
        firstchanged++;
      }
      ll.reverse(); pp.reverse();
      while(ll.length && pp.length) {
        if(ll[0] !== pp[0]) break;
        ll.shift(); pp.shift();
      }
      var add = pp.reverse().map(mapHi).join('');
      return {first:firstchanged, count:ll.length, newcount:pp.length, add:add};
    }

    function textScrolled() {
      if(!hltEnabled) return;
      if(ignoreTextScrolled) return;

      if(ignorePreScrolled) clearTimeout(ignorePreScrolled-1);
      ignorePreScrolled = 1 + setTimeout(nullIPS, 100);
      htext.scrollTop = text.scrollTop;
      htext.scrollLeft = text.scrollLeft;
    }
    var ignoreTextScrolled = false, ignorePreScrolled = false;
    function preScrolled() { // browser's in-page search
      if(!hltEnabled) return;
      if(ignorePreScrolled) return;
      if(ignoreTextScrolled) clearTimeout(ignoreTextScrolled-1);
      ignoreTextScrolled = 1 + setTimeout(nullITS, 100);
      text.scrollTop = htext.scrollTop;
      text.scrollLeft = htext.scrollLeft;
    }
    function nullITS(){ignoreTextScrolled = false;}
    function nullIPS(){ignorePreScrolled = false;}
    function dragstart(e) { cl.a(this,'dragging'); }
    function dragend(e) { cl.r(this,'dragging'); }

    function resizePre() {
      if(!hltEnabled) return;
      var rect = gbcr(text);
      var w = rect.width + 'px', h = rect.height + 'px';
      htext.style.width = w;
      htext.style.height = h;
      textScrolled();
    }

    function initPre() {
      hwrap = dce.div({cn: 'hwrap'});
      adj.bb(text, hwrap);
      hwrap.appendChild(text);
      htext = dce.div({cn: 'htext pmhlt', inert: true});
      cloneFontMetrics(text, htext);

      hwrap.appendChild(htext);

      updatePre();
      resizePre();

      on.scroll(htext, preScrolled);
      on.scroll(text, textScrolled);
      on.dragstart(text, dragstart);
      on.dragend(text, dragend);

      resizeObserver = new ResizeObserver(resizePre);
      resizeObserver.observe(text);
      if(text.autofocus) text.focus();
    }

    function storeEnabled(enabled) {
      if(enabled != defaultEnabled) ls.EnableHighlight = enabled;
      else delete ls.EnableHighlight;
    }

    function EnableHighlight() {
      if(hltEnabled) {
        if(chk_hlt) cl.a(chk_hlt, 'pmhlt');
        cl.a(text, 'hot');
        storeEnabled(1);
        updatePre();
        resizePre();
        on.input(text, scheduleUpdate);
      }
      else {
        if(chk_hlt) cl.r(chk_hlt, 'pmhlt');
        cl.r(text, 'hot');
        lastTextContent = false;
        storeEnabled(0);
        off.input(text, scheduleUpdate);
      }
    }

    function initToggle(x){
      var form = text.closest('form');
      if(form && text.id == 'text') {

        chk_hlt = dce.code({ cn:'chk_hlt', rc:[
          dce.span({cn:'pmpunct', tc:'[['}),
          dce.span({cn:'pmurl',   tc: Config.label}),
          dce.span({cn:'pmpunct', tc:']]'})
        ] });
        adj.ab(form, chk_hlt);

        tap(chk_hlt, function(e){
          hltEnabled = !hltEnabled;
          EnableHighlight();
        });
      }

      initPre();
      var stored = ls.EnableHighlight;
      var enabled = (stored===null) ? defaultEnabled : intval(stored);
      if(enabled) {
        hltEnabled = true;
        EnableHighlight();
      }
    }

    on.keydown(text, function(e) {
      if(e.key == 'F8') {
        pd(e);
        hltEnabled = !hltEnabled;
        EnableHighlight();
      }
    });

    initToggle();
  }

  var externalLangs = false;
  function initExtLangs() {
    if(typeof hljs == 'undefined') return;
    var langs = hljs.listLanguages();
    var aliases = langs.slice(0);
    var ll = langs.length
    for(var i=0; i<ll; i++) {
      var l = hljs.getLanguage(langs[i]);
      if(l.aliases) aliases = aliases.concat(l.aliases);
    }
    externalLangs = new RegExp('^('+aliases.join('|').replace(/[+]/g, '\\+')+')$', 'i');
  }

  ready(function(){
    sortRX();
    initExtLangs();
    PmHiAll();
    initEditForm();
  });
})(document.currentScript);

