/* *********************************************************
** FORMNUM2.JS - JS Number/Date/Time Formatting Library 2.0
** ========================================================
** This file contains the formatNum() function and all its
** supporting code. You use formatNum() to convert a number
** or Date object into a custom-formatted string, which you 
** can then document.write() to a Web page. For example, you
** could format the date 1/1/2001 as:
**   "1/1/01", "1-1-01", "1.1.01", "1/1/2001", "1-1-2001", 
**   "1.1.2001", "January 1, 2001", "1. January 2001", 
**   "January 1st, 2001", "January First, Two Thousand One", 
**   "Monday, January the First, Two Thousand One", etc.
** See the formatNum() header below for usage instructions. 
** And please be so kind as to maintain this header. :-) 
**
** For tons of examples of formatNum() in action, see the 
** formatNum() 1.0 and formatNum() 2.0 ScriptHead columns at:
**   www.zdnet.com/devhead/stories/articles/0,4413,2170375,00.html
**
** Author      Ver  Date     Comments
** ======      ===  ====     ========
** Rick Scott  2.0  11/1/00  Added shortcut tags, number
**                           rounding, formnum-.js; changed
**                           getTagstr() logic to simplify
**                           the switch() statement
** Rick Scott  1.0  10/1/00  Grand debut 
**
** Copyright 2000, Rick Scott, all rights reserved.
********************************************************* */


/* global vars ****************************************** */

var Tagstr = "";  // current ^...^ tag of formatNum() formatstr arg

var Months = new Array(  // month names
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December"
);

var Days = new Array(  // day names
  "Sunday",
  "Monday",
  "Tuesday",
  "Wednesday",
  "Thursday",
  "Friday",
  "Saturday"
);

var MIN_INT = -2147483648;  // JS min/max integer values
var MAX_INT = 2147483647; 

var cardnums = new Array(   // it's easier this way, trust me ... ;-) 
  "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", 
  "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", 
  "sixteen", "seventeen", "eighteen", "nineteen", "twenty", "twenty-one", 
  "twenty-two", "twenty-three", "twenty-four", "twenty-five", "twenty-six", 
  "twenty-seven", "twenty-eight", "twenty-nine", "thirty", "thirty-one", 
  "thirty-two", "thirty-three", "thirty-four", "thirty-five", "thirty-six", 
  "thirty-seven", "thirty-eight", "thirty-nine", "forty", "forty-one", 
  "forty-two", "forty-three", "forty-four", "forty-five", "forty-six", 
  "forty-seven", "forty-eight", "forty-nine", "fifty", "fifty-one", "fifty-two", 
  "fifty-three", "fifty-four", "fifty-five", "fifty-six", "fifty-seven", 
  "fifty-eight", "fifty-nine", "sixty", "sixty-one", "sixty-two", "sixty-three", 
  "sixty-four", "sixty-five", "sixty-six", "sixty-seven", "sixty-eight", 
  "sixty-nine", "seventy", "seventy-one", "seventy-two", "seventy-three", 
  "seventy-four", "seventy-five", "seventy-six", "seventy-seven", "seventy-eight",
  "seventy-nine", "eighty", "eighty-one", "eighty-two", "eighty-three", 
  "eighty-four", "eighty-five", "eighty-six", "eighty-seven", "eighty-eight", 
  "eighty-nine", "ninety", "ninety-one", "ninety-two", "ninety-three", "ninety-four",
  "ninety-five", "ninety-six", "ninety-seven", "ninety-eight", "ninety-nine"
);

var placevalues = new Array(  // for cardinalizing/ordinalizing numbers
  "", 
  " thousand ", 
  " million ", 
  " billion ",
  " trillion "  // it'll never go this high, but what the heck ... 
);

var lower = "lower";  // to specify case of formatted number string
var UPPER = "UPPER";
var Mixed = "Mixed";


/* formatNum() ****************************************** */
// usage: formatNum(num, formatstr)
// num - any valid numeric value or date object
// formatstr - str containing text and/or html tags and/or these formatNum() tags:
// number tags (examples: 1234.5678, -9)
//   ^[round|rndup|rnddn]#[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - "1234", "-9"
//     ^round#^ rounds num up or down to nearest integer - "1235", "-9"
//     ^rndup#^ rounds num up to nearest integer - "1235", "-9"
//     ^rnddn#^ rounds num down to nearest integer - "1234", "-9"
//     ^#ordtxt^ converts to ordinal text - 
//       "one thousand two hundred thirty-fourth", "minus ninth"
//       ^#Ordtxt^ and ^#ORDTXT^ capitalize differently
//     ^#ordnum^ converts to an ordinal number - "1234th", "-9th"
//       ^#ORDNUM" capitalizes differently
//     ^#card^ converts to a cardinal number - 
//       "one thousand two hundred thirty four", "minus nine"
//       ^#Card^ and "#CARD" capitalize differently
//   ^#.[card|Card|CARD]^ - 1234.5678, -9
//      rounding is inappropriate, since num is shown in its entirety
//   ^[round|rndup|rnddn]#.N[card|Card|CARD]^ - N is 1-8, ex: ^#.2^ - 1234.56, -9.00
//     ^round#.N^ rounds num up or down to nearest decimal value - "1235.57", "-9"
//     ^rndup#.N^ rounds num up to nearest decimal value >= - "1235.57", "-9"
//     ^rnddn#.N^ rounds num down to nearest decimal value >= - "1234.56", "-9"
//   ^[round|rndup|rnddn]#,###[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 1,234, -9
//   ^#,###.[card|Card|CARD]^ - 1,234.5678, -9
//      rounding is inappropriate, since num is shown in its entirety
//   ^[round|rndup|rnddn]#,###.N[card|Card|CARD]^ - N is 1-8, ex: ^#.2^ - 1,234.56, -9.00
// number shortcut tags (examples: 1, 2345.67)
//   ^[round|rndup|rnddn]int^ (= ^[round|rndup|rnddn]#^) - 1, 2345
//   ^float^ (= ^#.^) - 1, 2345.67
//      rounding is inappropriate, since num is shown in its entirety
//   ^[round|rndup|rnddn]thousands^ (= ^[round|rndup|rnddn]#,###^) - 1, 2,345
//   ^thousands_float^ (= ^#,###.^) - 1, 2,345.67
//      rounding is inappropriate, since num is shown in its entirety
//   ^[round|rndup|rnddn]currency^ (= ^[round|rndup|rnddn]#,###.##^) - 1.00, 2,345.00
// date formatting tags (example: 1/1/2001)
//   ^m[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 1
//   ^0m^ - 01
//   ^mon^ - jan
//   ^Mon^ - Jan
//   ^month^ - january
//   ^Month^ - January
//   ^d[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 1
//   ^0d^ - 01
//   ^dy^ - mon
//   ^Dy^ - Mon
//   ^day^ - monday
//   ^Day^ - Monday
//   ^y[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 1
//   ^0y^ - 01
//   ^yyyy[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 2001 
// date shortcut tags (example: 1/31/2000)
//   ^date^ (= ^m^/^d^/^0y^) - 1/31/00
//   ^date_slash^ (= ^m^/^d^/^0y^) - 1/31/00
//   ^date_dash^ (= ^m^-^d^-^0y^) - 1-31-00
//   ^date_dot^ (= ^m^.^d^.^0y^) - 1.31.00
//   ^date_euro^ (= ^m^.^d^.^0y^) - 31.1.00
//   ^date_euro_slash^ (= ^d^/^m^/^0y^) - 31/1/00
//   ^date_euro_dash^ (= ^d^-^m^-^0y^) - 31-1-00
//   ^date_euro_dot^ (= ^d^.^m^.^0y^) - 31.1.00
//   ^date_long^ (= ^Month^ ^d^, ^yyyy^) - January 31, 2000
//   ^date_longer^ (= ^Month^ ^dordnum^, ^yyyy^) - January 31st, 2000
//   ^date_long_euro^ (= ^d^. ^Month^, ^yyyy^) - 31. January, 2000
//   ^date_longer_euro^ (= ^dordnum^ ^Month^, ^yyyy^) - 31st January, 2000
//   ^date_longer_text^ (= ^Month^ ^dOrdtxt^, ^yyyyCard^)
//     - January Thirty First, Two Thousand
//   ^date_longest_text^ (= ^Month^ ^dOrdtxt^, ^yyyyCard^)
//     - Monday, January Thirty First, Two Thousand
// time formatting tags (examples: 9:08:09 a.m., 9:17:18 p.m.)
//   ^h[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 9, 9
//   ^0h^ - 09, 09
//   ^h12[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 9, 9
//   ^0h12^ - 09, 09
//   ^h24[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 9, 21
//   ^0h24^ - 09, 21
//   ^mi[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 8, 17
//   ^0mi^ - 08, 17
//   ^s[ordtxt|Ordtxt|ORDTXT|ordnum|ORDNUM|card|Card|CARD]^ - 9, 18
//   ^0s^ - 09, 18
//   ^am/pm^, ^AM/PM^, ^a.m./p.m.^, ^A.M./P.M.^
//     - am, pm, AM, PM, a.m., p.m., A.M., P.M.
// time shortcut tags (example: 2:08:07 p.m.)
//   ^time^ (= ^h^:^0mi^) - 2:08
//   ^time_12^ (= ^h^:^0mi^) - 2:08
//   ^time_24^ (= ^h24^:^0mi^) - 14:08
//   ^time_ampm^ (= ^h^:^0mi^ ^a.m./p.m.^) - 2:08 p.m.
//   ^time_long^ (= ^h^:^0mi^:^0s^) - 2:08:07
//   ^time_long_24^ (= ^h24^:^0mi^:^0s^) - 14:08:07
//   ^time_long_ampm^ (= ^h^:^0mi^:^0s^ ^a.m./p.m.^) - 2:08:07 p.m.
/* ****************************************************** */

function formatNum(num, formatstr) {

  // first, verify correctness of num and formatstr arguments
  //   you might want to remove these alert() statements ... 
  if (arguments.length != 2) {  // both arguments required
    alert("Error: Wrong number of arguments to formatNum().");
    return;
  }
  if (typeof num != "number" && typeof num != "object") {
    alert("Error: First argument to formatNum() must be a number or Date object.");
    return;
  }
  if (typeof num == "number" && (num < MIN_INT || num > MAX_INT)) { 
    alert("Error: JS cannot process numbers < " + MIN_INT + " or > " + MAX_INT + ".");
    return;
  }
  if (typeof formatstr != "string") {
    alert("Error: Second argument to formatNum() must be a string.");
    return;
  }
  
  // okay, num and formatstr args are fine, let's process, baybey! 
  var returnstr = "";
  for (var index=0; index<formatstr.length; index++) {
    if (formatstr.charAt(index) == "^") {
      index = getTagstr(num, formatstr, index);  // returns index of closing tag ^ 
      returnstr += Tagstr;
    }
    else {
      returnstr += formatstr.charAt(index);
    }
  }
  return returnstr;  
}


/* getTagstr() **************************************** */
/* assigns a value to the global var Tagstr *********** */
/*   and returns index of closing tag ^ delimiter ***** */

function getTagstr(num, formatstr, index) {

  var tag = "";
  var ordval = "";
  var cardval = "";
  var roundval = "";
  Tagstr = "";

  index++;  // advance to next char after opening tag ^ delimiter
  while (formatstr.charAt(index) != "^")
    tag += formatstr.charAt(index++);

  // set ordval, cardval, roundval vals then remove these codes from tag

  if (tag.toLowerCase().indexOf("ord") != -1) {
    ordval = tag.substring(tag.toLowerCase().indexOf("ord"), tag.length);
    tag = delsubstr(ordval, tag);
  }
  else if (tag.toLowerCase().indexOf("card") != -1) {
    cardval = tag.substring(tag.toLowerCase().indexOf("card"), tag.length);
    tag = delsubstr(cardval, tag);
  }
  if (tag.charAt(0) == "r") {
    roundval = tag.substring(0, 5);
    tag = delsubstr(roundval, tag);
  }

  switch (tag) {  // and away we go ... ! 

    /* number cases ************************************* */

    case "#": case "int":
      num = roundInt(num, roundval);  // round num, if specified in tag
      Tagstr = "" + parseInt(num);
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "#.": case "float": 
      Tagstr = "" + num + ".";
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "#.1": case "#.2": case "#.3": case "#.4":
    case "#.5": case "#.6": case "#.7": case "#.8":
      var decnums = parseInt(tag.substring(2,3));  // get specified # dec nums
      Tagstr = roundFloat(num, decnums, roundval);  // round float, if spec in tag
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "#,###": case "thousands": 
      num = roundInt(num, roundval);  // round num, if specified in tag
      Tagstr = "" + parseInt(num);
      var numunits = splitIntoThrees(Tagstr);  // split into threes
      Tagstr = "";
      for (var i=numunits.length-1; i>=0; i--)   // then insert commas
        Tagstr += numunits[i] + ((i > 0) ? "," : "");
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "#,###.": case "thousands_float": 
      num = "" + num;  // turn num into a string
      var numparts = num.split(".");  // then split into int + dec parts
      Tagstr = "" + parseInt(numparts[0]);
      numunits = splitIntoThrees(Tagstr);     // split into threes
      Tagstr = "";
      for (var i=numunits.length-1; i>=0; i--)  // then insert commas
        Tagstr += numunits[i] + ((i > 0) ? "," : "");
      if (numparts.length > 1)
        Tagstr += "." + numparts[1];
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "#,###.1": case "#,###.2": case "#,###.3": case "#,###.4":
    case "#,###.5": case "#,###.6": case "#,###.7": case "#,###.8":
    case "currency": 
      if (tag == "currency")
        tag = "#,###.2";
      var decnums = parseInt(tag.substring(6,7));  // get specified # dec nums
      Tagstr = roundFloat(num, decnums, roundval);  // round float, if spec in tag
      // at this point, Tagstr = #.N
      // now we need #,###.N -- to do this, we need to convert # to #,###
      var numparts = Tagstr.split(".");  // split Tagstr into int + dec parts
      Tagstr = "" + parseInt(numparts[0]);
      numunits = splitIntoThrees(Tagstr);     // split into threes
      Tagstr = "";
      for (var i=numunits.length-1; i>=0; i--)  // then insert commas
        Tagstr += numunits[i] + ((i > 0) ? "," : "");
      if (numparts.length > 1)
        Tagstr += "." + numparts[1];
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    /* date cases *************************************** */

    case "m":
      Tagstr = "" + (num.getMonth() + 1);
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0m":
      if (num.getMonth() + 1 < 10)
        Tagstr = "0" + (num.getMonth() + 1);
      else
        Tagstr = "" + (num.getMonth() + 1);
      break

    case "mon":
      Tagstr = Months[num.getMonth()].substring(0,3).toLowerCase();
      break;

    case "Mon":
      Tagstr = Months[num.getMonth()].substring(0,3);
      break;

    case "month":
      Tagstr = Months[num.getMonth()].toLowerCase();
      break;

    case "Month":
      Tagstr = Months[num.getMonth()];
      break;

    case "d":
      Tagstr = "" + num.getDate();
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0d":
      if (num.getDate() < 10)
        Tagstr = "0" + num.getDate();
      else
        Tagstr = "" + num.getDate();
      break

    case "dy":
      Tagstr = Days[num.getDay()].substring(0,3).toLowerCase();
      break;

    case "Dy":
      Tagstr = Days[num.getDay()].substring(0,3);
      break;

    case "day":
      Tagstr = Days[num.getDay()].toLowerCase();
      break;

    case "Day":
      Tagstr = Days[num.getDay()];
      break;

    case "y":
      Tagstr = ("" + getyyyyYear(num)).substring(2,4);
      if (Tagstr.charAt(0) == "0")
        Tagstr = Tagstr.substring(1,2)
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0y":
      Tagstr = ("" + getyyyyYear(num)).substring(2,4);
      break;

    case "yyyy":
      Tagstr = "" + getyyyyYear(num);
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    // date shortcut tags

    case "date": 
    case "date_slash": case "date_dash": case "date_dot": 
    case "date_euro": 
    case "date_euro_slash": case "date_euro_dash": case "date_euro_dot": 
      var separator = "/";
      if (tag.indexOf("dash") != -1)  // get separator: / or - or . 
        separator = "-";
      else if (tag.indexOf("dot") != -1 || tag == "date_euro")
        separator = ".";
      if (tag.indexOf("euro") != -1)  // euro date - dd/mm/yy
        Tagstr = num.getDate() + separator + 
                 (num.getMonth() + 1) + separator + 
                 ("" + getyyyyYear(num)).substring(2,4);
      else  // normal date - mm/dd/yy
        Tagstr = (num.getMonth() + 1) + separator + 
                 num.getDate() + separator + 
                 ("" + getyyyyYear(num)).substring(2,4);
      break;

    case "date_long": 
    case "date_long_euro": 
      if (tag.indexOf("euro") != -1)
        Tagstr = num.getDate() + ". " + 
                 Months[num.getMonth()] + ", " + getyyyyYear(num);
      else
        Tagstr = Months[num.getMonth()] + " " + 
                 num.getDate() + ", " + getyyyyYear(num);
      break;

    case "date_longer": 
    case "date_longer_euro": 
      var dayordstr = "" + num.getDate();
      dayordstr = get_ordnum(dayordstr, "ordnum");
      if (tag.indexOf("euro") != -1)
        Tagstr = dayordstr + " " + 
                 Months[num.getMonth()] + ", " + getyyyyYear(num);
      else
        Tagstr = Months[num.getMonth()] + " " + 
                 dayordstr + ", " + getyyyyYear(num);
      break;

    case "date_longer_text":  // = ^Month^ ^dOrdtxt^, ^yyyyCard^ 
    case "date_longest_text":  // = ^Day^, ^Month^ ^dOrdtxt^, ^yyyyCard^
      var dstr = "" + num.getDate();
      dstr = get_ordtxt(dstr, "Ordtxt");
      var yyyycardstr = "" + getyyyyYear(num);
      yyyycardstr = get_card(yyyycardstr, "Card");
      if (tag.indexOf("longest") != -1)
        Tagstr = Days[num.getDay()] + ", " + 
                 Months[num.getMonth()] + " " + dstr + ", " + yyyycardstr;
      else
        Tagstr = Months[num.getMonth()] + " " + dstr + ", " + yyyycardstr;
      break;      

    // time cases

    case "h": case "h12":
      Tagstr = num.getHours() % 12;
      if (Tagstr == 0)
        Tagstr = "" + 12;
      else
        Tagstr = "" + Tagstr;
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0h": case "0h12":
      Tagstr = num.getHours() % 12;
      if (Tagstr == 0)
        Tagstr = "" + 12;
      else if (Tagstr < 10)
        Tagstr = "0" + Tagstr;
      else
        Tagstr = "" + Tagstr;
      break;

    case "h24":
      Tagstr = num.getHours();
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0h24":
      Tagstr = num.getHours();
      if (Tagstr < 10)
        Tagstr = "0" + Tagstr;
      else
        Tagstr = "" + Tagstr;
      break;

    case "mi":
      Tagstr = "" + Tagstr;      Tagstr = num.getMinutes();
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0mi":
      Tagstr = num.getMinutes();
      if (Tagstr < 10)
        Tagstr = "0" + Tagstr;
      else
        Tagstr = "" + Tagstr;
      break;

    case "s":
      Tagstr = num.getSeconds();
      ordcardize(ordval, cardval);  // ord/card num, if specified in tag
      break;

    case "0s":
      Tagstr = num.getSeconds();
      if (Tagstr < 10)
        Tagstr = "0" + Tagstr;
      else
        Tagstr = "" + Tagstr;
      break;

    case "am/pm": case "AM/PM":
    case "a.m./p.m.": case "A.M./P.M.":
      var strarray = new Array();
      if (num.getHours() < 12) {
        strarray = tag.split("/");
        Tagstr = strarray[0];
      }
      else {
        strarray = tag.split("/");
        Tagstr = strarray[1];
      }
      break;

    // time shortcut tags

    case "time":  // = ^h^:^0mi^
    case "time_12":  // = ^h12^:^0mi^
    case "time_24":  // = ^h24^:^0mi^
    case "time_ampm":  // = ^h^:^0mi^ ^a.m./p.m.^
    case "time_long":  // = ^h^:^0mi^:^0s^
    case "time_long_24":  // = ^h24^:^0mi^:^0s^
    case "time_long_ampm":  // = ^h^:^0mi^:^0s^ ^a.m./p.m.^ 
      var hstr, mstr, sstr, ampmstr;
      if (tag.indexOf("24") != -1) {  // get hstr
        hstr = num.getHours();
      }
      else {
        hstr = num.getHours() % 12;
        if (hstr == 0)
          hstr = "" + 12;
        else if (hstr < 10)
          hstr = "0" + hstr;
        else
          hstr = "" + hstr;
      }
      mstr = num.getMinutes();  // get mstr
      if (mstr < 10)
        mstr = "0" + mstr;
      else
        mstr = "" + mstr;
      sstr = num.getSeconds();  // get sstr
      if (sstr < 10)
        sstr = "0" + sstr;
      else
        sstr = "" + sstr;
    Tagstr = hstr + ":" + mstr;
    if (tag.indexOf("long") != -1)
      Tagstr +=  ":" + sstr;
    if (tag.indexOf("ampm") != -1) {
      if (num.getHours() < 12)
        Tagstr += " a.m."; 
      else
        Tagstr += " p.m."; 
    }   

  }  
  return index;
}


/* get_ordtxt() ***************************************** */

function get_ordtxt(str, tag) {  // rets correct case of ordinalized text str
  if (typeof str == "string" && str.indexOf(",") != -1)
    str = stripcommas(str);
  if (tag.indexOf("ordtxt") != -1)
    str = ordinalizeToTxt(str, "lower")
  else if (tag.indexOf("ORDTXT") != -1)
    str = ordinalizeToTxt(str, "UPPER")
  else if (tag.indexOf("Ordtxt") != -1)
    str = ordinalizeToTxt(str, "Mixed")
  return str;
}


/* get_ordnum() ***************************************** */

function get_ordnum(str, tag) {  // rets correct case of ordinalized num str
  if (typeof str == "string" && str.indexOf(",") != -1)
    str = stripcommas(str);
  if (tag.indexOf("ordnum") != -1)
    str = ordinalizeToNum(str, "lower")
  else if (tag.indexOf("ORDNUM") != -1)
    str = ordinalizeToNum(str, "UPPER")
  return str;
}


/* get_card() ******************************************* */

function get_card(str, tag) {  // rets correct case of cardinalized text str
  if (typeof str == "string" && str.indexOf(",") != -1)
    str = stripcommas(str);
  if (tag.indexOf("card") != -1)
    str = cardinalize(str, "lower")
  else if (tag.indexOf("CARD") != -1)
    str = cardinalize(str, "UPPER")
  else if (tag.indexOf("Card") != -1)
    str = cardinalize(str, "Mixed")
  return str;
}


/* ordcardize() ***************************************** */

function ordcardize(ordval, cardval) {    // ord/card num, if specified in tag
  if (ordval != "") {  // ordinal text (first) 
    if (ordval.toLowerCase().indexOf("txt") != -1 || 
        ordval.toLowerCase() == "ord")
      Tagstr = get_ordtxt(Tagstr, ordval);
    else if (ordval.toLowerCase().indexOf("num") != -1)
      Tagstr = get_ordnum(Tagstr, ordval);
  }
  else if (cardval != "") {  // cardinal text (one)
    Tagstr = get_card(Tagstr, cardval);
  }
}


/* roundInt() ******************************************* */

function roundInt(num, roundval) {  // rounds up/down to integer
  if (roundval == "")
    return num;
  if (roundval == "round")
    return Math.round(num);
  if (roundval == "rndup")
    return Math.ceil(num);
  if (roundval == "rnddn")
    return Math.floor(num);
}


/* roundInt() ******************************************* */

function roundFloat(num, decplaces, roundval) {  // rounds up/down to float
  var multiplier = 1;
  for (var i=0; i<decplaces; i++)
    multiplier *= 10;
  if (roundval == "round")
    num = Math.round(num * multiplier);
  if (roundval == "rndup")
    num = Math.ceil(num * multiplier);
  if (roundval == "rnddn")
    num = Math.floor(num * multiplier);
  if (roundval != "")
    num = num / multiplier;

  var numstr = "" + num;
  var numstrparts = numstr.split(".");
  if (numstrparts.length < 2)
    numstrparts[1] = "";

  for (var i=0; i<decplaces; i++)
    if (numstrparts[1].length <= i)
      numstrparts[1] += "0";
  numstrparts[1] = numstrparts[1].substring(0, decplaces);
  return (numstrparts[0] + "." + numstrparts[1]);
} 


/* ordinalizeToNum() ************************************ */

function ordinalizeToNum(num, strcase) {  // return "1st", "2ND", etc.
  var val = Math.abs(num) % 100;  // get last two (or one) digit(s) of num
  var retstr = "";
  num = "" + num;  // convert num to a string
  if (val <= 20 && val >= 10)
    retstr = num + "th";
  else if (num.charAt(num.length-1) == "1")
    retstr = num + "st";
  else if (num.charAt(num.length-1) == "2")
    retstr = num + "nd";
  else if (num.charAt(num.length-1) == "3")
    retstr = num + "rd";
  else
    retstr = num + "th";

  if (arguments.length < 2 || strcase == "lower")
    return retstr.toLowerCase();
  else if (strcase == "UPPER")
    return retstr.toUpperCase();
  else if (strcase == "Mixed")
    return capitalizeStr(retstr);
}


/* ordinalizeToTxt() ************************************ */

function ordinalizeToTxt(num, strcase) {  // return "first", "Second", "THIRD", etc.
  var numstr = "";
  var val = Math.abs(num);
  numstr = cardinalize(val);
  var numstrarray = numstr.split(" ");
  var lastnumstr = numstrarray[numstrarray.length-1];
  var lastnumstrarray = lastnumstr.split("-");
  lastnumstr = lastnumstrarray[lastnumstrarray.length-1];
  switch (lastnumstr) { 
    case "zero":
      lastnumstr = "zeroeth";  // ppl: programmer's poetic license?
      break;
    case "one":
      lastnumstr = "first";
      break;
    case "two":
      lastnumstr = "second";
      break;
    case "three":
      lastnumstr = "third";
      break;
    case "five":
      lastnumstr = "fifth";
      break;
    case "eight":
      lastnumstr = "eighth";
      break;
    case "nine":
      lastnumstr = "ninth";
      break;
    case "twelve":
      lastnumstr = "twelfth";
      break;
    case "twenty":
      lastnumstr = "twentieth";
      break;
    case "thirty":
      lastnumstr = "thirtieth";
      break;
    case "forty":
      lastnumstr = "fortieth";
      break;
    case "fifty":
      lastnumstr = "fiftieth";
      break;
    case "sixty":
      lastnumstr = "sixtieth";
      break;
    case "seventy":
      lastnumstr = "seventieth";
      break;
    case "eighty":
      lastnumstr = "eightieth";
      break;
    case "ninety":
      lastnumstr = "ninetieth";
      break;
    default:
      lastnumstr = lastnumstr + "th";
      break;
  }
  var retstr = "";
  for (var i=0; i<numstrarray.length-1; i++)
    retstr += numstrarray[i] + " ";
  for (var i=0; i<lastnumstrarray.length-1; i++)
    retstr += lastnumstrarray[i] + "-";

  if (arguments.length < 2 || strcase == "lower") {
    if (num < 0)
      return ("minus " + retstr + lastnumstr).toLowerCase();
    else
      return (retstr + lastnumstr).toLowerCase();
  }
  else if (strcase == "UPPER") {
    if (num < 0)
      return ("minus " + retstr + lastnumstr).toUpperCase();
    else
      return (retstr + lastnumstr).toUpperCase();
  }
  else if (strcase == "Mixed") {
    if (num < 0)
      return capitalizeStr("minus " + retstr + lastnumstr);
    else
      return capitalizeStr(retstr + lastnumstr);
  }
}


/* cardinalize() **************************************** */

function cardinalize(num, strcase) {  // return "one", "two", "three point four", etc.
  var retstr = "";
  var numstr = "" + num;  // convert num to string
  if (numstr.charAt(0) == "-")
    numstr = numstr.substring(1,numstr.length);
  var numparts = numstr.split(".");
  var numunits = splitIntoThrees(numparts[0]);  // split into threes
  // cardinalize numunits[] elements
  for (var i=0; i<numunits.length; i++) { 
    if (numunits[i].length < 2) {
      retstr = cardnums[convertStrToInt(numunits[i])] +
               placevalues[i] + retstr;
    }
    else if (numunits[i].length < 3) {
      retstr = ((cardnums[convertStrToInt(numunits[i])] != "zero") 
               ? cardnums[convertStrToInt(numunits[i])] 
               : "") +
               placevalues[i] + retstr;
    }
    else { 
      retstr = ((cardnums[convertStrToInt(numunits[i].substring(0,1))] != "zero") 
               ? (cardnums[convertStrToInt(numunits[i].substring(0,1))] + " hundred ") 
               : "") + 
               ((cardnums[convertStrToInt(numunits[i].substring(1,3))] != "zero") 
               ? cardnums[convertStrToInt(numunits[i].substring(1,3))] 
               : "") + 
               placevalues[i] + retstr;
    }
  }
  if (num < 0)
    retstr = "minus " + retstr;
  if (numparts.length == 2) {  // there's a decimal portion to num
    retstr += " point";
    for (var i=0; i<numparts[1].length; i++)
      retstr += " " + cardnums[numparts[1].charAt(i)];
  }
  if (arguments.length < 2 || strcase == "lower")
    retstr = retstr.toLowerCase();
  else if (strcase == "UPPER")
    retstr = retstr.toUpperCase();
  else if (strcase == "Mixed")
    retstr = capitalizeStr(retstr);

  for (var i=retstr.length-1; i>=0; i--)
    if (retstr.charAt(i) != " ")
      break;
  retstr = retstr.substring(0,i+1);
  return retstr;
}


/* getyyyyYear(date) ************************************ */

function getyyyyYear(date) { // get year in yyyy format
  var yyyyYear = date.getYear();
  if (yyyyYear < 1000)
    yyyyYear += 1900;
  return yyyyYear;
}


/* splitIntoThrees() ************************************ */

function splitIntoThrees(str) {  // split str into array of three-char elements
  var retarray = new Array();
  for (var i=str.length-1,j=1,k=0; i>=0; i--,j++) { 
   retarray[k] = str.charAt(i) + ((retarray[k] != null) ? retarray[k] : "");
   if (j % 3 == 0)
     k++;
  }
  return retarray;
}


/* capitalizeStr() ************************************** */

function capitalizeStr(str) {  // ret str with capitalized first letter of each word
  // i really gotta learn how to use regular expressions ... 
  var capitalizeNextLetter = true;
  var retstr = "";

  for (var i=0; i<str.length; i++) {
    if (capitalizeNextLetter && (str.charAt(i) != " ")) {
      retstr += str.substring(i,i+1).toUpperCase();
      capitalizeNextLetter = false;
    }
    else if (str.charAt(i) == " " || str.charAt(i) == "-") {
      retstr += str.substring(i,i+1);
      capitalizeNextLetter = true;
    }
    else {
      retstr += str.substring(i,i+1);
    }
  }
  return retstr;
}


/* convertStrToInt() *************************************** */
// - because parseInt() doesn't work with 0-padded strs!
//   e.g., parseInt("09") evaluates to 0, not to 9

function convertStrToInt(str) {
  for (var i=0; i<str.length; i++) 
    if (str.charAt(i) != "0")
      break;
  if (i == str.length)
    return 0;
  else
    return parseInt(str.substring(i, str.length));
}


/* stripcommas() **************************************** */

function stripcommas(str) {  // returns str without commas (,)
  var retstr = "";
  for (var i=0; i<str.length; i++) {
    if (str.charAt(i) != ",")
      retstr += str.substring(i, i+1);
  }
  return retstr;
}


/* delsubstr() **************************************** */

function delsubstr(substr, str) {  // returns str *minus* substr
  var index = str.indexOf(substr);
  return (str.substring(0, index) + 
          str.substring(index+substr.length, str.length));
}

