Benutzer:Nw520/currencyConverter.js

Aus Wikivoyage

Hinweis: Leere nach dem Veröffentlichen 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/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
/*     = 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 );
		} );
	}
}() );