Usuario:Atalanta86/Live Preview/sources.js

De Wikipedia, la enciclopedia libre

Nota: Después de guardar, debes refrescar la caché de tu navegador para ver los cambios. Internet Explorer: mantén presionada Ctrl mientras pulsas Actualizar. Firefox: mientras presionas Mayús pulsas el botón Actualizar, (o presiona Ctrl-Shift-R). Los usuarios de Google Chrome y Safari pueden simplemente pulsar el botón Recargar. Para más detalles e instrucciones acerca de otros exploradores, véase Ayuda:Cómo limpiar la caché.

//<nowiki>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * MediaWiki to HTML converter
 * Written by Pedro Fayolle (pfayolle@gmail.com)
 *
 * Permission is granted to use and distribute.
 * You can modify the code provided that you send me message with the changes.
 *
 * Mediawiki is maintained by the Wikimedia Foundation
 * http://wikipedia.sourceforge.net
 * http://wikimedia.org
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * CHANGELOG:
 *
 * 0.5.1
 * + Added image failback (e.g. for Commons)
 * + Added support for inline images with complex syntax and full nesting
 * + Fixed some bugs with tables
 *
 * 0.5
 *
 * + Added basic image support, still needs _much_ improvement.
 *
 * 0.4.3
 * + Made code more object oriented by putting all self-modifying WikiCode methods
 *   inside the class. This probably means faster code execution too.
 *
 * 0.4.2
 * + Fixed bug in parsing of links with replaced text
 * + Fixed bug with combined bold and italics text
 * + Added support for continuous line breaking
 * + Added support for invisible namespaces in links (categories, interwiki, etc)
 * + Added support for signatures
 * + Done a few minor optimizations
 *
 * 0.4.1
 * + Fixed several bugs, mostly related to tables
 *
 * 0.4
 * + Added support for tables (with nesting)
 *
 * 0.3
 * + Added support for nested lists and definition lists (can all be mixed)
 *
 *
 * TODO:
 *
 * - Add HTML stripping
 * - Add nowiki support
 * - Add support for [[/subpage]] link syntax
 *
 *
 * DESIGN NOTES:
 *
 * This parser is not based on the original Mediawiki parser code (written in PHP),
 * but on its direct output instead. I figured it would take me longer if I had to
 * learn how the original parser does its trick.
 *
 * Some parts of the HTML generation are handled in a lazy fashion. That is, tags
 * are not always formally closed (with a </*>) as all browsers (or those I've tested)
 * can handle that sort of HTML very neatly, this saves for some code complexity.
 *
 * Stupid me! I spent days trying to figure out a neat way of preloading each used
 * image just to have the width and height beforehand so I could compute the right
 * height based on the original proportions and the given width... and, guess what?
 * simply by giving the width the browser will resize the image proportionally, so
 * there was not damn need to fetch the original proportions. I wanna slap myself.
 * Thinking again, preloading would be useful to check if there's already a thumb of
 * the image or to failback to commons.
 *
 * JAVASCRIPT NOTES:
 *
 * As much as I'd like to use the prettier string[char_index] syntax for char-wise
 * comparisons, neither Opera or IE seem to support that kind of syntax, forcing
 * me to use the much uglier string.substr() method instead. I'm new to JS, so if
 * anyone knows of a nicier (faster?) way of doing this just let me know.
 * UPDATED: hadn't noticed string.charAt(pos), I'm still not entirely happy though.
 *
 * Apparently variables declared without 'var' become globals, so be *very* careful.
 *
 * When passing function parameters, only primitives get passed by value, objects get
 * passed by reference.
 *
 * REFERENCES:
 *
 * - Optimization tips:
 *   http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html
 * - MD5 hash generator:
 *   http://pajhome.org.uk/crypt/md5/
 * - Javascript Compression:
 *   http://dean.edwards.name/packer/
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */



/*** User defined options ***/



// Your username
var wpUserName = wpUserName || 'Wikipedian';


// Signature string (user name)
var wpUserSignature = wpUserSignature || wpUserName;


// Download and display images
var wpShowImages = wpShowImages || true;


/*** System-wide options ***/



// Language code for local wiki
var wpLanguageCode = 'en';


// Interwiki codes
var wpInterwikiCodes = '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';


// Base path for internal links
var wpBaseArticlePath = '/wiki/';


// Image config vars
var wpImageBasePath = 'http://upload.wikimedia.org/wikipedia/' + wpLanguageCode + '/';
var wpImageFailbackPath = 'http://upload.wikimedia.org/wikipedia/commons/';
var wpDefaultThumbWidth = 180;
var wpSkinMagnifyClip = '/skins/common/images/magnify-clip.png';


// Namespaces
var wpUserNamespace = 'User';
var wpImageNamespace = 'Image';
var wpCategoryNamespace = 'Category';



/*** DO NOT EDIT BELOW THIS LINE ***/



// Main function, takes raw wikicode and outputs HTML
function wiki2html(str)
{
 str = strip_cr(str);
 var w = new WikiCode();
 w.lines = str.split(/\n/);
 w.parse();
 return w.html;
};


// Base signature format
var wpSignature = '[['+wpUserNamespace+':'+wpUserName+'|'+wpUserSignature+']]';


// Invisible namespace links matching regexp
var wpInvisibleNamespaces = new RegExp('\\[\\[(?:'+wpCategoryNamespace+'|'+wpInterwikiCodes+'):.*?\\]\\]', 'gi')


// Image matching regexps
var wpBlockImage = new RegExp('^\\[\\['+wpImageNamespace+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');


function WikiCode()
{
 this.lines = new Array;
 this.html = new String;

 this._endline = function(str)
 {
  this.html += str;
  this.lines.shift();
 };

 this.parse = function()
 {
  var p = false; // flag for checking if there's an open P element

  do {
   if ((h_match = this.lines[0].match(/^(={1,6})(.*)\1(.*)$/)) != null) { // heading
    p = false;
    this._endline('<h' + h_match[1].length + '>' + _parse_inline_wiki(h_match[2]) + '</h' + h_match[1].length + '>' + h_match[3]);
   } else if (this.lines[0].match(/^[*#:;]/) != null) { // list
    p = false;
    this._parse_list();
   } else if (this.lines[0].charAt(0) == ' ') { // preformatted text
    p = false;
    this._parse_pre();
   } else if (this.lines[0].substr(0, 2) == '{|') { // table
    p = false;
    this._parse_table();
   } else if (this.lines[0].match(/^----+$/) != null) { // horizontal bar
    p = false;
    this._endline('<hr/>');
   } else if (this.lines[0].match(wpBlockImage) != null) { // block image
    p = false;
    this._parse_block_image();
   } else {
    /*
     * NOTE: P elements can't contain block-level elements (including P),
     * so it's safe not to close P tags as anything else will break it.
     */
    if (this.lines[0] == '') {
     if (p = (this.lines.length > 1 && this.lines[1] == '')) {
      this._endline('<p><br />');
     }
    } else {
     if (!p) {
      this.html += '<p>';
      p = true;
     }
     this.html += _parse_inline_wiki(this.lines[0]) + ' ';
    }
    this.lines.shift();
   }
  } while (this.lines.length);
 };

 // parse nested lists and definitions
 this._parse_list = function() {

  var prev = new String;
  var l_match, imatch;

  while (this.lines.length && (l_match = this.lines[0].match(/^([*#:;]+)(.*)$/)) != null) {

   this.lines.shift();
   imatch = str_imatch(prev, l_match[1]);

   /* TODO:
   * Try to optimize this by using an auxiliary buffer to close
   * and open tags within the same loop
   */
   for (var i = prev.length-1; i >= imatch; i--) { // close tags
    if (prev.charAt(i) == '*') {
     this.html += '</ul>';
    } else if (prev.charAt(i) == '#') {
     this.html += '</ol>'
    } else {
     this.html += '</d' + ((prev.charAt(i) == ';')?'t':'d') + '>';
     switch (l_match[1].charAt(i)) {
      case '': case '*': case '#': this.html += '</dl>';
     }
    }
   }

   for (var i = imatch; i < l_match[1].length; i++) { // open tags
    if (l_match[1].charAt(i) == '*') {
     this.html += '<ul>';
    } else if (l_match[1].charAt(i) == '#') {
     this.html += '<ol>';
    } else {
     switch (prev.charAt(i)) {
      case '': case '*': case '#': this.html += '<dl>';
     }
     this.html += '<d' + ((l_match[1].charAt(i) == ';')?'t':'d') + '>';
    }
   }

   if (l_match[1].charAt(l_match[1].length-1) == '*' || l_match[1].charAt(l_match[1].length-1) == '#') {
    this.html += '<li>' + _parse_inline_wiki(l_match[2]) + '</li>';
   } else {
    if ((dt_match = l_match[2].match(/(.*?) (:.*?)$/)) != null) {
     this.html += _parse_inline_wiki(dt_match[1]);
     this.lines.unshift(dt_match[2]);
    } else {
     this.html += _parse_inline_wiki(l_match[2]);
    }
   }

   prev = l_match[1];
  }

  // TODO: Try to include this in main loop
  for (i = prev.length-1; i >= 0; i--) { // close remaining
   if (prev.charAt(i) == '*') {
    this.html += '</ul>';
   } else if (prev.charAt(i) == '#') {
    this.html += '</ol>';
   } else {
    this.html += '</d' + ((prev.charAt(i) == ';')?'t':'d') + '></dl>';
   }
  }
 };


 this._parse_table = function()
 {
  var table_match;

  if ((table_match = this.lines[0].match(/^\{\|( .*)$/)) != null) {
   this._endline('<table' + table_match[1] + '>');
  } else this._endline('<table>');

  do {
   if (this.lines[0].charAt(0) == '|') {
    switch (this.lines[0].charAt(1)) {
     case '}':
      this._endline('</table>');
      return;
     case '-':
      this._endline('<tr ' + this.lines[0].match(/\|-*(.*)/)[1] + '>');
      break;
     default:
      this._parse_table_data();
    }
   } else if (this.lines[0].charAt(0) == '!') {
    this._parse_table_data();
   } else {
    this.lines.shift();
   }
  } while (this.lines.length)
 };

 // parse table data (td, th, caption)
 this._parse_table_data = function()
 {
  var td_match, td_line;

  /*
  * Regexp disection:
  *
  * 1) ^(\|\+|\||!)
  *    match either |+, | or ! at beggining of line
  * 2) ((?:(.*?)\|(?!\|))?(.*))$
  *    match whole line (sub-matching follows...)
  * 3) (?:([^[|]*?)\|(?!\|))?
  *    match HTML attributes and make sure we don't match a wikilink
  * 4) (.*)$
  *    Match the rest
  */
  td_match = this.lines.shift().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);

  if (td_match[1] == '|+') {
   this.html += '<caption';
  } else {
   this.html += '<t' + ((td_match[1] == '|')?'d':'h');
  }

  if (typeof td_match[3] != 'undefined') {
   this.html += ' ' + td_match[3]; // insert attributes
   td_line = td_match[4].split('||');
  } else {
   td_line = td_match[2].split('||');
  }
  this.html += '>';

  while (td_line.length > 1) { // send inline cells to next pass TODO: handle this locally to avoid extra parsing overhead
   this.lines.unshift(td_match[1] + td_line.pop());
  }
  this.html += _parse_inline_wiki(td_line[0]);

  var td = new WikiCode;
  var table_count = 0;
  while (this.lines.length) {
   // manage nested tables
   if (this.lines[0].charAt(0) == '|') {
    if (table_count == 0) break;
    else if (this.lines[0].charAt(1) == '}') table_count--;
   } else if (this.lines[0].charAt(0) == '!' && table_count == 0) {
    break;
   } else if (this.lines[0].substr(0, 2) == '{|') table_count++;
   td.lines.push(this.lines.shift());
  }

  if (td.lines.length) {
   td.parse();
  }

  this.html += td.html;
 };

 // parse pre code
 this._parse_pre = function()
 {
  this.html += '<pre>';
  do {
   this._endline(_parse_inline_wiki(this.lines[0].substring(1, this.lines[0].length)) + "\n");
  } while (this.lines.length && this.lines[0].charAt(0) == ' ');
  this.html += '</pre>';
 };

 // parse block images (ie. not simple inline images)
 this._parse_block_image = function()
 {
  // FIXME: make this handle multiple lines and nested wiki

  /*var line = '';
  var exitflag = true;
  do { // put full image syntax into a single line
   exitflag = (this.lines[0].replace(/\[\[.*?\]\]/, '').match(/\]\]$/) == null); // evals to false to exit
   if (!exitflag) alert('a');
   line += this.lines.shift() + ' ';
  } while (this.lines.length && exitflag);
  this.html += _parse_image(line.substring(0, line.length - 1));*/

  this.html += _parse_image(this.lines.shift());
 };
};

// parse a wikicode image and return its HTML code
function _parse_image(str)
{
 var attr = str.substring(wpImageNamespace.length + 3, str.length - 2).split(/\s*\|\s*/);

 var filename = attr[0];
 var caption = attr[attr.length-1];
 //var width = '';
 var width, w_match;
 var thumb = false;
 var frame = false;
 var center = false;
 var align = '';
 var html = '';

 do { // mine image information
  if ((w_match = attr[0].match(/^(\d*)px$/)) != null) {
   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';
  }
  attr.shift();
 } while (attr.length);

 if (frame) {
  if (align == '') align = 'right';
  html += "<div class='thumb t" + align + "'>";
  if (thumb) {
   if (width == '') width = wpDefaultThumbWidth;
   html += "<div style='width:" + (2 + parseInt(width)) + "px;'>";
   html += _make_image(filename, caption, width);
   html += "<div class='thumbcaption'><div class='magnify' style='float:right'><a href='" + wpBaseArticlePath + wpImageNamespace + ':' + filename + "' class='internal' title='Enlarge'><img src='" + wpSkinMagnifyClip + "' /></a></div>" + _parse_inline_wiki(caption) + "</div>";
  } else {
   html += '<div>';
   html += _make_image(filename, caption);
   html += "<div class='thumbcaption'>" + _parse_inline_wiki(caption) + "</div>";
  }
  html += '</div></div>';
 } else if (align != '') {
  html += "<div class='float" + align + "'><span>" + _make_image(filename, caption, width) + "</span></div>";
 } else {
  return _make_image(filename, caption, width);
 }

 if (center) {
  return "<div class='center'>" + html + '</div>';
 } else {
  return html;
 }
};

function _make_image(filename, caption, /*optional*/ width)
{
 filename = filename.charAt(0).toUpperCase() + filename.substr(1); // capitalize
 filename = filename.replace(/ /g, '_'); // replace spaces with underscore
 var md5 = hex_md5(filename);
 var source = md5.charAt(0) + '/' + md5.substr(0, 2) + '/' + filename;
 var img;
 if (wpShowImages) {
  if (width) {
   width = "width='" + width + "px'";
  }
  img = "<img onerror='this.onerror=null;this.src=\"" + wpImageFailbackPath +  source + "\";' src='" + wpImageBasePath + source + "' alt='" + caption + "' " + width + "/>";
 } else {
  img = wpImageNamespace + ':' + filename + " <em style='color:red;'>(images disabled)</em>";
 }
 caption = _strip_inline_wiki(caption);
 return "<a class='image' title='" + caption + "' href='" + wpBaseArticlePath + wpImageNamespace + ':' + filename + "'>" + img + "</a>";
};

function _parse_inline_images(str)
{
 var start, substart = 0, nestlev = 0;
 var loop, close, open, wiki, html;

 while (-1 != (start = str.indexOf('[[', substart) )) { // iterate through each top-level image

  if (str.substr(start+2).match(RegExp('^' + wpImageNamespace + ':', 'i')) != null) {

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

// parse links and common inline formatting
function _parse_inline_wiki(str)
{
 // FIXME: simplify this mess!
 str = str.replace(/'''''(.*?)''(.*?)'''/g, '<strong><em>$1</em>$2</strong>');
 str = str.replace(/'''''(.*?)'''(.*?)''/g, '<em><strong>$1</strong>$2</em>');
 str = str.replace(/'''(.*?)''(.*?)'''''/g, '<strong>$1<em>$2</em></strong>');
 str = str.replace(/'''(.*?)'''/g, '<strong>$1</strong>');
 str = str.replace(/''(.*?)''/g, '<em>$1</em>');

 str = str.replace(/~{4,5}(?!~)/g, wpSignature + ' ' + Date()); // signature
 str = str.replace(/~{3}(?!~)/g, wpSignature); // simple signature

 str = _parse_inline_images(str); // inline images

 str = str.replace(wpInvisibleNamespaces, ''); // remove invisible namespaces

 str = str.replace(/\[\[([^|]*?)\]\](\w*)/g, "<a href='" + wpBaseArticlePath + "$1'>$1$2</a>"); // normal wikilink
 str = str.replace(/\[\[(.*?)\|([^\]]+?)\]\](\w*)/g, "<a href='" + wpBaseArticlePath + "$1'>$2$3</a>"); // wikilink with replaced text
 str = str.replace(/\[\[([^\]]*:)?(.*?)( *\(.*?\))?\|\]\]/g, "<a href='" + wpBaseArticlePath + "$1$2$3'>$2</a>"); // hidden namespace & parentheses

 str = str.replace(/\[(http|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, "<a href='$1:$2$3'>$4</a>");
 str = str.replace(/\[http:\/\/(.*?)\]/g, "<a href='http://$1'>[#]</a>");
 str = str.replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, "<a href='$1:$2$3'>$1:$2$3</a>");
 str = str.replace(/(^| )(http|news|ftp|mailto|gopher|irc):(\/*)([^ $]*)/g, "$1<a href='$2:$3$4'>$2:$3$4</a>");

 str = str.replace('__NOTOC__', '');
 str = str.replace('__NOEDITSECTION__', '');

 return str;
};

function _strip_inline_wiki(str)
{
 str = str.replace(/\[\[[^\]]*\|(.*?)\]\]/g, '$1');
 str = str.replace(/\[\[(.*?)\]\]/g, '$1');
 str = str.replace(/''(.*?)''/g, '$1');
 return str;
};


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


// return the greatest of two numbers
function max(a, b)
{
 if (a > b) return a; else return b;
};

// return the smallest of two numbers
function min(a, b)
{
 if (a < b) return a; else return b;
};

// find the length of the initial matching segment between two strings
function str_imatch(str_a, str_b)
{
 var lim = min(str_a.length, str_b.length);
 for (var i = 0; i < lim; i++) {
  if (str_a.charAt(i) != str_b.charAt(i)) return i;
 }
 return i;
};

// remove carriage returns
function strip_cr(str)
{
 str = str.replace(/\n\r/g, "\n");
 str = str.replace(/\r/g, '');
 return str;
};



/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 *
 * Stripped down for wikicode parser use
 */


var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
var hex_tab = "0123456789abcdef";

function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz)); };

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_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));
};

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
};

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var str = '';
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
};