/*
* This file is part of HOAM, copyright (C) 2002-2009 Robert D Butler Jr.
*
* HOAM is free software; you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* HOAM is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with HOAM; if not, see http://www.gnu.org/licenses or write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301
*
* Questions specific to HOAM should be directed to HOA Management. Please see
* the HOAM web site at http://hoam.hoa-management.com/
*
* Some portions of HOAM incorporate ideas and/or code from other sources, and
* those portions are explicitly mentioned and attributed in the relevant
* section of HOAM source code. Questions about that code should be directed to
* the original authors.
*
*/
/* global variables */
var AjaxGlobalHandlers = {
onCreate: HOAM_serverCommunicationBegin,
onException: HOAM_serverCommunicationError,
onLoading: HOAM_serverCommunicationLoading,
onComplete: HOAM_serverCommunicationEnd
};
Ajax.Responders.register (AjaxGlobalHandlers);
var ZebraTable = {
// Stolen from http://www.thewatchmakerproject.com/zebra.html
// A few modifications, though...
class_alternate: 'alternate-row',
class_hover: '',
stripe: function (el) {
if (!$(el)) return;
var rows = $(el).getElementsByTagName('tr');
for (var i = 0, len = rows.length; i < len; i++) {
// Added the .extend for f'ing IE
Element.extend (rows[i]);
if (i % 2 == 0) rows[i].addClassName (this.class_alternate);
Event.observe (rows[i], 'mouseover', function() { ZebraTable.mouseover(this); }, false);
Event.observe (rows[i], 'mouseout', function() { ZebraTable.mouseout(this); }, false);
}
},
mouseover: function (row) {
row.addClassName (this.class_hover);
},
mouseout: function (row) {
row.removeClassName (this.class_hover);
}
}
function HOAM_sentenceFormat (string) {
// Should be made smarter, just needed something quick.
string = string.toLowerCase();
string = string.substring(0,1).toUpperCase() + string.substring (1, string.length);
return (string);
}
function HOAM_countryLookup (messagePath) {
var languageString;
var url = '/scripts/help/countryLookup.php';
// var pars = 'string=' + messagePath.replace (/_/g, '/');
pars = 'string=' + messagePath;
// Note, this call is being done synchronously, which could potentially
// cause problems. However, it's done this way because the function was
// returning before the request completed.
var myAjax = new Ajax.Request (url, { method: 'get', asynchronous: false, parameters: pars, onComplete: function (originalRequest) {
languageString = originalRequest.responseText;
}
});
return (languageString);
}
function HOAM_verifyDate (event) {
if ((Event.element(event).value.length == 0)) {
HOAM_formErrorClear (Event.element(event).getAttribute ('name'));
} else {
if ((Event.element(event).value.length > 0) && (Event.element(event).value.length < Event.element(event).getAttribute ('minlength'))) {
HOAM_formErrorMessage (Event.element(event).getAttribute ('name'), HOAM_languageLookup ('errors|generic|invalid-date', Event.element(event).getAttribute ('minlength')));
} else {
// I don't know why, but the string read in through the config lookup isn't
// matching correctly, even though it's the same regex.
// if (Event.element(event).value.match (HOAM_countryLookup ('date|match'))) {
if (Event.element(event).value.match (/^([01]\d)[\-\.\/]([0-3]\d)[\-\.\/](19\d{2}|20\d{2})$/)) {
HOAM_formErrorClear (Event.element(event).getAttribute ('name'));
Event.element(event).value = Event.element(event).value.replace (/^([01]\d)[\-\.\/]([0-3]\d)[\-\.\/](19\d{2}|20\d{2})$/, '$1/$2/$3');
} else {
HOAM_formErrorMessage (Event.element(event).getAttribute ('name'), HOAM_languageLookup ('errors|generic|invalid-date'));
}
}
}
}
function HOAM_verifyPostalcode (event) {
if (Event.element(event).value.length < Event.element(event).getAttribute ('minlength')) {
HOAM_formErrorMessage (Event.element(event).getAttribute('name'), HOAM_languageLookup ('errors|generic|postalcode-too-short', Event.element(event).getAttribute ('minlength')));
} else {
// if (Event.element(event).value.match (HOAM_countryLookup ('postalcode|match-js'))) {
if (Event.element(event).value.match (/^(\d{5})$|^(\d{5})\-?(\d{4})$/)) {
HOAM_formErrorClear (Event.element(event).getAttribute ('name'));
// Would like to do some auto formatting, this isn't working
// currently
// Event.element(event).value = Event.element(event).value.replace (/^(\d{5})$|^(\d{5})\-?(\d{4})$/, '$1-$2');
} else {
HOAM_formErrorMessage (Event.element(event).getAttribute ('name'), HOAM_languageLookup ('errors|generic|invalid-postalcode'));
}
}
}
function HOAM_verifyTelephone (event) {
if ((Event.element(event).value.length == 0)) {
HOAM_formErrorClear (Event.element(event).getAttribute ('name'));
} else {
if ((Event.element(event).value.length > 0) && (Event.element(event).value.length < Event.element(event).getAttribute ('minlength'))) {
HOAM_formErrorMessage (Event.element(event).getAttribute ('name'), HOAM_languageLookup ('errors|generic|telephone-too-short', Event.element(event).getAttribute ('minlength')));
} else {
// I don't know why, but the string read in through the config lookup isn't
// matching correctly, even though it's the same regex.
// if (Event.element(event).value.match (HOAM_countryLookup ('telephone|match'))) {
if (Event.element(event).value.match (/^\(?([2-9]\d{2})[\)\-]?\s?(\d{3})\-?(\d{4})$/)) {
HOAM_formErrorClear (Event.element(event).getAttribute ('name'));
Event.element(event).value = Event.element(event).value.replace (/^\(?([2-9]\d{2})[\)\-]?\s?(\d{3})\-?(\d{4})$/, '($1) $2-$3');
} else {
HOAM_formErrorMessage (Event.element(event).getAttribute ('name'), HOAM_languageLookup ('errors|generic|invalid-telephone'));
}
}
}
}
function HOAM_languageLookup (messagePath, param1, param2) {
var languageString;
var url = '/scripts/help/languageLookup.php';
// var pars = 'string=' + messagePath.replace (/_/g, '/');
pars = 'string=' + messagePath;
pars = pars + '¶m1=' + param1 + '¶m2=' + param2;
// Note, this call is being done synchronously, which could potentially
// cause problems. However, it's done this way because the function was
// returning before the request completed.
var myAjax = new Ajax.Request (url, { method: 'get', asynchronous: false, parameters: pars, onComplete: function (originalRequest) {
languageString = originalRequest.responseText;
}
});
return (languageString);
}
function HOAM_languageLookupAsync (messagePath, param1, param2) {
var languageString;
var url = '/scripts/help/languageLookup.php';
// var pars = 'string=' + messagePath.replace (/_/g, '/');
pars = 'string=' + messagePath;
pars = pars + '¶m1=' + param1 + '¶m2=' + param2;
var myAjax = new Ajax.Request (url, { method: 'get', asynchronous: true, parameters: pars, onComplete: function (originalRequest) {
alert (originalRequest.responseText);
languageString = originalRequest.responseText;
}
});
return (languageString);
}
function HOAM_returnNumberCurrencyFormat (amount, show_sign) {
// From (with modifications) http://javascript.internet.com/forms/currency-format.html
// If we didn't receive anything, then just exit; we want a blank response.
amount = amount.toString().replace('/\$|\,/g', '');
if (isNaN (amount)) {
amount = "0";
}
sign = (amount == (amount = Math.abs(amount)));
amount = Math.floor(amount * 100 + 0.50000000001);
cents = amount % 100;
amount = Math.floor(amount / 100).toString();
if (cents < 10) {
cents = "0" + cents;
}
for (var i = 0; i < Math.floor((amount.length - (1 + i)) / 3); i++) {
amount = amount.substring(0, amount.length - (4 * i + 3)) + ',' + amount.substring(amount.length - (4 * i + 3));
}
if (show_sign) {
return (((sign) ? '' : '-') + amount + '.' + cents);
} else {
return (amount + '.' + cents);
}
}
function HOAM_returnCleanFloat (obj) {
return parseFloat ((obj).replace (/,/, ''))
}
function HOAM_serverCommunicationBegin () {
// Display notice to end-user that communciation is occuring with the server
// ie, activity is taking place
var working = document.createElement ('DIV');
if ($('logo')) {
var header = $('logo');
} else {
var header = $('page_logo');
}
var first = header.firstChild;
working.id = 'server-communication';
working.className = 'server-communication';
working.innerHTML = ' Connecting ';
header.insertBefore (working, first);
}
function HOAM_serverCommunicationLoading () {
// Update the notice to show communication is occuring
var working = $('server-communication');
working.innerHTML = ' Loading ';
}
function HOAM_serverCommunicationError () {
// Update the notice to show communication is occuring
var working = $('server-communication');
working.innerHTML = ' Error ';
}
function HOAM_serverCommunicationEnd () {
// Remove notice to end-user tha communication is occuring with the server
// ie, all activity has ceased
var working = $('server-communication');
if ($('logo')) {
var header = $('logo');
} else {
var header = $('page_logo');
}
header.removeChild (working);
}
function HOAM_tabLoading (container) {
if (container) {
var old_contents = container.descendants ();
for (var i = 0; i < old_contents.length; i++) {
Element.remove (old_contents[i]);
}
var div = document.createElement ('DIV');
div.className = 'center';
var img = document.createElement ('IMG');
img.setAttribute ('src', '/images/icons/throbber.gif');
img.setAttribute ('alt', 'Progress animation');
var message = document.createElement ('DIV');
message.className = 'center';
message.innerHTML = 'Loading …';
div.appendChild (img);
div.appendChild (message);
container.appendChild (div);
}
}
function HOAM_textareaDecreaseRows (event, divisor) {
// Decreases the total number of rows in a textarea by multiplier
// Switch the button to increaseRows, this provides a toggle effect.
Event.stopObserving (Event.element(event), 'click', HOAM_textareaDecreaseRows);
Event.observe (Event.element(event), 'click', HOAM_textareaIncreaseRows);
// Find the textarea. This will break if the textarea item is not
// the next node after the expander
var obj = Event.element(event).nextSibling;
if (!divisor) divisor = 2;
var rows = obj.getAttribute ("rows");
obj.setAttribute ("rows", rows / divisor);
}
function HOAM_textareaIncreaseRows (event, multiplier) {
// Increases the total number of rows in a textarea by multiplier
// Switch the button to decreaseRows, this provides a toggle effect.
Event.stopObserving (Event.element(event), 'click', HOAM_textareaIncreaseRows);
Event.observe (Event.element(event), 'click', HOAM_textareaDecreaseRows);
// Find the textarea. This will break if the textarea item is not
// the next node after the expander
var obj = Event.element(event).nextSibling;
if (!multiplier) multiplier = 2;
var rows = obj.getAttribute ("rows");
if (!rows) rows = 3;
obj.setAttribute ("rows", rows * multiplier);
}
function HOAM_textareaExpander () {
// Locate any textareas on the page, and allow them to be expanded / contracted
textarea = document.getElementsByTagName ("TEXTAREA");
for (var i = 0; i < textarea.length; i++) {
var parent = textarea[i].parentNode;
var toggleswitch = document.createElement ("SPAN");
toggleswitch.className = "textareaExpander";
toggleswitch.innerHTML = HOAM_languageLookup ('field_names|textarea-expander');
Event.observe (toggleswitch, 'click', HOAM_textareaIncreaseRows);
toggleswitch.title = HOAM_languageLookup ('titles|textarea-expander');
parent.insertBefore (toggleswitch, textarea[i]);
}
}
function HOAM_tableStripe () {
// Locate any tables on the page and stripe alternate rows
// NOTE! Only touches tbody's, not tables themselves. This may change in
// the future if I have time.
// NOTE! If you do _NOT_ want a table striped, add the class 'nostripe' to
// the tbody.
tables = document.getElementsByTagName ('TBODY');
for (var i = 0; i < tables.length; i++) {
// Added the .extend for f'ing IE
Element.extend (tables[i]);
if (!tables[i].hasClassName ('nostripe')) {
ZebraTable.stripe (tables[i]);
}
}
}
function HOAM_userLoginCheck (event) {
if (($F('user_loginName') == '') || ($F('user_loginName') == 'user name')) {
alert (HOAM_languageLookup ('errors|user|accountname|required'));
$('user_loginName').focus();
Event.stop (event);
}
if (!$F('user_loginPassword')) {
alert (HOAM_languageLookup ('errors|user|password|required'));
$('user_loginPassword').focus();
Event.stop (event);
}
}
function getElementTextNS (prefix, local, parentElem, index) {
// Based on Apple's example at http://developer.apple.com/internet/webcontent/xmlhttpreq.html
// retrieve text of an XML document element, including elements using namespaces
var result = "";
if (prefix) {
// IE/Windows way of handling namespaces
result = parentElem.getElementsByTagName (prefix + ":" + local)[index];
} else {
// the namespace versions of this method
// (getElementsByTagNameNS()) operate
// differently in Safari and Mozilla, but both
// return value with just local name, provided
// there aren't conflicts with non-namespace element
// names
result = parentElem.getElementsByTagName(local)[index];
}
if (result) {
// get text, accounting for possible
// whitespace (carriage return) text nodes
if (result.childNodes.length > 1) {
return result.childNodes[1].nodeValue;
} else {
if (result.childNodes.length == 1) {
return result.firstChild.nodeValue;
} else {
return result.nodeValue;
}
}
} else {
return "n/a";
}
}
function leadingZero (number) {
// Shamelessly stolen from http://www.quirksmode.org/js/date.html
if (number < 10) {
number = "0" + number;
}
return number;
}
function binarySearch (array, find, caseInsensitive, getSubstring, arrayCheckThisIndex) {
if (!array || typeof (array) != "object" || typeof (find) == "undefined" || !array.length) {
return null;
}
find = (!caseInsensitive) ? find : find.toLowerCase();
var low = 0;
var high = array.length - 1;
var highOnTop = (array[0] > array[array.length - 1]) ? 1 : 0;
while (low <= high) {
var aTry = parseInt((low + high) / 2);
var checkThis = (typeof (arrayCheckThisIndex) == "undefined") ? array[aTry] : array[aTry][arrayCheckThisIndex];
checkThis = (!caseInsensitive) ? checkThis : checkThis.toLowerCase();
checkThis = (!getSubstring) ? checkThis : checkThis.substring(0, find.length);
if (!highOnTop) {
if (checkThis < find) {
low = aTry + 1;
continue;
}
if (checkThis > find) {
high = aTry - 1;
continue;
}
} else {
if (checkThis > find) {
low = aTry + 1;
continue;
}
if (checkThis < find) {
high = aTry - 1;
continue;
}
}
return aTry;
}
return null;
}
function HOAM_moveOption (form, source, dest) {
// Believe this was originally Matt Kruse's
/* Moves selected OPTION elements from one SELECT control to another.
*
* form - A reference to the containing form,
* or a string containing the name or id.
* source - The name or id of the source SELECT element.
* dest - The name or id of the destination SELECT element.
*/
if (typeof form == 'string') {
form = $(form);
}
if (form && 'object' == typeof form && this['Option']) {
dest = $(dest);
source = $(source);
if (dest && source && 'object' == typeof dest && 'object' == typeof source) {
var c, i = 0, j = 0, dO = dest.options, sO = source.options, t;
while (i < sO.length) {
c = sO[i++]; t = new Option (c.text, c.value);
if (c.selected) {
dO[dO.length] = t;
} else {
sO[j++] = t;
}
}
sO.length = j;
}
}
}
function appendToSelectList (select, value, content) {
// add item to select element the less elegant, but compatible way.
var option = document.createElement ("option");
option.value = value;
option.appendChild (content);
select.appendChild (option);
}
function clearSelectList (id) {
// empty select list content
var select = document.getElementById (id);
while (select.length > 0) {
select.remove (0);
}
}
function getSelectIndex (id, value) {
// find out the select option index of the value we have
var select = document.getElementById (id);
// Make sure we have something to do...
if (select.length > 0) {
var i = 0;
while (i < select.length) {
if (select.childNodes[i].value == value) {
return (i);
}
i++
}
// fail out and return the default value
return -1;
}
}
function include (filename) {
// From http://forums.digitalpoint.com/showthread.php?t=146094
//
// Be aware of the limitation that this function will place the included
// file at the end of the hard-coded head. This means that any code
// dependent on the incuded file must be listed after the file
// just included (typically meaning it must be included also).
var head = document.getElementsByTagName ('head')[0];
script = document.createElement ('script');
script.src = filename;
script.type = 'text/javascript';
head.appendChild (script)
}
function moreFields (source_elem, dest_elem) {
// Stolen from the excellent http://www.quirksmode.org/ site
// Modified, of course
if (!source_elem) source_elem = $('readroot');
if (!dest_elem) dest_elem = $('writeroot');
// We're using a non-standard attribute to store a temporary counter of how many 'new' fields we've added
var counter = parseInt (source_elem.parentNode.getAttribute ('ref')) + 1;
if (isNaN (counter)) {
counter = 1;
}
var newFields = source_elem.cloneNode (true);
newFields.id = '';
newFields.style.display = 'block';
var newField = newFields.childNodes;
for (var i = 0; i < newField.length; i++) {
var theName = newField[i].name
if (theName)
newField[i].name = theName + counter;
}
// Save the counter.
source_elem.parentNode.setAttribute ('ref', counter);
dest_elem.parentNode.insertBefore (newFields, dest_elem);
}
function removeAllChildren (id) {
i = 0;
var object = document.getElementById (id);
while (i < object.childNodes.length) {
object.removeChild (object.childNodes[0]);
}
}
function removeLastChild (id) {
// Remove the last child (if present)
var object = document.getElementById (id).lastChild;
if (object) {
document.getElementById (id).removeChild (object);
}
}
function replaceTextNode (id, text) {
var element = document.getElementById (id);
element.value = text;
}
function setSelectIndex (id, value) {
// find out the select option index of the value we have
var select = document.getElementById (id);
// Make sure we have something to do...
for (var i = 0; i < select.length; i++) {
if (select[i].value == value) {
select.selectedIndex = i;
return i;
}
}
}
function sleep (time) {
// wait (time) millisecs
var then, now;
then = new Date ().getTime ();
now = then;
while ((now - then) < time) {
now = new Date ().getTime ();
}
}
function setTextNode (id, text) {
var node = document.createTextNode (text);
document.getElementById (id).appendChild (node);
}
function ToggleDisplay (id) {
// DEPRECATED. any usage needs to be moved to the prototype equivlent.
var element = document.getElementById (id);
if (element.style.display == 'none') {
if (document.all) {
element.style.display = 'block';
} else{
element.style.display = '';
}
} else {
element.style.display = 'none';
}
}
function setSelect (select, property) {
var found = false;
for (var i = 0; i < select.options.length; i++) {
if (select.options[i][property].toUpperCase().indexOf(field.value.toUpperCase()) == 0) {
found = true;
break;
}
}
if (found) {
select.selectedIndex = i;
} else {
select.selectedIndex = -1;
}
}
function setSelectAll (select) {
var select = document.getElementById (select);
for (var i = 0; i < select.options.length; i++) {
select.options[i].selected = true;
}
}
/**********************************************************************\
* The JavaScript code in this section was written by: *
* Rahul Mittal *
* Sophomore, Dept. of Astronomy, Villanova University *
* Feel free to use the code below provided this header remains intact! *
* Drop me e-mail at rasteroid@hotmail.com if you want, and *
* that way I can check out your homepage... :) *
* Corrected Y2K bug October 3, 2003 (JD 2452915) by Adric Riedel
\**********************************************************************/
function calculateJD(calendarDate) {
cdDate = new Date(calendarDate)
year = cdDate.getYear() //added + 1900 10/03/2003
if (year < 1000 ) {year+=1900} // modified 10/03/2003
//year = 1900+cdDate.getYear()
month = cdDate.getMonth() + 1 //getMonth() returns 0-11
day = cdDate.getDate()
hour = cdDate.getHours()
min = cdDate.getMinutes()
sec = cdDate.getSeconds()
univTime = hour+(min/60)+(sec/3600)
if ((100*year+month-190002.5) >= 0) {sign = 1}
else {sign = -1}
with (Math) {
part1 = 367 * year
part2 = floor((7*(year+floor((month+9)/12)))/4)
part3 = day+floor((275*month)/9)
part4 = 1721013.5+(univTime/24)
part5 = 0.5*sign
jd = part1-part2+part3+part4-part5+0.5
}
return jd
}
function calculateCD(julianDate) {
with (Math) {
X = parseFloat(julianDate)+0.5
Z = floor(X)
F = X - Z
Y = floor((Z-1867216.25)/36524.25)
A = Z+1+Y-floor(Y/4)
B = A+1524
C = floor((B-122.1)/365.25)
D = floor(365.25*C)
G = floor((B-D)/30.6001)
month = (G<13.5) ? (G-1) : (G-13)
year = (month<2.5) ? (C-4715) : (C-4716)
month -= 1 // month in JavaScript is from 0 to 11
UT = B-D-floor(30.6001*G)+F
day = floor(UT)
UT -= floor(UT)
UT *= 24;
hour = floor(UT)
UT -= floor(UT)
UT *= 60
minute = floor(UT)
UT -= floor(UT)
UT *= 60
second = round(UT)
}
cdDate = new Date(Date.UTC(year, month, day, hour, minute, second))
// return cdDate.toGMTString()
return cdDate.toLocaleDateString();
}
/*************** End Julian Date Code by Rahul Mittal *****************/
/**********************************************************************
* Word highlighting code. Based on http://www.kryogenix.org/code/browser/searchhi/
* with several modifications (primarily to make it easier to understand.)
HOWEVER, it's /horribly/ inefficient (checks every single word), and
needs to be rewritten from scratch.
**********************************************************************/
function highlightWord (node, word) {
// Iterate into this nodes childNodes
if (node.hasChildNodes) {
var hi_cn;
for (hi_cn = 0; hi_cn < node.childNodes.length; hi_cn++) {
highlightWord (node.childNodes[hi_cn], word);
}
}
// And do this node itself
if (node.nodeType == 3) { // text node
tempNodeVal = node.nodeValue.toLowerCase();
tempWordVal = word.toLowerCase();
if (tempNodeVal.indexOf(tempWordVal) != -1) {
pn = node.parentNode;
if (pn.className != "highlight") {
// word has not already been highlighted!
nv = node.nodeValue;
ni = tempNodeVal.indexOf(tempWordVal);
// Create a load of replacement nodes
before = document.createTextNode(nv.substr(0,ni));
docWordVal = nv.substr(ni,word.length);
after = document.createTextNode(nv.substr(ni+word.length));
hiwordtext = document.createTextNode(docWordVal);
hiword = document.createElement("span");
hiword.className = "highlight";
hiword.appendChild(hiwordtext);
pn.insertBefore(before,node);
pn.insertBefore(hiword,node);
pn.insertBefore(after,node);
pn.removeChild(node);
}
}
}
}
function wordHighlight () {
// Does the user's client support the createElement function?
if (!document.createElement)
return;
url = document.URL;
// Does the url contain any Get options?
if (url.indexOf ('?') == -1)
return;
// Find the '?'
query_string = url.substr (url.indexOf ('?') + 1);
// Create an array of all the different options specified
query_string_array = query_string.split ('&');
// Go through the array
for (i = 0; i < query_string_array.length; i++) {
// Separate the query and response
qsip = query_string_array[i].split ('=');
// Does this query match what we're looking for?
if (qsip[0] == 'highlight') {
// words = unescape (qsip[1].replace('/\+/g',' ')).split('/\s+/');
// The original form of this code doesn't seem to work correctly.
// This needs to be enhanced to also handle common search strings,
// such as phrases in quotes.
words = unescape (qsip[1].replace('/\+/g',' ')).split('+');
for (j = 0; j < words.length; j++) {
// highlightWord (document.getElementsByTagName ("body")[0],words[j]);
highlightWord ($('article'), words[j]);
}
}
}
}
/*************** End Word Highlight Code *****************/
/**
* sprintf() for JavaScript v.0.4
*
* Copyright (c) 2007 Alexandru Marasteanu
* Thanks to David Baird (unit test and patch).
*
* This program is free software; 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.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }
function sprintf () {
var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
while (f) {
if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
else if (m = /^\x25{2}/.exec(f)) o.push('%');
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
throw("Expecting number but found " + typeof(a));
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length;
p = m[5] ? str_repeat(c, x) : '';
o.push(m[4] ? a + p : p + a);
}
else throw ("Huh ?!");
f = f.substring(m[0].length);
}
return o.join('');
}