/* Copyright (c) 2012-2013 Epimorphics Ltd. Released under Apache License 2.0 http://www.apache.org/licenses/ */ var qonsole = function() { "use strict"; var YASQE = require('yasqe'), YASR = require('yasr'); /** * Escape html function, inspired by http://stackoverflow.com/questions/5499078/fastest-method-to-escape-html-tags-as-html-entities */ var escapeString = function(unescaped) { if (!unescaped) return ''; return unescaped.replace(/&/g, '&').replace(//g, '>'); } /** * Some custom requirements for Jena, on how to present the bindings. I.e., bnodes prefixed with _:, literals with surrounding quotes, and URIs with brackets */ YASR.plugins.table.defaults.getCellContent = function (yasr, plugin, bindings, variable, context) { var binding = bindings[variable]; var value = null; if (binding.type == "uri") { var title = null; var href = binding.value; var visibleString = escapeString(href); var prefixed = false; if (context.usedPrefixes) { for (var prefix in context.usedPrefixes) { if (visibleString.indexOf(context.usedPrefixes[prefix]) == 0) { visibleString = prefix + ':' + href.substring(context.usedPrefixes[prefix].length); prefixed = true; break; } } } if (!prefixed) visibleString = "<" + visibleString + ">"; value = "" + visibleString + ""; } else if (binding.type == "bnode"){ value = "_:" + escapeString(binding.value) + ""; } else if (binding.type == "literal") { var stringRepresentation = escapeString(binding.value); if (binding["xml:lang"]) { stringRepresentation = '"' + stringRepresentation + '"@' + binding["xml:lang"]; } else if (binding.datatype) { var xmlSchemaNs = "http://www.w3.org/2001/XMLSchema#"; var dataType = binding.datatype; if (dataType.indexOf(xmlSchemaNs) == 0) { dataType = "xsd:" + dataType.substring(xmlSchemaNs.length); } else { dataType = "<" + dataType + ">"; } stringRepresentation = '"' + stringRepresentation + '"^^' + dataType; } else { //just put quotes around it stringRepresentation = '"' + stringRepresentation + '"'; } value = "" + stringRepresentation + ""; } else { //this is a catch-all: when using e.g. a csv content type, the bindings are not typed value = escapeString(binding.value); } return "
" + value + "
"; }; /* JsLint */ /* * global sprintf, testCSS, loadConfig, bindEvents, $, onConfigLoaded, * updatePrefixDeclaration, _, showCurrentQuery, setCurrentEndpoint, * setCurrentFormat, elementVisible, runQuery, onLookupPrefix, * startTimingResults, onAddPrefix, initQuery, CodeMirror, onQuerySuccess, * onQueryFail, ajaxDataType, resetResults, XMLSerializer, showTableResult, * showCodeMirrorResult */ /* --- module vars --- */ /** The loaded configuration */ var _config = {}; var yasqe = null; var yasr = null; var _startTime = 0; var _outstandingQueries = 0; /* --- utils --- */ /** * Return the string representation of the given XML value, which may be a * string or a DOM object */ var xmlToString = function(xmlData) { var xs = _.isString(xmlData) ? xmlData : null; if (!xs && window.ActiveXObject && xmlData.xml) { xs = xmlData.xml; } if (!xs) { xs = new XMLSerializer().serializeToString(xmlData); } return xs; }; /** Browser sniffing */ var isOpera = function() { return !!(window.opera && window.opera.version); }; // Opera 8.0+ var isFirefox = function() { return testCSS('MozBoxSizing'); }; // FF 0.8+ var isSafari = function() { return Object.prototype.toString.call(window.HTMLElement).indexOf( 'Constructor') > 0; }; // At least Safari 3+: "[object HTMLElementConstructor]" var isChrome = function() { return !isSafari() && testCSS('WebkitTransform'); }; // Chrome 1+ var isIE = function() { return /* @cc_on!@ */false || testCSS('msTransform'); }; // At least IE6 var testCSS = function(prop) { return document.documentElement.style.hasOwnProperty(prop); }; /* --- application code --- */ /** Initialisation - only called once */ var init = function(config) { initYasqe(); loadConfig(config); bindEvents(); }; var initYasqe = function() { yasqe = YASQE(document.getElementById("query-edit-cm"), { sparql: { showQueryButton: true, callbacks: { beforeSend: startTimingResults, complete: showTime, } } }); yasr = YASR(document.getElementById("results"), { useGoogleCharts: false, //this way, the URLs in the results are prettified using the defined prefixes in the query getUsedPrefixes: yasqe.getPrefixesFromQuery }); /** * Set some of the hooks to link YASR and YASQE */ yasqe.options.sparql.callbacks.complete = yasr.setResponse; }; /** Load the configuration definition */ var loadConfig = function(config) { if (config.configURL) { $.getJSON(config.configURL, onConfigLoaded); } else { onConfigLoaded(config); } }; /** Return the current config object */ var config = function() { return _config; }; /** Bind events that we want to manage */ var bindEvents = function() { $("ul.prefixes").on( "click", "a.btn", function(e) { var elem = $(e.currentTarget); updatePrefixDeclaration($.trim(elem.text()), elem .data("uri"), !elem.is(".active")); }); $("ul.examples").on("click", "a", function(e) { var elem = $(e.currentTarget); $("ul.examples a").removeClass("active"); _.defer(function() { showCurrentQuery(); }); }); $(".endpoints").on("click", "a", function(e) { var elem = $(e.currentTarget); setCurrentEndpoint($.trim(elem.text())); }); $("#sparqlEndpoint").change(function() { yasqe.options.sparql.endpoint = $(this).val(); }); // dialogue events $("#prefixEditor").on("click", "#lookupPrefix", onLookupPrefix).on( "keyup", "#inputPrefix", function(e) { var elem = $(e.currentTarget); $("#lookupPrefix span").text(sprintf("'%s'", elem.val())); }); $("#addPrefix").on("click", onAddPrefix); /** * register content type changes. * Do not need to set them on load, as their default values are already the default vals of YASQE as well */ $("#graphContentType").change(function(){yasqe.options.sparql.acceptHeaderGraph = $(this).val()}); $("#selectContentType").change(function(){yasqe.options.sparql.acceptHeaderSelect = $(this).val()}); }; /** List the current defined prefixes from the config */ var initPrefixes = function(config) { var prefixAdd = $("ul.prefixes li:last"); $ .each( config.prefixes, function(key, value) { var html = sprintf( "
  • %s
  • ", value, key); $(html).insertBefore(prefixAdd); }); }; /** List the example queries from the config */ var initExamples = function(config) { var examples = $("ul.examples"); examples.empty(); $ .each( config.queries, function(i, queryDesc) { var html = sprintf( "
  • %s
  • ", queryDesc.name); examples.append(html); if (queryDesc.queryURL) { loadRemoteQuery(queryDesc.name, queryDesc.queryURL); } }); setFirstQueryActive(); }; /** Set the default active query */ var setFirstQueryActive = function() { if (_outstandingQueries === 0 && yasqe.getValue() == YASQE.defaults.value) { //only load the example query, when YASQE has not retrieved a previous query executed by the client $("ul.examples").find("a").first().addClass("active"); showCurrentQuery(); } }; /** Load a remote query */ var loadRemoteQuery = function(name, url) { _outstandingQueries++; var options = { success : function(data, xhr) { namedExample(name).query = data; _outstandingQueries--; setFirstQueryActive(); }, failure : function() { namedExample(name).query = "Not found: " + url; _outstandingQueries--; setFirstQueryActive(); }, dataType : "text" }; $.ajax(url, options); }; /** Set up the drop-down list of end-points */ var initEndpoints = function(config) { var endpoints = $("ul.endpoints"); endpoints.empty(); if (config.endpoints) { $ .each( config.endpoints, function(key, url) { var html = sprintf( "
  • %s
  • ", url); endpoints.append(html); }); setCurrentEndpoint(config.endpoints["default"]); } }; /** Successfully loaded the configuration */ var onConfigLoaded = function(config, status, jqXHR) { _config = config; initPrefixes(config); initExamples(config); initEndpoints(config); }; /** Set the current endpoint text */ var setCurrentEndpoint = function(url) { yasqe.options.sparql.endpoint = url; $("[id=sparqlEndpoint]").val(url); }; /** Return the current endpoint text */ var currentEndpoint = function(url) { return $("[id=sparqlEndpoint]").val(); }; /** Return the query definition with the given name */ var namedExample = function(name) { return _.find(config().queries, function(ex) { return ex.name === name; }); }; /** Return the currently active named example */ var currentNamedExample = function() { return namedExample($.trim($("ul.examples a.active").first().text())); }; /** Display the given query, with the currently defined prefixes */ var showCurrentQuery = function() { var query = currentNamedExample(); displayQuery(query); }; /** Display the given query */ var displayQuery = function(query) { if (query) { var queryBody = query.query ? query.query : query; var prefixes = assemblePrefixes(queryBody, query.prefixes) var q = sprintf("%s\n\n%s", renderPrefixes(prefixes), stripLeader(queryBody)); yasqe.setValue(q); syncPrefixButtonState(prefixes); } }; /** Return the currently selected output format */ var selectedFormat = function() { return $("a.display-format").data("value"); }; /** Update the user's format selection */ var setCurrentFormat = function(val, label) { $("a.display-format").data("value", val).find("span").text(label); }; /** Assemble the set of prefixes to use when initially rendering the query */ var assemblePrefixes = function(queryBody, queryDefinitionPrefixes) { if (queryBody.match(/^prefix/)) { // strategy 1: there are prefixes encoded in the query body return assemblePrefixesFromQuery(queryBody); } else if (queryDefinitionPrefixes) { // strategy 2: prefixes given in query def return _.map(queryDefinitionPrefixes, function(prefixName) { return { name : prefixName, uri : config().prefixes[prefixName] }; }); } else { return assembleCurrentPrefixes(); } }; /** Return an array comprising the currently selected prefixes */ var assembleCurrentPrefixes = function() { var l = $("ul.prefixes a.active").map(function(i, elt) { return { name : $.trim($(elt).text()), uri : $(elt).data("uri") }; }); return $.makeArray(l); }; // /** Return an array of the prefixes parsed from the given query body */ // var assemblePrefixesFromQuery = function(queryBody) { // var leader = queryLeader(queryBody)[0].trim(); // var pairs = _.compact(leader.split("prefix")); // var prefixes = []; // // _.each(pairs, function(pair) { // var m = pair.match("^\\s*(\\w+)\\s*:\\s*<([^>]*)>\\s*$"); // prefixes.push({ // name : m[1], // uri : m[2] // }); // }); // // return prefixes; // }; /** * Ensure that the prefix buttons are in sync with the prefixes used in a * new query */ var syncPrefixButtonState = function(prefixes) { $("ul.prefixes a").each(function(i, elt) { var name = $.trim($(elt).text()); if (_.find(prefixes, function(p) { return p.name === name; })) { $(elt).addClass("active"); } else { $(elt).removeClass("active"); } }); }; /** Split a query into leader (prefixes and leading blank lines) and body */ var queryLeader = function(query) { var pattern = /(prefix [^>]+>[\s\n]*)/; var queryBody = query; var i = 0; var m = queryBody.match(pattern); while (m) { i += m[1].length; queryBody = queryBody.substring(i); m = queryBody.match(pattern); } return [ query.substring(0, query.length - queryBody.length), queryBody ]; }; /** Remove the query leader */ var stripLeader = function(query) { return queryLeader(query)[1]; }; /** Return a string comprising the given prefixes */ var renderPrefixes = function(prefixes) { return _.map(prefixes, function(p) { return sprintf("prefix %s: <%s>", p.name, p.uri); }).join("\n"); }; /** Add or remove the given prefix declaration from the current query */ var updatePrefixDeclaration = function(prefix, uri, added) { var prefixObj = {}; prefixObj[prefix] = uri; if (added) { yasqe.addPrefixes(prefixObj); } else { yasqe.removePrefixes(prefixObj); } }; /** Return the sparql service we're querying against */ var sparqlService = function() { var service = config().service; if (!service) { // default is the remote service config().service = new RemoteSparqlService(); service = config().service; } return service; }; /** Hide or reveal an element using Bootstrap .hidden class */ var elementVisible = function(elem, visible) { if (visible) { $(elem).removeClass("hidden"); } else { $(elem).addClass("hidden"); } }; /** Prepare to show query time taken */ var startTimingResults = function() { _startTime = new Date().getTime(); elementVisible(".timeTaken"); }; /** Show results count and time */ var showTime = function() { var duration = new Date().getTime() - _startTime; var ms = duration % 1000; duration = Math.floor(duration / 1000); var s = duration % 60; var m = Math.floor(duration / 60); var html = sprintf("time taken: %d min %d.%03d s", m, s, ms); $(".timeTaken").html(html); elementVisible(".timeTaken", true); }; /** Lookup a prefix on prefix.cc */ var onLookupPrefix = function(e) { e.preventDefault(); var prefix = $.trim($("#inputPrefix").val()); $("#inputURI").val(""); if (prefix) { $.getJSON(sprintf("http://prefix.cc/%s.file.json", prefix), function(data) { $("#inputURI").val(data[prefix]); }); } }; /** User wishes to add the prefix */ var onAddPrefix = function(e) { var prefix = $.trim($("#inputPrefix").val()); var uri = $.trim($("#inputURI").val()); if (uri) { _config.prefixes[prefix] = uri; } else { delete _config.prefixes[prefix]; } // remember the state of current user selections, then re-create the // list var selections = {}; $("ul.prefixes a.btn").each(function(i, a) { selections[$(a).text()] = $(a).hasClass("active"); }); $("ul.prefixes li[class!=keep]").remove(); initPrefixes(_config); // restore selections state $.each(selections, function(k, v) { if (!v) { $(sprintf("ul.prefixes a.btn:contains('%s')", k)).removeClass( "active"); } }); var lines = yasqe.getValue().split("\n"); lines = _.reject(lines, function(line) { return line.match(/^prefix/); }); var q = sprintf("%s\n%s", renderPrefixes(assembleCurrentPrefixes()), lines.join("\n")); yasqe.setValue(q); }; /** Disable or enable the button to submit a query */ var disableSubmit = function(disable) { var elem = $("a.run-query"); elem.prop('disabled', disable); if (disable) { elem.addClass("disabled"); } else { elem.removeClass("disabled"); } }; return { init : init, setCurrentEndpoint: setCurrentEndpoint }; }();