Benutzer:Nw520/currencyConverter.js

Aus Wikivoyage
Zur Navigation springen Zur Suche springen

Hinweis: Leere nach dem Speichern den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Gehe zu Menü → Einstellungen (Opera → Einstellungen auf dem Mac) und dann auf Datenschutz & Sicherheit → Browserdaten löschen → Gespeicherte Bilder und Dateien.
/*     = CurrencyConverter =
 *     Dies ist nur ein Proof-of-Concept um zu testen, inwiefern ein Währungsumrechner umsetzbar ist
 *
 *     == ToDo Liste ==
 *     * Nachkommastellen sinnvoll runden (10er Schritte? bei 10/90 ab-/aufrufen?)
 *     * Festlegen einer Referenzwährung (EUR oder USD, je nachdem was in Wikidata gespeichert ist)
 *     * Stand des Wechselkurses darstellen (ggf. als title)
 *     * Bei vCards Währung aus data-currency-Parameter beziehen
 *     * Sinnvoll kommentieren
 *     * Falls gewünscht: Zusätzliche Klasse für Preisangaben die nicht mit   getrennt sind (Grundlage: Hilfe:Angabe_von_Telefonnummern,_Währungen_und_Öffnungszeiten#Währungen)
 *     * Erkennung von Preisspannen im Format `12-34 €`
 *     * Erkennung von Alternativen im Format `12/13 €`
 *     * Einstellbare Ziel-Währung
 *     * Währungskurse von Wikidata beziehen
 *     ** Ggf. in Browser für x Tage speichern
 *     * Erkennung und Umwandeln von Preisangaben in Fließtext (Realisierbarkeit noch fraglich)
 */
(function() {
	var CLASS_PRICEMARK = "pricemark";
    var CLASS_PRICEMARK_CONVERTED = "pricemark-converted";
    var CLASS_PRICEMARK_LOCAL = "pricemark-local";
    var CLASS_PRICEMARK_NOCONVERSION = "pricemark-noconversion";
    var TAG = "nw520.currencyConverter";   // Prefix for log output

	var currencyExchangeRateMap = {};
    var currencyPreSigns = [];
    var currencyPostSigns = [];
    var currencySignMap = {};
    var currencyUpdateMap = {};
	var currencyWdMap = {};
	var regexp = null;

	class PricemarkDefinition {
		constructor(value, currency, match) {
			this.value = value;
			this.currency = currency;
			this.match = match;
        }
        getFormattedPrice(isoId) {
			var out = this.getPrice(isoId).toLocaleString("de", {
                maximumFractionDigits: 2,
                minimumFractionDigits: 2
            });
            if(out.substr(out.length - 3) === ",00") {
            	out = out.substr(0, out.length - 3);
            }
            return out;
		}
        getPrice(isoId) {
			if(this.requiresConversion(isoId)) {
                return this.getPriceUsd() * (1 / currencyExchangeRateMap[isoId]);
            } else {   // No conversion needed
                return this.value;
            }
		}
		getPriceUsd() {
			return this.value * currencyExchangeRateMap[this.currency];
        }
        requiresConversion(isoId) {
            return isoId !== this.currency;
        }
	}

	setup();

    /* Erster Versuch Fließtext Währungsangaben bei Klick zu erkennen. vCard müssten ausgeschlossen werden.
     * Darstellen des umgerechneten Werts (ohne bestehende EventListener zu töten) problematisch
	function attachClickListener() {
		$("#bodyContent").click(function(e) {
			e.preventDefault();
			var selection = window.getSelection();
			var range = selection.getRangeAt(0);
			var anchor = selection.anchorNode;

			var pricemarks = findPricemarks(anchor.textContent);
			if(pricemarks.length > 0) {
				var clickedPricemark = filter(selection.anchorOffset, pricemarks);
			} else {
				// Nicht auf Preisangabe geklickt
			}
		});

		function filter(offset, pricemarks) {
			var result = null;
			for(i = 0; i < pricemarks.length && result === null; i++) {
				var pricemark = pricemarks[i];
				if(pricemark.range[0] <= offset && offset <= pricemark.range[1]) {
					result = pricemark;
				}
			}
			return result;
		}
    }
    */
	function setup() {
		setupCurrencies();
		setupRegex();
		injectInVcards();
		// attachClickListener();	
	}
	function setupCurrencies() {
		add("EUR",	"Q4916",	["€", "Euro"], 1.15, "2019-01-14", true, false);
		add("USD",	"Q4917",	["$", "US$", "$US"], 1, null, true, true);
		add("VND",	"Q192090",	["₫", "Dong"], 0.000043, "2019-01-14", true, true);
		console.debug(`[${TAG}]`, currencyExchangeRateMap, currencyPreSigns, currencySignMap, currencyWdMap);

		function add(isoId, wdId, signList, conversionToEur, lastUpdate, allowPostfix, allowPrefix) {
			isoId = isoId.toUpperCase();
			// Store exchange rate
			currencyExchangeRateMap[isoId] = conversionToEur;
			// Store wikidata id
			currencyWdMap[isoId] = wdId;
            // Store last update date
            currencyUpdateMap[isoId] = lastUpdate;

			register(isoId, isoId);
			if(typeof signList === "string") {
				console.warn(`[${TAG}] ${isoId}: Please pass alternative signs in an array, not as a string!`);
				register(signList, isoId);
			} else if (typeof signList === "object") {
				signList.forEach(function(val, i) {
					register(val, isoId);
				});
			}

			function register(sign, isoId) {
                // Store pre sign for regex generation
                if(allowPrefix) {
                    currencyPreSigns.push(sign);
                }
                // Store post sign for regex generation
				if(allowPostfix) {
                    currencyPostSigns.push(sign);
                }
				// Map alternative to ISO 4217
				currencySignMap[sign] = isoId;
			}
		}
	}
	function setupRegex() {
        // Backslashs are encoded as "\\"
        // RegExp can be tested on https://regex101.com/ and visualised on https://www.debuggex.com/ . Don't forget to replace "\\" with "\"
        var expression = `(^| |\\u00a0)((${currencyPreSigns.join("|").replace(new RegExp("\\$", "g"), "\\$")})( |\\u00a0))?(\\d+)((\\.\\d{3})*)(,(\\d{1,2}|-))?(( |\\u00a0)(${currencyPostSigns.join("|").replace(new RegExp("\\$", "g"), "\\$")}))?($|[ \\u00a0;)]|([.,]($|[ \\u00a0])))`;
        console.debug(`[${TAG}] ${expression}`);
		regexp = new RegExp(expression, "gmu");
	}
	function findPricemarks(text) {
		var pricemarks = [];
		var match;
		do {
			match = regexp.exec(text);

			if(match !== null) {
                var newPricemark = matchToPricemark(match);
				if(newPricemark !== null) {
                    pricemarks.push(newPricemark);
                }
			}
		} while(match);

		// Reset regex state
		regexp.lastIndex = 0;

		return pricemarks;

		function matchToPricemark(match) {
			/* 0: Full match
             * 1: Prefixed character
             * 2:   –
             * 3: Prefixed currency
             * 4: Separator between 3 and 5
             * 5: Digits before . and ,
             * 6: . and digits - repeatedly
             * 7:   –
             * 8:   –
             * 9: Digits after , or dash
             * 10:   -
             * 11: Separator between 9 and 12
             * 12: Postfixed currency
			 * 13: Postfixed character
             * 14:   -
			 */
            
            // Exactly one currency (pre- or postfixed is set)
            if(match[3] === undefined && match[12] !== undefined || match[3] !== undefined && match[12] === undefined) {
                var currency = (match[3] === undefined ? match[12] : match[3]);
                return new PricemarkDefinition(parsePrice(match), currencySignMap[currency], match);
            } else {
                return null;
            }
            
            function parsePrice(match) {
                return parseFloat(`${match[5]}${match[6].replace(new RegExp("\\.", "g"), "")}.${match[9] === undefined || match[9] === "-" ? "00" : match[9]}`);
            }
		}
	}
	function injectInVcards() {
		$(".listing-price").each(function() {
            var pricemarks = findPricemarks(this.textContent);
            var result = this.textContent;
            $.each(pricemarks, function(i, pricemark) {
                var pricemarkText = pricemark.match[0].substring(pricemark.match[1].length, pricemark.match[0].length - pricemark.match[13].length);
                // @TODO: Hardcoded currency, hardcoded internalisation
                result = result.replace(pricemark.match[0],
                            pricemark.match[1] +    // Pre- and suffixed characters are replaced too in order to differentiate between multiple occurences of same value with differentiating pre- or suffixed characters
                			`<span class="${CLASS_PRICEMARK} ${pricemark.requiresConversion("EUR") ? "" : CLASS_PRICEMARK_NOCONVERSION}">` + 
	                            `<span class="${CLASS_PRICEMARK_LOCAL}" data-currency="${pricemark.currency}">` +
	                                `${pricemarkText}`+
	                            `</span>` +
	                            (pricemark.requiresConversion("EUR") ? `<span class="${CLASS_PRICEMARK_CONVERTED}" data-currency="EUR"> (${pricemark.getFormattedPrice("EUR")}&nbsp;€)</span>` : "") +
                            `</span>` +
                            pricemark.match[13]
                         );
            });
            $(this).html(result);
        });
	}
})();