User:Pilaf~enwiki/instaview.js

// Last update: Cacycle 22:26, 22 November 2008 (UTC)

// *** Important security issue: wiki code passed to InstaView MUST be html-escaped (&, <, and > to character entities). Cacycle, 25 August 2014 ***

//

// Script to embed InstaView in MediaWiki's edit page

$(function(){

if (document.getElementById('editpage-copywarn')) {

var oldPreview = document.getElementById('wpPreview');

var newPreview = document.createElement('input');

newPreview.setAttribute('type', 'button');

newPreview.setAttribute('style', 'font-style: italic');

newPreview.setAttribute('value', 'InstaView');

newPreview.setAttribute('id', 'InstaView');

newPreview.setAttribute('name', 'InstaView');

newPreview.setAttribute('onclick', "InstaView.dump('wpTextbox1', 'InstaViewDump')");

oldPreview.parentNode.insertBefore(newPreview, oldPreview);

oldPreview.parentNode.innerHTML += '

';

oldPreview.value = 'Classic Preview';

}

});

/*

* InstaView - a Mediawiki to HTML converter in JavaScript

* Version 0.6.1

* Copyright (C) Pedro Fayolle 2005-2006

* http://en.wikipedia.org/wiki/User:Pilaf

* Distributed under the BSD license

*

* Changelog:

*

* 0.6.1

* - Fixed problem caused by \r characters

* - Improved inline formatting parser

*

* 0.6

* - Changed name to InstaView

* - Some major code reorganizations and factored out some common functions

* - Handled conversion of relative links (i.e. /foo)

* - Fixed misrendering of adjacent definition list items

* - Fixed bug in table headings handling

* - Changed date format in signatures to reflect Mediawiki's

* - Fixed handling of :Image:...

* - Updated MD5 function (hopefully it will work with UTF-8)

* - Fixed bug in handling of links inside images

*

* To do:

* - Better support for

* - Full support for

* - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof)

* - Support for templates (through AJAX)

* - Support for coloured links (AJAX)

*/

var InstaView = {}

// options

InstaView.conf =

{

user: {},

wiki: {

lang: 'en',

interwiki: 'ab|aa|af|ak|sq|als|am|ang|ar|an|arc|hy|roa-rup|as|ast|av|ay|az|bm|ba|eu|be|bn|bh|bi|bs|br|bg|my|ca|ch|ce|chr|chy|ny|zh|zh-tw|zh-cn|cho|cv|kw|co|cr|hr|cs|da|dv|nl|dz|en|eo|et|ee|fo|fj|fi|fr|fy|ff|gl|ka|de|got|el|kl|gn|gu|ht|ha|haw|he|hz|hi|ho|hu|is|io|ig|id|ia|ie|iu|ik|ga|it|ja|jv|kn|kr|csb|ks|kk|km|ki|rw|rn|tlh|kv|kg|ko|kj|ku|ky|lo|la|lv|li|ln|lt|jbo|nds|lg|lb|mk|mg|ms|ml|mt|gv|mi|minnan|mr|mh|zh-min-nan|mo|mn|mus|nah|na|nv|ne|se|no|nn|oc|or|om|pi|fa|pl|pt|pa|ps|qu|ro|rm|ru|sm|sg|sa|sc|gd|sr|sh|st|tn|sn|scn|simple|sd|si|sk|sl|so|st|es|su|sw|ss|sv|tl|ty|tg|ta|tt|te|th|bo|ti|tpi|to|tokipona|ts|tum|tr|tk|tw|uk|ur|ug|uz|ve|vi|vo|wa|cy|wo|xh|ii|yi|yo|za|zu',

default_thumb_width: 180

},

paths: {

articles: '/wiki/',

math: '/math/',

images: '',

images_fallback: 'http://upload.wikimedia.org/wikipedia/commons/',

magnify_icon: 'skins/common/images/magnify-clip.png'

},

locale: {

user: 'User',

image: 'Image',

category: 'Category',

months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

}

}

// options with default values or backreferences

with (InstaView.conf) {

user.name = user.name || 'Wikipedian'

user.signature = ''+user.name+''

paths.images = 'http://upload.wikimedia.org/wikipedia/' + wiki.lang + '/'

}

// define constants

InstaView.BLOCK_IMAGE = new RegExp('^\\[\\['+InstaView.conf.locale.image+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');

InstaView.dump = function(from, to)

{

if (typeof from == 'string') from = document.getElementById(from)

if (typeof to == 'string') to = document.getElementById(to)

to.innerHTML = this.convert(from.value)

}

InstaView.convert = function(wiki)

{

var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode

o='', // output

p=0, // para flag

$r // result of passing a regexp to $()

// some shorthands

function remain() { return ll.length }

function sh() { return ll.shift() } // shift

function ps(s) { o+=s } // push

function f() // similar to C's printf, uses ? as placeholders, ?? to escape question marks

{

var i=1,a=arguments,f=a[0],o='',c,p

for (;i

// allow character escaping

i -= c=f.charAt(p+1)=='?'?1:0

o += f.substring(0,p)+(c?'?':a[i])

f=f.substr(p+1+c)

} else break;

return o+f

}

function html_entities(s) { return s.replace(/&/g,"&").replace(//g,">") }

function max(a,b) { return (a>b)?a:b }

function min(a,b) { return (a

// return the first non matching character position between two strings

function str_imatch(a, b)

{

for (var i=0, l=min(a.length, b.length); i

return i

}

// compare current line against a string or regexp

// if passed a string it will compare only the first string.length characters

// if passed a regexp the result is stored in $r

function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)) }

function $$(c) { return ll[0]==c } // compare current line against a string

function _(p) { return ll[0].charAt(p) } // return char at pos p

function endl(s) { ps(s); sh() }

function parse_list()

{

var prev='';

while (remain() && $(/^([*#:;]+)(.*)$/)) {

var l_match = $r

sh()

var ipos = str_imatch(prev, l_match[1])

// close uncontinued lists

for (var i=prev.length-1; i >= ipos; i--) {

var pi = prev.charAt(i)

if (pi=='*') ps('')

else if (pi=='#') ps('')

// close a dl only if the new item is not a dl item (:, ; or empty)

else switch (l_match[1].charAt(i)) { case'':case'*':case'#': ps('') }

}

// open new lists

for (var i=ipos; i

var li = l_match[1].charAt(i)

if (li=='*') ps('

    ')

    else if (li=='#') ps('

      ')

      // open a new dl only if the prev item is not a dl item (:, ; or empty)

      else switch(prev.charAt(i)) { case'':case'*':case'#': ps('

      ') }

      }

      switch (l_match[1].charAt(l_match[1].length-1)) {

      case '*': case '#':

      ps('

    1. ' + parse_inline_nowiki(l_match[2])); break

      case ';':

      ps('

      ')

      var dt_match

      // handle ;dt :dd format

      if (dt_match = l_match[2].match(/(.*?) (:.*?)$/)) {

      ps(parse_inline_nowiki(dt_match[1]))

      ll.unshift(dt_match[2])

      } else ps(parse_inline_nowiki(l_match[2]))

      break

      case ':':

      ps('

      ' + parse_inline_nowiki(l_match[2]))

      }

      prev=l_match[1]

      }

      // close remaining lists

      for (var i=prev.length-1; i>=0; i--)

      ps(f('', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')))

      }

      function parse_table()

      {

      endl(f('', $(/^\{\|( .*)$/)? $r[1]: ''))

      for (;remain();) if ($('|')) switch (_(1)) {

      case '}': endl(''); return

      case '-': endl(f('', $(/\|-*(.*)/)[1])); break

      default: parse_table_data()

      }

      else if ($('!')) parse_table_data()

      else sh()

      }

      function parse_table_data()

      {

      var td_line, match_i

      // 1: "|+", '|' or '+'

      // 2: ??

      // 3: attributes ??

      // TODO: finish commenting this regexp

      var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/)

      if (td_match[1] == '|+') ps('

      else ps('

      if (typeof td_match[3] != 'undefined') {

      ps(' ' + td_match[3])

      match_i = 4

      } else match_i = 2

      ps('>')

      if (td_match[1] != '|+') {

      // use || or !! as a cell separator depending on context

      // NOTE: when split() is passed a regexp make sure to use non-capturing brackets

      td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/)

      ps(parse_inline_nowiki(td_line.shift()))

      while (td_line.length) ll.unshift(td_match[1] + td_line.pop())

      } else ps(td_match[match_i])

      var tc = 0, td = []

      for (;remain(); td.push(sh()))

      if ($('|')) {

      if (!tc) break // we're at the outer-most level (no nested tables), skip to td parse

      else if (_(1)=='}') tc--

      }

      else if (!tc && $('!')) break

      else if ($('{|')) tc++

      if (td.length) ps(InstaView.convert(td))

      }

      function parse_pre()

      {

      ps('

      ')

      do endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); while (remain() && $(' '))

      ps('

      ')

      }

      function parse_block_image()

      {

      ps(parse_image(sh()))

      }

      function parse_image(str)

      {

      // get what's in between "Image:" and ""

      var tag = str.substring(InstaView.conf.locale.image.length + 3, str.length - 2);

      var width;

      var attr = [], filename, caption = '';

      var thumb=0, frame=0, center=0;

      var align='';

      if (tag.match(/\|/)) {

      // manage nested links

      var nesting = 0;

      var last_attr;

      for (var i = tag.length-1; i > 0; i--) {

      if (tag.charAt(i) == '|' && !nesting) {

      last_attr = tag.substr(i+1);

      tag = tag.substring(0, i);

      break;

      } else switch (tag.substr(i-1, 2)) {

      case ']]':

      nesting++;

      i--;

      break;

      case '[[':

      nesting--;

      i--;

      }

      }

      attr = tag.split(/\s*\|\s*/);

      attr.push(last_attr);

      filename = attr.shift();

      var w_match;

      for (;attr.length; attr.shift())

      if (w_match = attr[0].match(/^(\d*)px$/)) width = w_match[1]

      else switch(attr[0]) {

      case 'thumb':

      case 'thumbnail':

      thumb=true;

      case 'frame':

      frame=true;

      break;

      case 'none':

      case 'right':

      case 'left':

      center=false;

      align=attr[0];

      break;

      case 'center':

      center=true;

      align='none';

      break;

      default:

      if (attr.length == 1) caption = attr[0];

      }

      } else filename = tag;

      var o='';

      if (frame) {

      if (align=='') align = 'right';

      o += f("

      ", align);

      if (thumb) {

      if (!width) width = InstaView.conf.wiki.default_thumb_width;

      o += f("

      ?", 2+width*1, make_image(filename, caption, width)) +

      f("

      ?
      ",

      InstaView.conf.paths.articles + InstaView.conf.locale.image + ':' + filename,

      InstaView.conf.paths.magnify_icon,

      parse_inline_nowiki(caption)

      )

      } else {

      o += '

      ' + make_image(filename, caption) + f("
      ?
      ", parse_inline_nowiki(caption))

      }

      o += '

      ';

      } else if (align != '') {

      o += f("

      ?
      ", align, make_image(filename, caption, width));

      } else {

      return make_image(filename, caption, width);

      }

      return center? f("

      ?
      ", o): o;

      }

      function parse_inline_nowiki(str)

      {

      var start, lastend=0

      var substart=0, nestlev=0, open, close, subloop;

      var html='';

      while (-1 != (start = str.indexOf('', substart))) {

      html += parse_inline_wiki(str.substring(lastend, start));

      start += 8;

      substart = start;

      subloop = true;

      do {

      open = str.indexOf('', substart);

      close = str.indexOf('', substart);

      if (close<=open || open==-1) {

      if (close==-1) {

      return html + html_entities(str.substr(start));

      }

      substart = close+9;

      if (nestlev) {

      nestlev--;

      } else {

      lastend = substart;

      html += html_entities(str.substring(start, lastend-9));

      subloop = false;

      }

      } else {

      substart = open+8;

      nestlev++;

      }

      } while (subloop)

      }

      return html + parse_inline_wiki(str.substr(lastend));

      }

      function make_image(filename, caption, width)

      {

      // uppercase first letter in file name

      filename = filename.charAt(0).toUpperCase() + filename.substr(1);

      // replace spaces with underscores

      filename = filename.replace(/ /g, '_');

      caption = strip_inline_wiki(caption);

      var md5 = hex_md5(filename);

      var source = md5.charAt(0) + '/' + md5.substr(0,2) + '/' + filename;

      if (width) width = "width='" + width + "px'";

      var img = f("", InstaView.conf.paths.images_fallback + source, InstaView.conf.paths.images + source, (caption!=)? "alt='" + caption + "'" : , width);

      return f("?", (caption!=)? "title='" + caption + "'" : , InstaView.conf.paths.articles + InstaView.conf.locale.image + ':' + filename, img);

      }

      function parse_inline_images(str)

      {

      var start, substart=0, nestlev=0;

      var loop, close, open, wiki, html;

      while (-1 != (start=str.indexOf('[[', substart))) {

      if(str.substr(start+2).match(RegExp('^' + InstaView.conf.locale.image + ':','i'))) {

      loop=true;

      substart=start;

      do {

      substart+=2;

      close=str.indexOf(']]',substart);

      open=str.indexOf('[[',substart);

      if (close<=open||open==-1) {

      if (close==-1) return str;

      substart=close;

      if (nestlev) {

      nestlev--;

      } else {

      wiki=str.substring(start,close+2);

      html=parse_image(wiki);

      str=str.replace(wiki,html);

      substart=start+html.length;

      loop=false;

      }

      } else {

      substart=open;

      nestlev++;

      }

      } while (loop)

      } else break;

      }

      return str;

      }

      // the output of this function doesn't respect the FILO structure of HTML

      // but since most browsers can handle it I'll save myself the hassle

      function parse_inline_formatting(str)

      {

      var em,st,i,li,o='';

      while ((i=str.indexOf("''",li))+1) {

      o += str.substring(li,i);

      li=i+2;

      if (str.charAt(i+2)=="'") {

      li++;

      st=!st;

      o+=st?'':'';

      } else {

      em=!em;

      o+=em?'':'';

      }

      }

      return o+str.substr(li);

      }

      function parse_inline_wiki(str)

      {

      var aux_match;

      str = parse_inline_images(str);

      str = parse_inline_formatting(str);

      // math

      while (aux_match = str.match(/<(?:)math>(.*?)<\/math>/i)) {

      var math_md5 = hex_md5(aux_match[1]);

      str = str.replace(aux_match[0], f("", InstaView.conf.paths.math+math_md5));

      }

      // Build a Mediawiki-formatted date string

      var date = new Date;

      var minutes = date.getUTCMinutes();

      if (minutes < 10) minutes = '0' + minutes;

      var date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), InstaView.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());

      // text formatting

      return str.

      // signatures

      replace(/~{5}(?!~)/g, date).

      replace(/~{4}(?!~)/g, InstaView.conf.user.name+' '+date).

      replace(/~{3}(?!~)/g, InstaView.conf.user.name).

      // :Category:..., :Image:..., etc...

      replace(RegExp('\\[\\[:((?:'+InstaView.conf.locale.category+'|'+InstaView.conf.locale.image+'|'+InstaView.conf.wiki.interwiki+'):.*?)\\]\\]','gi'), "$1").

      replace(RegExp('\\[\\[(?:'+InstaView.conf.locale.category+'|'+InstaView.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').

      // /Relative links

      replace(/\[\[(\/[^|]*?)\]\]/g, f("$1", location)).

      // Relative links

      replace(/\[\[(\/.*?)\|(.+?)\]\]/g, f("$2", location)).

      // Common links

      replace(/\[\[([^|]*?)\]\](\w*)/g, f("$1$2", InstaView.conf.paths.articles)).

      // Links

      replace(/\[\[(.*?)\|([^\]]+?)\]\](\w*)/g, f("$2$3", InstaView.conf.paths.articles)).

      // Namespace

      replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, f("$2", InstaView.conf.paths.articles)).

      // External links

      replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, "$4").

      replace(/\[http:\/\/(.*?)\]/g, "[#]").

      replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, "$1:$2$3").

      replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*)/g, "$1$2:$3$4").

      replace('__NOTOC__','').

      replace('__NOEDITSECTION__','');

      }

      function strip_inline_wiki(str)

      {

      return str

      .replace(/\[\^\*\|(.*?)\]\]/g,'$1')

      .replace(/\[\[(.*?)\]\]/g,'$1')

      .replace(/(.*?)/g,'$1');

      }

      // begin parsing

      for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {

      p=0

      endl(f('??', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]))

      } else if ($(/^[*#:;]/)) {

      p=0

      parse_list()

      } else if ($(' ')) {

      p=0

      parse_pre()

      } else if ($('{|')) {

      p=0

      parse_table()

      } else if ($(/^----+$/)) {

      p=0

      endl('


      ')

      } else if ($(InstaView.BLOCK_IMAGE)) {

      p=0

      parse_block_image()

      } else {

      // handle paragraphs

      if ($$('')) {

      if (p = (remain()>1 && ll[1]==(''))) endl('


      ')

      } else {

      if(!p) {

      ps('

      ')

      p=1

      }

      ps(parse_inline_nowiki(ll[0]) + ' ')

      }

      sh();

      }

      return o

      }

      /*

      * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message

      * Digest Algorithm, as defined in RFC 1321.

      * Version 2.2-alpha Copyright (C) Paul Johnston 1999 - 2005

      * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet

      * Distributed under the BSD License

      * See http://pajhome.org.uk/crypt/md5 for more info.

      */

      /*

      * Configurable variables. You may need to tweak these to be compatible with

      * the server-side, but the defaults work in most cases.

      */

      var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */

      var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */

      /*

      * These are the functions you'll usually want to call

      * They take string arguments and return either hex or base-64 encoded strings

      */

      function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }

      function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }

      function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }

      function hex_hmac_md5(k, d)

      { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }

      function b64_hmac_md5(k, d)

      { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }

      function any_hmac_md5(k, d, e)

      { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

      /*

      * Calculate the MD5 of a raw string

      */

      function rstr_md5(s)

      {

      return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));

      }

      /*

      * Calculate the HMAC-MD5, of a key and some data (raw strings)

      */

      function rstr_hmac_md5(key, data)

      {

      var bkey = rstr2binl(key);

      if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);

      var ipad = Array(16), opad = Array(16);

      for(var i = 0; i < 16; i++)

      {

      ipad[i] = bkey[i] ^ 0x36363636;

      opad[i] = bkey[i] ^ 0x5C5C5C5C;

      }

      var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);

      return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));

      }

      /*

      * Convert a raw string to a hex string

      */

      function rstr2hex(input)

      {

      var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";

      var output = "";

      var x;

      for(var i = 0; i < input.length; i++)

      {

      x = input.charCodeAt(i);

      output += hex_tab.charAt((x >>> 4) & 0x0F)

      + hex_tab.charAt( x & 0x0F);

      }

      return output;

      }

      /*

      * Convert a raw string to a base-64 string

      */

      function rstr2b64(input)

      {

      var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

      var output = "";

      var len = input.length;

      for(var i = 0; i < len; i += 3)

      {

      var triplet = (input.charCodeAt(i) << 16)

      | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)

      | (i + 2 < len ? input.charCodeAt(i+2) : 0);

      for(var j = 0; j < 4; j++)

      {

      if(i * 8 + j * 6 > input.length * 8) output += b64pad;

      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);

      }

      }

      return output;

      }

      /*

      * Convert a raw string to an arbitrary string encoding

      */

      function rstr2any(input, encoding)

      {

      var divisor = encoding.length;

      var remainders = Array();

      var i, q, x, quotient;

      /* Convert to an array of 16-bit big-endian values, forming the dividend */

      var dividend = Array(input.length / 2);

      for(i = 0; i < dividend.length; i++)

      {

      dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);

      }

      /*

      * Repeatedly perform a long division. The binary array forms the dividend,

      * the length of the encoding is the divisor. Once computed, the quotient

      * forms the dividend for the next step. We stop when the dividend is zero.

      * All remainders are stored for later use.

      */

      while(dividend.length > 0)

      {

      quotient = Array();

      x = 0;

      for(i = 0; i < dividend.length; i++)

      {

      x = (x << 16) + dividend[i];

      q = Math.floor(x / divisor);

      x -= q * divisor;

      if(quotient.length > 0 || q > 0)

      quotient[quotient.length] = q;

      }

      remainders[remainders.length] = x;

      dividend = quotient;

      }

      /* Convert the remainders to the output string */

      var output = "";

      for(i = remainders.length - 1; i >= 0; i--)

      output += encoding.charAt(remainders[i]);

      return output;

      }

      /*

      * Encode a string as utf-8.

      * For efficiency, this assumes the input is valid utf-16.

      */

      function str2rstr_utf8(input)

      {

      var output = "";

      var i = -1;

      var x, y;

      while(++i < input.length)

      {

      /* Decode utf-16 surrogate pairs */

      x = input.charCodeAt(i);

      y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;

      if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)

      {

      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);

      i++;

      }

      /* Encode output as utf-8 */

      if(x <= 0x7F)

      output += String.fromCharCode(x);

      else if(x <= 0x7FF)

      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),

      0x80 | ( x & 0x3F));

      else if(x <= 0xFFFF)

      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),

      0x80 | ((x >>> 6 ) & 0x3F),

      0x80 | ( x & 0x3F));

      else if(x <= 0x1FFFFF)

      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),

      0x80 | ((x >>> 12) & 0x3F),

      0x80 | ((x >>> 6 ) & 0x3F),

      0x80 | ( x & 0x3F));

      }

      return output;

      }

      /*

      * Encode a string as utf-16

      */

      function str2rstr_utf16le(input)

      {

      var output = "";

      for(var i = 0; i < input.length; i++)

      output += String.fromCharCode( input.charCodeAt(i) & 0xFF,

      (input.charCodeAt(i) >>> 8) & 0xFF);

      return output;

      }

      function str2rstr_utf16be(input)

      {

      var output = "";

      for(var i = 0; i < input.length; i++)

      output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,

      input.charCodeAt(i) & 0xFF);

      return output;

      }

      /*

      * Convert a raw string to an array of little-endian words

      * Characters >255 have their high-byte silently ignored.

      */

      function rstr2binl(input)

      {

      var output = Array(input.length >> 2);

      for(var i = 0; i < output.length; i++)

      output[i] = 0;

      for(var i = 0; i < input.length * 8; i += 8)

      output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);

      return output;

      }

      /*

      * Convert an array of little-endian words to a string

      */

      function binl2rstr(input)

      {

      var output = "";

      for(var i = 0; i < input.length * 32; i += 8)

      output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);

      return output;

      }

      /*

      * Calculate the MD5 of an array of little-endian words, and a bit length.

      */

      function binl_md5(x, len)

      {

      /* append padding */

      x[len >> 5] |= 0x80 << ((len) % 32);

      x[(((len + 64) >>> 9) << 4) + 14] = len;

      var a = 1732584193;

      var b = -271733879;

      var c = -1732584194;

      var d = 271733878;

      for(var i = 0; i < x.length; i += 16)

      {

      var olda = a;

      var oldb = b;

      var oldc = c;

      var oldd = d;

      a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);

      d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);

      c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);

      b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);

      a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);

      d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);

      c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);

      b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);

      a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);

      d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);

      c = md5_ff(c, d, a, b, x[i+10], 17, -42063);

      b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);

      a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);

      d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);

      c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);

      b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);

      a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);

      d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);

      c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);

      b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);

      a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);

      d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);

      c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);

      b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);

      a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);

      d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);

      c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);

      b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);

      a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);

      d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);

      c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);

      b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

      a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);

      d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);

      c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);

      b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);

      a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);

      d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);

      c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);

      b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);

      a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);

      d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);

      c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);

      b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);

      a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);

      d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);

      c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);

      b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

      a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);

      d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);

      c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);

      b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);

      a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);

      d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);

      c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);

      b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);

      a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);

      d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);

      c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);

      b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);

      a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);

      d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);

      c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);

      b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

      a = safe_add(a, olda);

      b = safe_add(b, oldb);

      c = safe_add(c, oldc);

      d = safe_add(d, oldd);

      }

      return Array(a, b, c, d);

      }

      /*

      * These functions implement the four basic operations the algorithm uses.

      */

      function md5_cmn(q, a, b, x, s, t)

      {

      return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);

      }

      function md5_ff(a, b, c, d, x, s, t)

      {

      return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);

      }

      function md5_gg(a, b, c, d, x, s, t)

      {

      return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);

      }

      function md5_hh(a, b, c, d, x, s, t)

      {

      return md5_cmn(b ^ c ^ d, a, b, x, s, t);

      }

      function md5_ii(a, b, c, d, x, s, t)

      {

      return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);

      }

      /*

      * Add integers, wrapping at 2^32. This uses 16-bit operations internally

      * to work around bugs in some JS interpreters.

      */

      function safe_add(x, y)

      {

      var lsw = (x & 0xFFFF) + (y & 0xFFFF);

      var msw = (x >> 16) + (y >> 16) + (lsw >> 16);

      return (msw << 16) | (lsw & 0xFFFF);

      }

      /*

      * Bitwise rotate a 32-bit number to the left.

      */

      function bit_rol(num, cnt)

      {

      return (num << cnt) | (num >>> (32 - cnt));

      }

      //