User:Nikki/ChecksumCheck.js

From Wikidata
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * This script displays a symbol after external identifiers which contain
 * checksums, to indicate whether the checksum in the identifier is correct.
 * 
 * Identifiers currently supported:
 * BNF (P268)
 * ELNET (P6394)
 * IdRef/SUDOC (P269)
 * ISNI (P213)
 * NTA (P1006)
 * ORCID (P496)
 * 
 * To use it, add the following to your common.js:
 * mw.loader.load("//www.wikidata.org/w/index.php?title=User:Nikki/ChecksumCheck.js&action=raw&ctype=text/javascript");
 * 
 * @license CC0-1.0
 */
(function () {

	// TODO: i18n
	let goodtitle = "Checksum is correct";
	let badtitle = "Checksum is incorrect";
	
	let goodsvg = `<svg xmlns="http://www.w3.org/2000/svg" height="10" width="10" viewBox="0 0 2 2"><circle fill="#096" r="1" cx="1" cy="1"/></svg>`;
	let badsvg = `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10"><path fill="#900" d="M5,1L10,9H1z"/></svg>`;

	mw.util.addCSS(`
		.checksum {
			display: inline;
			margin-inline: 0.25em;
			user-select: none;
		}
	`);

	function checkBNF(id) {
		let chars = id.split("");
		let checksum = chars.pop();

		let checksums = "0123456789bcdfghjkmnpqrstvwxz".split("");
		let sum = 31; // = c*1 + b*2

		for (let i in chars) {
			sum += (parseInt(i, 10) + 3) * parseInt(chars[i], 10);
		}

		let mod = sum % 29;
		return checksums[mod] == checksum;
	}

	function checkSUDOC(id) {
		let chars = id.split("");
		let checksum = chars.pop();
		if (checksum === "X") {
			checksum = 10;
		}

		var sum = 0;

		for (let i in chars) {
			sum += (9 - parseInt(i, 10)) * parseInt(chars[i], 10);
		}

		let mod1 = sum % 11;
		let mod2 = (11 - mod1) % 11;
		return mod2 == checksum;
	}

	function checkISNI(id) {
		id = id.replace(/[ -]/g, "");
		if (id.length !== 16) {
			return false;
		}

		let sum = 0;
		let chars = id.split("").reverse();

		let cd = chars.shift();
		if (cd === "X") {
			cd = 10;
		}

		for (let i in chars) {
			let w = Math.pow(2, parseInt(i)+1) % 11;
			let v = parseInt(chars[i], 10);
			sum += w * v;
		}

		let mod = (sum % 11);
		let expected_cd = 12 - mod - Math.floor((12 - mod) / 11) * 11;

		return parseInt(cd, 10) === expected_cd;
	}

	function checkELNET(id) {
		let sum = 0;
		let chars = id.split("");

		let cd = chars.pop();
		if (cd === "x") {
			cd = 10;
		}

		for (let i = 1; i < chars.length; i++) {
			sum += chars[i] * (9 - i);
		}

		let mod = sum % 11;
		return mod == cd;
	}

	function checkProperty(pid, func) {
		// TODO: Use entity data from wikibase.entityPage.entityLoaded
		for (let e of document.querySelectorAll(`#${ pid } .wikibase-statementview-mainsnak .wikibase-snakview-value`)) {
			let id = e.querySelector("a").textContent;
			let res = func.apply(null, [id]);
			
			let div = document.createElement("div");
			div.classList.add("checksum");

			if (res) {
				div.insertAdjacentHTML("afterbegin", goodsvg);
				div.title = goodtitle;
			} else {
				div.insertAdjacentHTML("afterbegin", badsvg);
				div.title = badtitle;
			}

			e.appendChild(div);
		}
	}

	function init() {
		checkProperty("P268", checkBNF);

		checkProperty("P269", checkSUDOC);
		checkProperty("P1006", checkSUDOC); // NTA
		//checkProperty("P227", checkSUDOC); // GND - works for some

		checkProperty("P213", checkISNI);
		checkProperty("P496", checkISNI); // ORCID

		checkProperty("P6394", checkELNET);
	}

	mw.hook("wikibase.entityPage.entityView.rendered").add(init);

})();