// shex-simple - Simple ShEx2 validator for HTML.
// Copyright 2017 Eric Prud'hommeux
// Release under MIT License.
const START_SHAPE_LABEL = "START";
const START_SHAPE_INDEX_ENTRY = "- start -"; // specificially not a JSON-LD @id form.
const INPUTAREA_TIMEOUT = 250;
const NO_MANIFEST_LOADED = "no manifest loaded";
var LOG_PROGRESS = false;
var DefaultBase = location.origin + location.pathname;
var Caches = {};
Caches.inputSchema = makeSchemaCache($("#inputSchema textarea.schema"));
Caches.inputData = makeTurtleCache($("#inputData textarea"));
Caches.manifest = makeManifestCache($("#manifestDrop"));
Caches.shapeMap = makeShapeMapCache($("#textMap")); // @@ rename to #shapeMap
var ShExRSchema; // defined below
const ParseTriplePattern = (function () {
const uri = "<[^>]*>|[a-zA-Z0-9_-]*:[a-zA-Z0-9_-]*";
const literal = "((?:" +
"'(?:[^'\\\\]|\\\\')*'" + "|" +
"\"(?:[^\"\\\\]|\\\\\")*\"" + "|" +
"'''(?:(?:'|'')?[^'\\\\]|\\\\')*'''" + "|" +
"\"\"\"(?:(?:\"|\"\")?[^\"\\\\]|\\\\\")*\"\"\"" +
")" +
"(?:@[a-zA-Z-]+|\\^\\^(?:" + uri + "))?)";
const uriOrKey = uri + "|FOCUS|_";
// const termOrKey = uri + "|" + literal + "|FOCUS|_";
return "(\\s*{\\s*)("+
uriOrKey+")?(\\s*)("+
uri+"|a)?(\\s*)("+
uriOrKey+"|" + literal + ")?(\\s*)(})?(\\s*)";
})();
var Getables = [
{queryStringParm: "schema", location: Caches.inputSchema.selection, cache: Caches.inputSchema},
{queryStringParm: "data", location: Caches.inputData.selection, cache: Caches.inputData },
{queryStringParm: "manifest", location: Caches.manifest.selection, cache: Caches.manifest , fail: e => $("#manifestDrop li").text(NO_MANIFEST_LOADED)},
{queryStringParm: "shape-map", location: $("#textMap"), cache: Caches.shapeMap },
];
var QueryParams = Getables.concat([
{queryStringParm: "interface", location: $("#interface"), deflt: "human" },
{queryStringParm: "regexpEngine", location: $("#regexpEngine"), deflt: "threaded-val-nerr" },
]);
// utility functions
function parseTurtle (text, meta, base) {
var ret = ShEx.N3.Store();
ShEx.N3.Parser._resetBlankNodeIds();
var parser = ShEx.N3.Parser({documentIRI: base, format: "text/turtle" });
var triples = parser.parse(text);
if (triples !== undefined)
ret.addTriples(triples);
meta.base = parser._base;
meta.prefixes = parser._prefixes;
return ret;
}
var shexParser = ShEx.Parser.construct(DefaultBase);
function parseShEx (text, meta, base) {
shexParser._setOptions({duplicateShape: $("#duplicateShape").val()});
shexParser._setBase(base);
var ret = shexParser.parse(text);
// ret = ShEx.Util.canonicalize(ret, DefaultBase);
meta.base = ret.base;
meta.prefixes = ret.prefixes;
return ret;
}
function sum (s) { // cheap way to identify identical strings
return s.replace(/\s/g, "").split("").reduce(function (a,b){
a = ((a<<5) - a) + b.charCodeAt(0);
return a&a
},0);
}
//
").text(ShEx.Util.errsToSimple(entry.appinfo).join("\n"))); break; case "minimal": if (fails) entry.reason = ShEx.Util.errsToSimple(entry.appinfo).join("\n"); delete entry.appinfo; // fall through to default default: elt = $("").text(JSON.stringify(entry, null, " ")).addClass(klass); } results.append(elt); // update the FixedMap var shapeString = entry.shape === ShEx.Validator.start ? START_SHAPE_INDEX_ENTRY : entry.shape; var fixedMapEntry = $("#fixedMap .pair"+ "[data-node='"+entry.node+"']"+ "[data-shape='"+shapeString+"']"); fixedMapEntry.addClass(klass).find("a").text(resultStr); var nodeLex = fixedMapEntry.find("input.focus").val(); var shapeLex = fixedMapEntry.find("input.inputShape").val(); var anchor = encodeURIComponent(nodeLex) + "@" + encodeURIComponent(shapeLex); elt.attr("id", anchor); fixedMapEntry.find("a").attr("href", "#" + anchor); fixedMapEntry.attr("title", entry.elapsed + " ms") } function finishRendering (done) { $("#results .status").text("rendering results...").show(); // Add commas to JSON results. if ($("#interface").val() !== "human") $("#results div *").each((idx, elt) => { if (idx === 0) $(elt).prepend("["); $(elt).append(idx === $("#results div *").length - 1 ? "]" : ","); }); $("#results .status").hide(); // for debugging values and schema formats: // try { // var x = ShEx.Util.valToValues(ret); // // var x = ShEx.Util.ShExJtoAS(valuesToSchema(valToValues(ret))); // res = results.replace(JSON.stringify(x, null, " ")); // var y = ShEx.Util.valuesToSchema(x); // res = results.append(JSON.stringify(y, null, " ")); // } catch (e) { // console.dir(e); // } results.finish(); } } var LastFailTime = 0; function failMessage (e, action, text) { $("#results .status").empty().text("Errors encountered:").show() var div = $("").addClass("error"); div.append($("").text("error " + action + ":\n")); div.append($("").text(e.message)); if (text) div.append($("").text(text)); results.append(div); LastFailTime = new Date().getTime(); } function addEmptyEditMapPair (evt) { addEditMapPairs(null, $(evt.target).parent().parent()); markEditMapDirty(); return false; } function addEditMapPairs (pairs, target) { (pairs || [{node: {type: "empty"}}]).forEach(pair => { var nodeType = (typeof pair.node !== "object" || "@value" in pair.node) ? "node" : pair.node.type; var skip = false; var node; var shape; switch (nodeType) { case "empty": node = shape = ""; break; case "node": node = ldToTurtle(pair.node, Caches.inputData.meta.termToLex); shape = startOrLdToTurtle(pair.shape); break; case "TriplePattern": node = renderTP(pair.node); shape = startOrLdToTurtle(pair.shape); break; case "Extension": failMessage(Error("unsupported extension: <" + pair.node.language + ">"), "parsing Query Map", pair.node.lexical); skip = true; // skip this entry. break; default: results.append($("").append( $("").text("unrecognized ShapeMap:"), $("").text(JSON.stringify(pair)) ).addClass("error")); skip = true; // skip this entry. break; } if (!skip) { var spanElt = $("", {class: "pair"}); var focusElt = $("", { rows: '1', type: 'text', class: 'data focus' }).text(node).on("change", markEditMapDirty); var shapeElt = $("", { type: 'text', value: shape, class: 'schema inputShape' }).on("change", markEditMapDirty); var addElt = $("", { class: "addPair", title: "add a node/shape pair"}).text("+"); var removeElt = $("", { class: "removePair", title: "remove this node/shape pair"}).text("-"); addElt.on("click", addEmptyEditMapPair); removeElt.on("click", removeEditMapPair); spanElt.append([focusElt, "@", shapeElt, addElt, removeElt].map(elt => { return $(" ").append(elt); })); if (target) { target.after(spanElt); } else { $("#editMap").append(spanElt); } } }); if ($("#editMap .removePair").length === 1) $("#editMap .removePair").css("visibility", "hidden"); else $("#editMap .removePair").css("visibility", "visible"); $("#editMap .pair").each(idx => { addContextMenus("#editMap .pair:nth("+idx+") .focus", Caches.inputData); addContextMenus(".pair:nth("+idx+") .inputShape", Caches.inputSchema); }); return false; function renderTP (tp) { var ret = ["subject", "predicate", "object"].map(k => { var ld = tp[k]; if (ld === ShEx.ShapeMap.focus) return "FOCUS"; if (!ld) // ?? ShEx.Uti.any return "_"; return ldToTurtle(ld, Caches.inputData.meta.termToLex); }); return "{" + ret.join(" ") + "}"; } function startOrLdToTurtle (term) { return term === ShEx.Validator.start ? START_SHAPE_LABEL : ldToTurtle(term, Caches.inputSchema.meta.termToLex); } } function removeEditMapPair (evt) { markEditMapDirty(); if (evt) { $(evt.target).parent().parent().remove(); } else { $("#editMap .pair").remove(); } if ($("#editMap .removePair").length === 1) $("#editMap .removePair").css("visibility", "hidden"); return false; } function prepareControls () { $("#menu-button").on("click", toggleControls); $("#interface").on("change", setInterface); $("#regexpEngine").on("change", toggleControls); $("#validate").on("click", disableResultsAndValidate); $("#clear").on("click", clearAll); $("#download-results-button").on("click", downloadResults); $("#loadForm").dialog({ autoOpen: false, modal: true, buttons: { "GET": function (evt, ui) { results.clear(); var target = Getables.find(g => g.queryStringParm === $("#loadForm span").text()); var url = $("#loadInput").val(); var tips = $(".validateTips"); function updateTips (t) { tips .text( t ) .addClass( "ui-state-highlight" ); setTimeout(function() { tips.removeClass( "ui-state-highlight", 1500 ); }, 500 ); } if (url.length < 5) { $("#loadInput").addClass("ui-state-error"); updateTips("URL \"" + url + "\" is way too short."); return; } tips.removeClass("ui-state-highlight").text(); target.cache.asyncGet(url).catch(function (e) { updateTips(e.message); }); }, Cancel: function() { $("#loadInput").removeClass("ui-state-error"); $("#loadForm").dialog("close"); toggleControls(); } }, close: function() { $("#loadInput").removeClass("ui-state-error"); $("#loadForm").dialog("close"); toggleControls(); } }); Getables.forEach(target => { var type = target.queryStringParm $("#load-"+type+"-button").click(evt => { var prefillURL = target.url ? target.url : target.cache.meta.base && target.cache.meta.base !== DefaultBase ? target.cache.meta.base : ""; $("#loadInput").val(prefillURL); $("#loadForm").attr("class", type).find("span").text(type); $("#loadForm").dialog("open"); }); }); $("#about").dialog({ autoOpen: false, modal: true, width: "50%", buttons: { "Dismiss": dismissModal }, close: dismissModal }); $("#about-button").click(evt => { $("#about").dialog("open"); }); $("#shapeMap-tabs").tabs({ activate: function (event, ui) { if (ui.oldPanel.get(0) === $("#editMap-tab").get(0)) copyEditMapToTextMap(); } }); $("#textMap").on("change", evt => { results.clear(); copyTextMapToEditMap(); }); Caches.inputData.selection.on("change", evt => { copyEditMapToFixedMap(); }); $("#copyEditMapToFixedMap").on("click", copyEditMapToFixedMap); // may add this button to tutorial function dismissModal (evt) { // $.unblockUI(); $("#about").dialog("close"); toggleControls(); return true; } // Prepare file uploads $("input.inputfile").each((idx, elt) => { $(elt).on("change", function (evt) { var reader = new FileReader(); reader.onload = function(evt) { if(evt.target.readyState != 2) return; if(evt.target.error) { alert("Error while reading file"); return; } $($(elt).attr("data-target")).val(evt.target.result); }; reader.readAsText(evt.target.files[0]); }); }); } function toggleControls (evt) { var revealing = evt && $("#controls").css("display") !== "flex"; $("#controls").css("display", revealing ? "flex" : "none"); toggleControlsArrow(revealing ? "up" : "down"); if (revealing) { var target = evt.target; while (target.tagName !== "BUTTON") target = target.parentElement; if ($("#menuForm").css("position") === "absolute") { $("#controls"). css("top", 0). css("left", $("#menu-button").css("margin-left")); } else { var bottonBBox = target.getBoundingClientRect(); var controlsBBox = $("#menuForm").get(0).getBoundingClientRect(); var left = bottonBBox.right - bottonBBox.width; // - controlsBBox.width; $("#controls").css("top", bottonBBox.bottom).css("left", left); } $("#permalink a").attr("href", getPermalink()); } return false; } function toggleControlsArrow (which) { // jQuery can't find() a prefixed attribute (xlink:href); fall back to DOM: if (document.getElementById("menu-button") === null) return; var down = $(document.getElementById("menu-button"). querySelectorAll('use[*|href="#down-arrow"]')); var up = $(document.getElementById("menu-button"). querySelectorAll('use[*|href="#up-arrow"]')); switch (which) { case "down": down.show(); up.hide(); break; case "up": down.hide(); up.show(); break; default: throw Error("toggleControlsArrow expected [up|down], got \"" + which + "\""); } } function setInterface (evt) { toggleControls(); customizeInterface(); } function downloadResults (evt) { var typed = [ { type: "text/plain", name: "results.txt" }, { type: "application/json", name: "results.json" } ][$("#interface").val() === "appinfo" ? 1 : 0]; var blob = new Blob([results.text()], {type: typed.type}); $("#download-results-button") .attr("href", window.URL.createObjectURL(blob)) .attr("download", typed.name); toggleControls(); console.log(results.text()); } /** * * location.search: e.g. "?schema=asdf&data=qwer&shape-map=ab%5Ecd%5E%5E_ef%5Egh" */ var parseQueryString = function(query) { if (query[0]==='?') query=query.substr(1); // optional leading '?' var map = {}; query.replace(/([^&,=]+)=?([^&,]*)(?:[&,]+|$)/g, function(match, key, value) { key=decodeURIComponent(key);value=decodeURIComponent(value); (map[key] = map[key] || []).push(value); }); return map; }; function markEditMapDirty () { $("#editMap").attr("data-dirty", true); } function markEditMapClean () { $("#editMap").attr("data-dirty", false); } /** getShapeMap -- zip a node list and a shape list into a ShapeMap * use {Caches.inputData,Caches.inputSchema}.meta.{prefix,base} to complete IRIs */ function copyEditMapToFixedMap () { $("#fixedMap tbody").empty(); // empty out the fixed map. var fixedMapTab = $("#shapeMap-tabs").find('[href="#fixedMap-tab"]'); var restoreText = fixedMapTab.text(); fixedMapTab.text("resolving Fixed Map").addClass("running"); var nodeShapePromises = $("#editMap .pair").get().reduce((acc, queryPair) => { $(queryPair).find(".error").removeClass("error"); // remove previous error markers var node = $(queryPair).find(".focus").val(); var shape = $(queryPair).find(".inputShape").val(); if (!node || !shape) return acc; var smparser = ShEx.ShapeMapParser.construct( Caches.shapeMap.meta.base, Caches.inputSchema.meta, Caches.inputData.meta); var nodes = []; try { var sm = smparser.parse(node + '@' + shape)[0]; var added = typeof sm.node === "string" || "@value" in sm.node ? Promise.resolve({nodes: [node], shape: shape}) : Promise.resolve({nodes: getTriples(sm.node.subject, sm.node.predicate, sm.node.object), shape: shape}); return acc.concat(added); } catch (e) { // find which cell was broken try { smparser.parse(node + '@' + "START"); } catch (e) { $(queryPair).find(".focus").addClass("error"); } try { smparser.parse("<>" + '@' + shape); } catch (e) { $(queryPair).find(".inputShape").addClass("error"); } failMessage(e, "parsing Edit Map", node + '@' + shape); nodes = Promise.resolve([]); // skip this entry return acc; } }, []); Promise.all(nodeShapePromises).then(pairs => pairs.reduce((acc, pair) => { pair.nodes.forEach(node => { var nodeTerm = Caches.inputData.meta.lexToTerm(node + " "); // for langcode lookahead var shapeTerm = Caches.inputSchema.meta.lexToTerm(pair.shape); if (shapeTerm === ShEx.Validator.start) shapeTerm = START_SHAPE_INDEX_ENTRY; var key = nodeTerm + "|" + shapeTerm; if (key in acc) return; var spanElt = createEntry(node, nodeTerm, pair.shape, shapeTerm); acc[key] = spanElt; // just needs the key so far. }); return acc; }, {})).then(() => { // scroll inputs to right $("#fixedMap input").each((idx, focusElt) => { focusElt.scrollLeft = focusElt.scrollWidth; }); fixedMapTab.text(restoreText).removeClass("running"); }); function getTriples (s, p, o) { var get = s === ShEx.ShapeMap.focus ? "subject" : "object"; return Caches.inputData.refresh().getTriplesByIRI(mine(s), mine(p), mine(o)).map(t => { return Caches.inputData.meta.termToLex(t[get]); }); function mine (term) { return term === ShEx.ShapeMap.focus || term === ShEx.ShapeMap.wildcard ? null : term; } } function createEntry (node, nodeTerm, shape, shapeTerm) { var spanElt = $(" ", {class: "pair" ,"data-node": nodeTerm ,"data-shape": shapeTerm }); var focusElt = $("", { type: 'text', value: node, class: 'data focus', disabled: "disabled" }); var shapeElt = $("", { type: 'text', value: shape, class: 'schema inputShape', disabled: "disabled" }); var removeElt = $("", { class: "removePair", title: "remove this node/shape pair"}).text("-"); removeElt.on("click", evt => { // Remove related result. var href, result; if ((href = $(evt.target).closest("tr").find("a").attr("href")) && (result = document.getElementById(href.substr(1)))) $(result).remove(); // Remove FixedMap entry. $(evt.target).closest("tr").remove(); }); spanElt.append([focusElt, "@", shapeElt, removeElt, $("")].map(elt => { return $(" ").append(elt); })); $("#fixedMap").append(spanElt); return spanElt; } } function lexifyFirstColumn (row) { return Caches.inputData.meta.termToLex(row[0]); // row[0] is the first column. } function copyEditMapToTextMap () { if ($("#editMap").attr("data-dirty") === "true") { var text = $("#editMap .pair").get().reduce((acc, queryPair) => { var node = $(queryPair).find(".focus").val(); var shape = $(queryPair).find(".inputShape").val(); if (!node || !shape) return acc; return acc.concat([node+"@"+shape]); }, []).join(",\n"); $("#textMap").empty().val(text); copyEditMapToFixedMap(); markEditMapClean(); } } /** * Parse a supplied query map and build #editMap * @returns list of errors. ([] means everything was good.) */ function copyTextMapToEditMap () { $("#textMap").removeClass("error"); var shapeMap = $("#textMap").val(); try { Caches.inputSchema.refresh(); } catch (e) { } try { Caches.inputData.refresh(); } catch (e) { } try { var smparser = ShEx.ShapeMapParser.construct( Caches.shapeMap.meta.base, Caches.inputSchema.meta, Caches.inputData.meta); var sm = smparser.parse(shapeMap); removeEditMapPair(null); addEditMapPairs(sm.length ? sm : null); copyEditMapToFixedMap(); markEditMapClean(); } catch (e) { $("#textMap").addClass("error"); $("#fixedMap").empty(); failMessage(e, "parsing Query Map"); } return []; } function makeFreshEditMap () { removeEditMapPair(null); addEditMapPairs(null, null); markEditMapClean(); return []; } /** fixedShapeMapToTerms -- map ShapeMap to API terms * @@TODO: add to ShExValidator so API accepts ShapeMap */ function fixedShapeMapToTerms (shapeMap) { return shapeMap; /*.map(pair => { return {node: Caches.inputData.meta.lexToTerm(pair.node + " "), shape: Caches.inputSchema.meta.lexToTerm(pair.shape)}; });*/ } /** * Load URL search parameters */ function loadSearchParameters () { // don't overwrite if we arrived here from going back for forth in history if (Caches.inputSchema.selection.val() !== "" || Caches.inputData.selection.val() !== "") return; var iface = parseQueryString(location.search); toggleControlsArrow("down"); $(".manifest li").text("no manifest schemas loaded"); if ("examples" in iface) { // deprecated ?examples= interface iface.manifestURL = iface.examples; delete iface.examples; } if (!("manifest" in iface) && !("manifestURL" in iface)) { iface.manifestURL = ["../examples/manifest.json"]; } // Load all known query parameters. return Promise.all(QueryParams.reduce((promises, input) => { var parm = input.queryStringParm; if (parm + "URL" in iface) { var url = iface[parm + "URL"][0]; if (url.length > 0) { // manifest= loads no manifest // !!! set anyways in asyncGet? input.cache.url = url; // all fooURL query parms are caches. promises.push(input.cache.asyncGet(url).catch(function (e) { if ("fail" in input) { input.fail(e); } else { input.location.val(e.message); } results.append($("").text(e).addClass("error")); throw e })); } } else if (parm in iface) { var prepend = input.location.prop("tagName") === "TEXTAREA" ? input.location.val() : ""; var value = prepend + iface[parm].join(""); if ("cache" in input) // If it parses, make meta (prefixes, base) available. try { input.cache.set(value, location.href); } catch (e) { if ("fail" in input) { input.fail(e); } results.append($("").text( "error setting " + input.queryStringParm + ":\n" + e + "\n" + value ).addClass("error")); throw e } else { // Set HTML interface state. // A little insulation against improper values: let orig = input.location.val(); input.location.val(prepend + value); if (input.location.val() === null) { // invalid value so return to last value input.location.val(orig); } } } else if ("deflt" in input) { input.location.val(input.deflt); } return promises; }, [])).then(function (_) { // Parse the shape-map using the prefixes and base. var shapeMapErrors = $("#textMap").val().trim().length > 0 ? copyTextMapToEditMap() : makeFreshEditMap(); customizeInterface(); $("body").keydown(function (e) { // keydown because we need to preventDefault var code = e.keyCode || e.charCode; // standards anyone? if (e.ctrlKey && (code === 10 || code === 13)) { var at = $(":focus"); $("#validate").focus().click(); at.focus(); return false; // same as e.preventDefault(); } else { return true; } }); addContextMenus("#focus0", Caches.inputData); addContextMenus("#inputShape0", Caches.inputSchema); if ("schemaURL" in iface || // some schema is non-empty ("schema" in iface && iface.schema.reduce((r, elt) => { return r+elt.length; }, 0)) && shapeMapErrors.length === 0) { callValidator(); } }); } /** * update location with a current values of some inputs */ function getPermalink () { var parms = []; copyEditMapToTextMap(); parms = parms.concat(QueryParams.reduce((acc, input) => { var parm = input.queryStringParm; var val = input.location.val(); if (input.cache && input.cache.url && // Specifically avoid loading from DefaultBase?schema=blah // because that will load the HTML page. !input.cache.url.startsWith(DefaultBase)) { parm += "URL"; val = input.cache.url; } return val.length > 0 ? acc.concat(parm + "=" + encodeURIComponent(val)) : acc; }, [])); var s = parms.join("&"); return location.origin + location.pathname + "?" + s; } function customizeInterface () { if ($("#interface").val() === "minimal") { $("#inputSchema .status").html("schema (ShEx)").show(); $("#inputData .status").html("data (Turtle)").show(); $("#actions").parent().children().not("#actions").hide(); $("#title img, #title h1").hide(); $("#menuForm").css("position", "absolute").css( "left", $("#inputSchema .status").get(0).getBoundingClientRect().width - $("#menuForm").get(0).getBoundingClientRect().width ); $("#controls").css("position", "relative"); } else { $("#inputSchema .status").html("schema (ShEx)").hide(); $("#inputData .status").html("data (Turtle)").hide(); $("#actions").parent().children().not("#actions").show(); $("#title img, #title h1").show(); $("#menuForm").removeAttr("style"); $("#controls").css("position", "absolute"); } } /** * Prepare drag and drop into text areas */ function prepareDragAndDrop () { QueryParams.filter(q => { return "cache" in q; }).map(q => { return { location: q.location, targets: [{ ext: "", // Will match any file media: "", // or media type. target: q.cache }] }; }).concat([ {location: $("body"), targets: [ {media: "application/json", target: Caches.manifest}, {ext: ".shex", media: "text/shex", target: Caches.inputSchema}, {ext: ".ttl", media: "text/turtle", target: Caches.inputData}, {ext: ".json", media: "application/json", target: Caches.manifest}, {ext: ".smap", media: "text/plain", target: Caches.shapeMap}]} ]).forEach(desc => { var droparea = desc.location; // kudos to http://html5demos.com/dnd-upload desc.location. on("drag dragstart dragend dragover dragenter dragleave drop", function (e) { e.preventDefault(); e.stopPropagation(); }). on("dragover dragenter", (evt) => { desc.location.addClass("hover"); }). on("dragend dragleave drop", (evt) => { desc.location.removeClass("hover"); }). on("drop", (evt) => { evt.preventDefault(); droparea.removeClass("droppable"); $("#results .status").removeClass("error"); results.clear(); let xfer = evt.originalEvent.dataTransfer; const prefTypes = [ {type: "files"}, {type: "application/json"}, {type: "text/uri-list"}, {type: "text/plain"} ]; if (prefTypes.find(l => { if (l.type.indexOf("/") === -1) { if (xfer[l.type].length > 0) { $("#results .status").text("handling "+xfer[l.type].length+" files...").show(); readfiles(xfer[l.type], desc.targets); return true; } } else { if (xfer.getData(l.type)) { var val = xfer.getData(l.type); $("#results .status").text("handling "+l.type+"...").show(); if (l.type === "application/json") { if (desc.location.get(0) === $("body").get(0)) { var parsed = JSON.parse(val); if (!(parsed.constructor === Array)) { parsed = [parsed]; } parsed.map(elt => { var action = "action" in elt ? elt.action: elt; action.schemaURL = action.schema; delete action.schema; action.dataURL = action.data; delete action.data; }); Caches.manifest.set(parsed, DefaultBase, "drag and drop"); } else { inject(desc.targets, DefaultBase, val, l.type); } } else if (l.type === "text/uri-list") { $.ajax({ accepts: { mycustomtype: 'text/shex,text/turtle,*/*' }, url: val, dataType: "text" }).fail(function (jqXHR, textStatus) { var error = jqXHR.statusText === "OK" ? textStatus : jqXHR.statusText; results.append($("").text("GET <" + val + "> failed: " + error)); }).done(function (data, status, jqXhr) { try { inject(desc.targets, val, data, (jqXhr.getResponseHeader("Content-Type") || "unknown-media-type").split(/[ ;,]/)[0]); $("#loadForm").dialog("close"); toggleControls(); } catch (e) { results.append($("").text("unable to evaluate <" + val + ">: " + (e.stack || e))); } }); } else if (l.type === "text/plain") { inject(desc.targets, DefaultBase, val, l.type); } $("#results .status").text("").hide(); // desc.targets.text(xfer.getData(l.type)); return true; function inject (targets, url, data, mediaType) { var target = targets.length === 1 ? targets[0].target : targets.reduce((ret, elt) => { return ret ? ret : mediaType === elt.media ? elt.target : null; }, null); if (target) { var appendTo = $("#append").is(":checked") ? target.get() : ""; target.set(appendTo + data, url); } else { results.append("don't know what to do with " + mediaType + "\n"); } } } } return false; }) === undefined) results.append($("").text( "drag and drop not recognized:\n" + JSON.stringify({ dropEffect: xfer.dropEffect, effectAllowed: xfer.effectAllowed, files: xfer.files.length, items: [].slice.call(xfer.items).map(i => { return {kind: i.kind, type: i.type}; }) }, null, 2) )); }); }); function readfiles(files, targets) { var formData = new FormData(); var sucecesses = 0; for (var i = 0; i < files.length; i++) { var file = files[i], name = file.name; var target = targets.reduce((ret, elt) => { return ret ? ret : name.endsWith(elt.ext) ? elt.target : null; }, null); if (target) { formData.append("file", file); var reader = new FileReader(); reader.onload = (function (target) { return function (event) { var appendTo = $("#append").is(":checked") ? target.get() : ""; target.set(appendTo + event.target.result, DefaultBase); }; })(target); reader.readAsText(file); ++sucecesses; } else { results.append("don't know what to do with " + name + "\n"); } } $("#results .status").text("loaded "+sucecesses+" files.").show(); } } function prepareManifest (demoList, base) { var listItems = Object.keys(Caches).reduce((acc, k) => { acc[k] = {}; return acc; }, {}); var nesting = demoList.reduce(function (acc, elt) { var key = elt.schemaLabel + "|" + elt.schema; if (!(key in acc)) { // first entry with this schema acc[key] = { label: elt.schemaLabel, text: elt.schema, url: elt.schemaURL || (elt.schema ? base : undefined) }; } else { // nth entry with this schema } if ("dataLabel" in elt) { var dataEntry = { label: elt.dataLabel, text: elt.data, url: elt.dataURL || (elt.data ? base : undefined), entry: elt }; var target = elt.status === "nonconformant" ? "fails" : elt.status === "conformant" ? "passes" : "indeterminant"; if (!(target in acc[key])) { // first entry with this data acc[key][target] = [dataEntry]; } else { // n'th entry with this data acc[key][target].push(dataEntry); } } else { // this is a schema-only example } return acc; }, {}); var nestingAsList = Object.keys(nesting).map(e => nesting[e]); paintManifest("#inputSchema .manifest ul", nestingAsList, pickSchema, listItems, "inputSchema"); var timeouts = Object.keys(Caches).reduce((acc, k) => { acc[k] = undefined; return acc; }, {}); function later (target, side, cache) { cache.dirty(true); if (timeouts[side]) clearTimeout(timeouts[side]); timeouts[side] = setTimeout(() => { timeouts[side] = undefined; var curSum = sum($(target).val()); if (curSum in listItems[side]) listItems[side][curSum].addClass("selected"); else $("#"+side+" .selected").removeClass("selected"); delete cache.url; }, INPUTAREA_TIMEOUT); } Object.keys(Caches).forEach(function (cache) { Caches[cache].selection.keyup(function (e) { // keyup to capture backspace var code = e.keyCode || e.charCode; // if (!(e.ctrlKey)) { // results.clear(); // } if (!(e.ctrlKey && (code === 10 || code === 13))) { later(e.target, cache, Caches[cache]); } }); }); } function addContextMenus (inputSelector, cache) { // !!! terribly stateful; only one context menu at a time! var terms = null, nodeLex = null, target, scrollLeft, m, addSpace = ""; $.contextMenu({ selector: inputSelector, callback: function (key, options) { markEditMapDirty(); if (options.items[key].ignore) { // ignore the event } else if (terms) { var term = terms.tz[terms.match]; var val = nodeLex.substr(0, term[0]) + key + addSpace + nodeLex.substr(term[0] + term[1]); if (terms.match === 2 && !m[9]) val = val + "}"; else if (term[0] + term[1] === nodeLex.length) val = val + " "; $(options.selector).val(val); // target.scrollLeft = scrollLeft + val.length - nodeLex.length; target.scrollLeft = target.scrollWidth; } else { $(options.selector).val(key); } }, build: function (elt, evt) { if (elt.hasClass("data")) { nodeLex = elt.val(); var shapeLex = elt.parent().parent().find(".schema").val() // Would like to use SMParser but that means users can't fix bad SMs. // var sm = smparser.parse(nodeLex + '@START')[0]; // var m = typeof sm.node === "string" || "@value" in sm.node // ? null // : tpToM(sm.node); m = nodeLex.match(RegExp("^"+ParseTriplePattern+"$")); if (m) { target = evt.target; var selStart = target.selectionStart; scrollLeft = target.scrollLeft; terms = [0, 1, 2].reduce((acc, ord) => { if (m[(ord+1)*2-1] !== undefined) { var at = acc.start + m[(ord+1)*2-1].length; var len = m[(ord+1)*2] ? m[(ord+1)*2].length : 0; return { start: at + len, tz: acc.tz.concat([[at, len]]), match: acc.match === null && at + len >= selStart ? ord : acc.match }; } else { return acc; } }, {start: 0, tz: [], match: null }); function norm (tz) { return tz.map(t => { return t.startsWith('!') ? {name: "- " + t.substr(1) + " -", ignore: true} : {name: Caches.inputData.meta.termToLex(t)}; }); } const queryMapKeywords = [{name: "FOCUS"}, {name: "_"}]; const getTermsFunctions = [ () => { return queryMapKeywords.concat(norm(store.getSubjects())); }, () => { return norm(store.getPredicates()); }, () => { return queryMapKeywords.concat(norm(store.getObjects())); }, ]; var store = Caches.inputData.refresh(); var items = []; if (terms.match === null) return false; // prevent contextMenu from whining about an empty list items = getTermsFunctions[terms.match](); return { items: items.reduce((ret, opt) => { ret[opt.name] = opt; return ret; }, {}) }; } } terms = nodeLex = null; try { return { items: cache.getItems().reduce((ret, opt) => { ret[opt] = { name: opt }; return ret; }, {}) }; } catch (e) { failMessage(e, cache === Caches.inputSchema ? "parsing schema" : "parsing data"); let items = {}; const failContent = "no choices found"; items[failContent] = failContent; return { items: items } } // hack to emulate regex parsing product // function tpToM (tp) { // return [nodeLex, '{', lex(tp.subject), " ", lex(tp.predicate), " ", lex(tp.object), "", "}", ""]; // function lex (node) { // return node === ShEx.ShapeMap.focus // ? "FOCUS" // : node === null // ? "_" // : Caches.inputData.meta.termToLex(node); // } // } } }); } prepareControls(); prepareDragAndDrop(); loadSearchParameters().then( () => { if ('_testCallback' in window) { window._testCallback() } }).catch( e => { if ('_testCallback' in window) { window._testCallback(e) } } )