User:Teester/EntityShape.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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * EntityShape.js adds an input box to a wikidata page wherein you can enter an entityschema 
 * (such as E10).  When you click "Check", checks whether each statement and property conforms
 * to the schema.  It then displays a summary at the top of the item for each property indicating 
 * whether they conform or not.  It also adds a badge to each statement and each property on the
 * page indicating whether they conform or not.
 **/

(function() {
let entitycheck_stylesheet = entitycheck_getStylesheet();
$('html > head').append("<style>" + entitycheck_stylesheet + "</style>");

$(document).ready(function(){
	let entityID = mw.config.get( 'wbEntityId' );
	let lang = mw.config.get( 'wgUserLanguage' );
	if (entityID) {
		let schema = window.localStorage.getItem("entitycheck");
		let value = window.localStorage.getItem("entitycheck-auto");
		let entitycheck_entityName = document.location.pathname.substring(6);
		if (value == "true") {
			entitycheck_checkEntity(entitycheck_entityName, schema, lang);
			$("#entitycheck-checkbox").prop('checked', true);
		} else {
			$("#entitycheck-checkbox").prop('checked', false);
		}
		$("#entitycheck-entityToCheck:text").val(schema);
	}
});

let entitycheck_conditions = ["/wiki/Q", "/wiki/P", "/wiki/L"];
if (entitycheck_conditions.some(el => document.location.pathname.includes(el))) {
	let entitycheck_entity_html = '<div><span id="entitycheck-simpleSearch"><span>';
	entitycheck_entity_html += '<input type="text" id="entitycheck-entityToCheck" placeholder="Enter a schema to check against e.g. E234">';
	entitycheck_entity_html += '<input type="submit" id="entitycheck-schemaSearchButton" class="searchButton" name="check" value="Check">';
	entitycheck_entity_html += '</span></span><input type="checkbox" id="entitycheck-checkbox">';
	entitycheck_entity_html += '<label for="entitycheck-checkbox"><small>Automatically check schema</small></label><span id="entityCheckResponse"></span></div>';
	var entitycheck_entityTitle = $(".wikibase-title-id" )[0].innerText;
	$(entitycheck_entity_html).insertAfter("#siteSub");
	$("#entitycheck-schemaSearchButton").click(function(){ entitycheck_update() });
	$("#entitycheck-checkbox").click(function() { entitycheck_checkbox(); });
}

$("#entitycheck-entityToCheck").on("keyup", function(event){
	if (event.key === "Enter") {
		event.preventDefault();
		$("#entitycheck-schemaSearchButton").click();
		return false;
	}
});

function entitycheck_update() {
	let entitycheck_entitySchema = $("#entitycheck-entityToCheck")[0].value.toUpperCase();
	let entitycheck_entityName = document.location.pathname.substring(6);
	let lang = mw.config.get( 'wgUserLanguage' );
	window.localStorage.setItem("entitycheck", entitycheck_entitySchema);
	entitycheck_checkEntity(entitycheck_entityName, entitycheck_entitySchema, lang);
}

function entitycheck_checkbox() {
	if ($('#entitycheck-checkbox').is(":checked")) {
		window.localStorage.setItem("entitycheck-auto", true);
	} else {
		window.localStorage.setItem("entitycheck-auto", false);
	}
}

function entitycheck_checkEntity(entity, entitySchema, language) {
	$("#entityCheckResponse").contents().remove();
	$(".entitycheck-property").remove();
	let url = "https://entityshape.toolforge.org/api?entityschema=" + entitySchema + "&entity=" + entity + "&language=" + language;
	$.ajax({
		type: "GET",
		dataType: "json",
		url: url,
		success: function(data){
			let html = "";

			html += "<br/>Checking against <b><a href='https://www.wikidata.org/wiki/EntitySchema:" + data.schema + "'>" + data.schema + ":" + data.name + '</a></b>:';
			if (data.validity.results) {
				if (data.validity.results[0].result) {
					html += '<span class="response"><span class="pass">✔</span><span> Pass</span></span><br/>';
				} else {
					html += entitycheck_parseResult(data.validity.results[0]);
				}
			}
			html += '<div style="overflow-y: scroll; max-height:200px;"><table style="width:100%;">';
			html += '<th class="entitycheck_table" title="Properties in this item which must be present">Required properties</th>';
			html += '<th class="entitycheck_table" title="Properties in this item which can be present but do not have to be">Optional properties</th>';
			html += '<th class="entitycheck_table" title="Properties in this item which are not allowed to be present or not mentioned in the entityschema">Other properties</th><tr>';

			let required_html = '<td class="entitycheck_table required">';
			let optional_html = '<td class="entitycheck_table optional">';
			let absent_html = '<td class="entitycheck_table absent">';
			if (data.properties) {
				for (let key in data.properties) {
					let shape_html = "";
					let response1 = data.properties[key].response;
					let response_class  = "";
					switch (response1) {
						case "present":
							response_class = "present";
							break;
						case "correct":
							response_class = "correct";
							break;
						case "missing":
							response_class = "missing";
							break;
						default:
							response_class = "wrong";
							break;
					}
					if (data.properties[key].necessity == "absent") {
					    if (response1 == "too many statements") {
					        response1 = "not allowed";
					    }
					}
					if (!response1) {
						response1 = "Not in schema";
						response_class = "notinschema";
					}
					if (response1 == null) {
						response1 = "";
						shape_html += '<a href="https://www.wikidata.org/wiki/Property:' + key;
						shape_html +='">'+ key + " - <small>" + data.properties[key].name + '</small></a><br/>';
					} else {
						shape_html += '<span class="entitycheck-span entitycheck-' + response_class + '">' + response1 + '</span><a href="https://www.wikidata.org/wiki/Property:' + key;
						shape_html +='" class="is_entitycheck-'+ response_class+'">'+ key + " - <small>" + data.properties[key].name + '</small></a><br/>';
					}
					switch (data.properties[key].necessity){
						case "required":
							required_html += shape_html;
							break;
						case "optional":
							optional_html += shape_html;
							break;
						default:
							absent_html += shape_html;
							break;
					}
					if ($("#" + key)[0]) {
						$("#" + key + " .wikibase-statementgroupview-property-label").append("<br class='entitycheck-property'/><div style='display:inline-block;' class='entitycheck-span entitycheck-property entitycheck-" + response_class + "' title='" + data.schema + ": " + data.name + "'>" + data.schema + ": " + response1 + "</div>");
					}
				}
			}
			required_html += "</td>";
			optional_html += "</td>";
			absent_html += "</td>";
			html += required_html + optional_html + absent_html;
			html += '</tr></table></div>';

			$("#entityCheckResponse" ).append( html );

			if (data.statements) {
				for (var statement in data.statements) {
					let response2 = data.statements[statement].response;
					if (response2 != "not in schema") {
						html = "<br class='entitycheck-property'/><span class='entitycheck-span entitycheck-property entitycheck-" + response2 + "'>" + response2 + "</span>";
						$("div[id='" + statement + "'] .wikibase-toolbar-button-edit").append(html);
					}
				}
			}
			if (data.general) {
			    if (data.general.language) {
			    	let response3 = data.general.language;
                    if (response3 != "not in schema") {
                        html = "<span class='entitycheck-property'/><span class='entitycheck-span entitycheck-property entitycheck-" + response3 + "'>" + response3 + "</span>";
                        $("span[class='language-lexical-category-widget_language']").append(html);
                    }
			    }
                if (data.general.lexicalCategory) {
                    let response4 = data.general.lexicalCategory;
                    if (response4 != "not in schema") {
                        html = "<span class='entitycheck-property'/><span class='entitycheck-span entitycheck-property entitycheck-" + response4 + "'>" + response4 + "</span>";
                        $("span[class='language-lexical-category-widget_lexical-category']").append(html);
                    }
                }
			}
		},
		error: function(data) {
			$("#entityCheckResponse").append( '<span>Unable to validate schema</span>' );
		}
	});
}

function entitycheck_parseResult(data) {
	let property = [];
	let html = '<span class="response" title="' + data.reason + '"><span class="fail">✘</span><span class="response"> Fail</span>';
	let no_matching_triples = "No matching triples found for predicate";
	if (data.reason.includes(no_matching_triples)) {
		property = data.reason.match(/P\d+/g);
	}
	if (property !== null) {
		property = property.reduce(function(a,b) {
		        if (a.indexOf(b) < 0) {
		            a.push(b);
		        }
		        return a;
		    },[]);
		for (let i = 0; i < property.length; i++) {
			if (property[i].length > 100) {
				property[i] = property[i].substr(0,100) + "…";
			}
			html += '<span class="missing"> Missing valid ' + property[i] + '</span>';
		}
	}
	html += "</span>";
	return html;
}

function entitycheck_getStylesheet() {
	let stylesheet = "#entitycheck-schemaSearchButton { background-position: center center; background-repeat: no-repeat; position: relative !important; top: 0; right: 0; overflow: hidden; height: 100%; background-color: #1E90FF !important; color: #FFFFFF !important; padding: 0.5em; text-indent: 0px !important; margin-left: 5px; width: 50px; margin-right: 5px;}";
	stylesheet += "#entitycheck-entityToCheck { padding: 0.5em; margin: 0; width: 33%;}";
	stylesheet += ".response { padding: 2px;}";
	stylesheet += "a.is_entitycheck-present { color: #008800; }";
	stylesheet += "a.is_entitycheck-allowed { color: #008800; }";
	stylesheet += "a.is_entitycheck-correct { color: #00CC00; }";
	stylesheet += "a.is_entitycheck-missing { color: #FF5500; }";
	stylesheet += "a.is_entitycheck-notinschema { color: #FF5500; }";
	stylesheet += "a.is_entitycheck-wrong { color: #CC0000; }";
	stylesheet += "a.is_entitycheck-wrong_amount { color: #CC0000; }";
	stylesheet += "a.is_entitycheck-incorrect { color: #CC0000; }";
	stylesheet += ".entitycheck_table {vertical-align: top; width: 33%;} ";
	stylesheet += ".entitycheck-missing { background-color: #FF8C00; }";
	stylesheet += ".entitycheck-notinschema { background-color: #FF8C00; }";
	stylesheet += ".entitycheck-wrong { background-color: #CC0000; }";
	stylesheet += ".entitycheck-incorrect { background-color: #CC0000; }";
	stylesheet += ".entitycheck-wrong_amount { background-color: #CC0000; }";
	stylesheet += ".entitycheck-excess { background-color: #CC0000; }";
	stylesheet += ".entitycheck-deficit { background-color: #CC0000; }";
	stylesheet += ".entitycheck-present { background-color: #008800; }";
	stylesheet += ".entitycheck-allowed { background-color: #008800; }";
	stylesheet += ".entitycheck-correct { background-color: #00CC00; }";
	stylesheet += ".required .entitycheck-missing { background-color: #FF0000;}";
	stylesheet += ".required a.is_entitycheck-missing { color: #FF0000;}";
	stylesheet += ".absent .entitycheck-missing { display: none;}";
	stylesheet += ".absent a.is_entitycheck-missing { display: none;}";
	stylesheet += ".entitycheck-span { color: #ffffff; padding:2px; margin: 2px; font-size:75%; border-radius:2px; }";
	return stylesheet;
}
}());