Convert Foreign to Local Currency

By Tesiph Last update Apr 17, 2008 — Installed 1,942 times. Daily Installs: 2, 0, 1, 0, 0, 0, 2, 2, 5, 1, 0, 3, 2, 3, 0, 1, 2, 1, 1, 4, 1, 4, 2, 0, 1, 2, 1, 0, 1, 1, 1, 0
// ==UserScript==
// @name           Convert Foreign to Local Currency
// @namespace      none
// @description    Converts all prices to your local currency
// @include        *
// ==/UserScript==
// ------------------------------------------------------------------------- //

/* This list is taken at semirandom from wikipedia. Feel free to add your own
 * currency.
 * Higher ranked currencies have priority if they happen to have the same symbol
 */

var currencies = {
	'USD': ['$', 'US$'],
	'GBP': ['£'],
	'EUR': ['€'],
	'AUD': ['$', 'A$'],
	'CAD': ['$', 'CDN', 'C$', 'CDN$'],
	'JPY': ['¥'],
	'CHF': ['Fr.'],
	'HKD': ['$', 'HK$'],
	'CNY': ['¥'],
	'INR': ['₨'],
	'IDR': ['Rp'],
	'ILS': ['₪'],
	'DKK': ['kr'],
	'NOK': ['kr', 'NOR'],
	'SEK': ['kr'],
	'THB': ['฿'],
	'TRY': ['₤'],
	'RUB': ['руб.', 'руб'],
	'PLN': ['zł']
}

// These currencies prefer to suffix their symbol
var suffix = ['CHF', 'DKK', 'NOR'];

// --[ Only code remains ... ]---------------------------------------------- //

// Retrieves the currency code for the given symbol
function getCurrency(symbol)
{
	return symbolCurrency[symbol.toUpperCase()];
}

// Retrieves the standard symbol for the given currency code.
// If none is found it defaults to the currency code itself.
function getSymbol(currency)
{
	if (currency in currencies)
		return currencies[currency][0];
	else
		return currency;
}

// Creates a float/int from a string. Also does some magic to detect
// thousand separators and get rid of them.
function parseNumber(input) {
	var point = input.indexOf('.');
	var comma = input.indexOf(',');
	if (point > -1 && comma > -1)
	{
		if (point > comma)
			input = input.replace(/\,/g, "");
		else
			input = input.replace(/\./g, "");
	}
	else if (point > -1 || comma > -1)
	{
		var sep = (point > comma) ? '.' : ',';
		var r = new RegExp("\\"+sep+"[0-9]{3}$");
		if (r.test(input)) {
			var n = input.split(sep);
			if (parseInt(n[0]) != 0)
				input = input.replace(new RegExp("\\"+sep, 'g'), "");
		}
	}
	return parseFloat(input);
}

// Create a special currency element which stores all needed information
// Currentyly it's a abbr element with some non-standard attributes.
function createCurrencyElement(match)
{
	var foreignCurrency = getCurrency(match[1]).join(',');
	var foreignAmount = parseNumber(match[2]);

	var n = document.createElement("abbr");
	n.setAttribute("class", "currency");
	n.setAttribute("currency", foreignCurrency);
	n.setAttribute("value", foreignAmount);
	if (convertInline)
		n.setAttribute("title", "(" + foreignCurrency + " " + formatCurrency(foreignAmount) + ") " + match[0]);
	n.appendChild(document.createTextNode(match[0]));
	updateCurrencyElement(n);
	return n;
}
function createCurrencyElementSuffix(m)
{
	return createCurrencyElement([m[0], m[2], m[1]]);
}

// Will update an existing currency element using the latest exchange rate
function updateCurrencyElement(e)
{
	var foreignCurrencyList = e.getAttribute("currency").split(',');
	var displayString = "";
	for (i in foreignCurrencyList)
	{
		if (i > 0 && convertInline)
			break; 

		var foreignCurrency = foreignCurrencyList[i];
		var foreignAmount = parseFloat(e.getAttribute("value"));
		var localAmount = foreignAmount * getExchangeRate(foreignCurrency);

		// The default precision of a converted number is 2 decimals. 
		// If the original has more, also give the converted number more.
		var decimal = foreignAmount.toString().split(".");
		var precision = 2;
		if (decimal.length > 1 && decimal[1].length > 2) 
			precision = decimal[1].length+1;

		if (isNaN(localAmount) || localAmount == 0) 
			return;

		if (!suffix) 
			formattedString = getSymbol(localCurrency) + "\xA0" + formatCurrency(localAmount, precision);
		else
			formattedString = formatCurrency(localAmount, precision) + "\xA0" + getSymbol(localCurrency);

		if (i > 0)
			displayString += ", ";

		if (convertInline)
			displayString += formattedString;
		else
			displayString += formattedString + " (from " +    foreignCurrency + " " + formatCurrency(foreignAmount) +")";
	}

	if (convertInline)
		e.replaceChild(document.createTextNode(displayString), e.firstChild);
	else
		e.setAttribute("title", displayString);
}

// Return cached exchange rate, else update from the web
function getExchangeRate(foreign)
{
	var r = GM_getValue("rate-" + foreign, false);
	if (r != false)
	{
		var s = r.split("|");
		if (s[1] == localCurrency + todayString)
			return s[0];
	}
	updateExchangeRate(foreign);
	return NaN;
}

// Retrieve the current exchange rate from Yahoo
var requests = new Array();
function updateExchangeRate(foreign)
{
	if (foreign in requests)
		return;
	requests[foreign] = true;
	
	GM_xmlhttpRequest({
		method: "GET",
		url: "http://download.finance.yahoo.com/d/quotes.csv?s=" + foreign + localCurrency + "=X&f=Xl1&e=.csv",
		onload: function(responseDetails) 
		{
			var response = responseDetails.responseText.split(",");
			var foreignCurrency = response[0].substring(1,4);
			GM_setValue('rate-'+foreignCurrency, parseFloat(response[1]) + "|" + localCurrency + todayString);
			// Update all nodes we skipped
			var nodes = document.evaluate(
				"//abbr[contains(@currency,\""+foreignCurrency+"\")]",
			    document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
			for (var i = 0; i < nodes.snapshotLength; i++)
			    updateCurrencyElement(nodes.snapshotItem(i));
		}
	});
}

/* This function will replace all regexp matches with an element.
 * At its current incarnation it's an evil hack.
 */
function replaceWithElement(textnode, regex, generator)
{
	var text = textnode.data;
	if (!textnode.parentNode || /^(SCRIPT|IFRAME|TEXTAREA|STYLE|OPTION|TITLE)$/.test(textnode.parentNode.nodeName))  
		return 
	if (regex.test(text))
	{
		var span = document.createElement("span");
		var m;
		while(m = text.match(regex))
		{
			var pos = text.indexOf(m[0]);
			
			// included another hack because there is no look-behind assertion in javascript
			var offset = m[1].length; 
			m.splice(1, 1);

			span.appendChild(document.createTextNode(text.substring(0, pos+offset)));
			m[0] = m[0].substring(offset);
			span.appendChild(generator(m));
			text = text.substring(pos+m[0].length+offset);
		}
		span.appendChild(document.createTextNode(text));
		textnode.parentNode.replaceChild(span, textnode);
	}
}

// Format number for display
var regexpFixFormat = new RegExp("[,.][0-9]$");
function formatCurrency(num, fixed)
{
	if (fixed)
	{
		var p = Math.pow(10, fixed < 2 ? 2 : fixed);
		num = Math.round(num * p) / p;
	}
	num = num.toLocaleString();
	if (regexpFixFormat.test(num))
		return num + "0";
	else
		return num;
}

// --[ Settings ]----------------------------------------------------------- //

function setLocalCurrency()
{
	currency = prompt("Please enter your 3-letter currency code (e.g. USD, EUR, GBP)\nIf you do not know the proper code, you can look it up at:\n\t http://en.wikipedia.org/wiki/List_of_circulating_currencies", localCurrency);
	localCurrency = currency;
	GM_setValue("local-currency", localCurrency);
}

function setInline()
{
	convertInline = confirm("Do you want to replace the local currency inline? (Not recommended)");
	GM_setValue("inline", convertInline);
}

GM_registerMenuCommand("Change currency inline setting", setInline);
var convertInline = GM_getValue("inline", null);
if (convertInline == null)
	setInline();

GM_registerMenuCommand("Change local currency", setLocalCurrency);
var localCurrency = GM_getValue("local-currency", null);
if (localCurrency == null)
{
	setLocalCurrency();
	alert("You can change your settings later using the Greasemonkey menu");
}
suffix = (suffix.indexOf(localCurrency) != -1);

// ------------------------------------------------------------------------- //

var dateObject = new Date();
var todayString = dateObject.getDay() + "-" + dateObject.getMonth();

// Create a regexp that matches all defined currencies, both by code and symbol(s).
var regexpCurrency = "";
var symbolCurrency = new Array();
for (i in currencies)
{
	if (i != localCurrency)
		regexpCurrency = regexpCurrency + "|" + i;

	if (!(i in symbolCurrency))
		symbolCurrency[i] = new Array(i);
	else
		symbolCurrency.push(i);

	for (j in currencies[i])
	{
		symbol = currencies[i][j];
		if (!(symbol.toUpperCase() in symbolCurrency))
		{
			if (i != localCurrency)
				regexpCurrency = regexpCurrency + "|" + symbol;
			symbolCurrency[symbol.toUpperCase()] = new Array(i);
		} else {
			symbolCurrency[symbol.toUpperCase()].push(i);
		}
	}
}

regexpCurrency = regexpCurrency.substring(1).replace(/\$/g, "\\$");
// I want my positive look-behind assertion!
var whitespace = '[ \t\s\xA0,()+:-]'; // Almost white
var regexpCurrencyPrefix = new RegExp('('+whitespace+'|^)('+regexpCurrency+')(?:[ \n\t\s\xA0])*([0-9.,]*[0-9\-])', 'im');
var regexpCurrencySuffix = new RegExp('('+regexpCurrency+'|'+whitespace+'|^)([0-9.,]*[0-9\-])(?:[ \n\s\xA0])*('+regexpCurrency+')(?=$|[^A-Za-z0-9])', 'im');

textnodes = document.evaluate(
	"//text()",
    document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0; i < textnodes.snapshotLength; i++) 
{
	node = textnodes.snapshotItem(i);
	replaceWithElement(node, regexpCurrencyPrefix, createCurrencyElement);
	replaceWithElement(node, regexpCurrencySuffix, createCurrencyElementSuffix);
}