https://github.com/shexSpec/shex.js
Tip revision: e0072d7d1842b53234aff90dea73d2f7abf5a410 authored by Eric Prud'hommeaux on 29 September 2018, 07:05:24 UTC
Merge branch 'shex-next'
Merge branch 'shex-next'
Tip revision: e0072d7
shex-simple.js
// 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);
}
// <n3.js-specific>
function rdflib_termToLex (node, resolver) {
if (node === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
return "a";
if (node === ShEx.Validator.start)
return START_SHAPE_LABEL;
if (node === resolver._base)
return "<>";
if (node.indexOf(resolver._base) === 0/* &&
['#', '?'].indexOf(node.substr(resolver._base.length)) !== -1 */)
return "<" + node.substr(resolver._base.length) + ">";
if (node.indexOf(resolver._basePath) === 0 &&
['#', '?', '/', '\\'].indexOf(node.substr(resolver._basePath.length)) === -1)
return "<" + node.substr(resolver._basePath.length) + ">";
return ShEx.N3.Writer({ prefixes:resolver.meta.prefixes || {} })._encodeObject(node);
}
function rdflib_lexToTerm (lex, resolver) {
return lex === START_SHAPE_LABEL ? ShEx.Validator.start :
lex === "a" ? "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" :
ShEx.N3.Lexer().tokenize(lex + " ") // need " " to parse "chat"@en
.map(token => {
var left =
token.type === "typeIRI" ? "^^" :
token.type === "langcode" ? "@" :
token.type === "type" ? "^^" + resolver.meta.prefixes[token.prefix] :
token.type === "prefixed" ? resolver.meta.prefixes[token.prefix] :
token.type === "blank" ? "_:" :
"";
var right = token.type === "IRI" || token.type === "typeIRI" ?
resolver._resolveAbsoluteIRI(token) :
token.value;
return left + right;
}).join("");
return lex === ShEx.Validator.start ? lex : lex[0] === "<" ? lex.substr(1, lex.length - 2) : lex;
}
// </n3.js-specific>
// caches for textarea parsers
function _makeCache (selection) {
var _dirty = true;
var resolver;
var ret = {
selection: selection,
parsed: null,
meta: { prefixes: {}, base: DefaultBase },
dirty: function (newVal) {
var ret = _dirty;
_dirty = newVal;
return ret;
},
get: function () {
return selection.val();
},
set: function (text, base) {
_dirty = true;
selection.val(text);
this.meta.base = base;
if (base !== DefaultBase) {
this.url = base; // @@crappyHack1 -- parms should differntiate:
// working base: base for URL resolution.
// loaded base: place where you can GET current doc.
// Note that Caches.manifest.set takes a 3rd parm.
}
},
refresh: function () {
if (!_dirty)
return this.parsed;
this.parsed = this.parse(selection.val(), this.meta.base);
resolver._setBase(this.meta.base);
_dirty = false;
return this.parsed;
},
asyncGet: function (url) {
var _cache = this;
return new Promise(function (resolve, reject) {
$.ajax({
accepts: {
mycustomtype: 'text/shex,text/turtle,*/*'
},
url: url,
cache: false, // force reload
dataType: "text"
}).fail(function (jqXHR, textStatus) {
var error = jqXHR.statusText === "OK" ? textStatus : jqXHR.statusText;
reject(Error("GET <" + url + "> failed: " + error));
}).done(function (data) {
try {
_cache.meta.base = url;
resolver._setBase(url);
_cache.set(data, url);
$("#loadForm").dialog("close");
toggleControls();
resolve({ url: url, data: data });
} catch (e) {
reject(Error("unable to " + (e.action || "evaluate") + " <" + url + ">: " + '\n' + e.message));
}
});
});
},
url: undefined // only set if inputarea caches some web resource.
};
resolver = new IRIResolver(ret.meta);
ret.meta.termToLex = function (trm) { return rdflib_termToLex(trm, resolver); };
ret.meta.lexToTerm = function (lex) { return rdflib_lexToTerm(lex, resolver); };
return ret;
}
function makeSchemaCache (selection) {
var ret = _makeCache(selection);
var graph = null;
ret.language = null;
ret.parse = function (text, base) {
var isJSON = text.match(/^\s*\{/);
graph = isJSON ? null : tryN3(text);
this.language =
isJSON ? "ShExJ" :
graph ? "ShExR" :
"ShExC";
$("#results .status").text("parsing "+this.language+" schema...").show();
var schema =
isJSON ? ShEx.Util.ShExJtoAS(JSON.parse(text)) :
graph ? parseShExR() :
parseShEx(text, ret.meta, base);
$("#results .status").hide();
return schema;
function tryN3 (text) {
try {
if (text.match(/^\s*$/))
return null;
var db = parseTurtle (text, ret.meta, DefaultBase); // interpret empty schema as ShExC
if (db.getTriples().length === 0)
return null;
return db;
} catch (e) {
return null;
}
}
function parseShExR () {
var graphParser = ShEx.Validator.construct(
parseShEx(ShExRSchema, {}, base), // !! do something useful with the meta parm (prefixes and base)
{}
);
var schemaRoot = graph.getTriples(null, ShEx.Util.RDF.type, "http://www.w3.org/ns/shex#Schema")[0].subject;
var val = graphParser.validate(ShEx.Util.makeN3DB(graph), schemaRoot); // start shape
return ShEx.Util.ShExJtoAS(ShEx.Util.ShExRtoShExJ(ShEx.Util.valuesToSchema(ShEx.Util.valToValues(val))));
}
};
ret.getItems = function () {
var obj = this.refresh();
var start = "start" in obj ? [START_SHAPE_LABEL] : [];
var rest = "shapes" in obj ? Object.keys(obj.shapes).map(Caches.inputSchema.meta.termToLex) : [];
return start.concat(rest);
};
return ret;
}
function makeTurtleCache (selection) {
var ret = _makeCache(selection);
ret.parse = function (text, base) {
return ShEx.Util.makeN3DB(parseTurtle(text, ret.meta, base));
};
ret.getItems = function () {
var data = this.refresh();
return data.getTriplesByIRI().map(t => {
return Caches.inputData.meta.termToLex(t.subject);
});
};
return ret;
}
function makeManifestCache (selection) {
var ret = _makeCache(selection);
ret.set = function (textOrObj, url, source) {
$("#inputSchema .manifest li").remove();
$("#inputData .passes li, #inputData .fails li").remove();
if (typeof textOrObj !== "object") {
try {
// exceptions pass through to caller (asyncGet)
textOrObj = JSON.parse(textOrObj);
} catch (e) {
$("#inputSchema .manifest").append($("<li/>").text(NO_MANIFEST_LOADED));
var throwMe = Error(e + '\n' + textOrObj);
throwMe.action = 'load manifest'
throw throwMe
// @@DELME(2017-12-29)
// transform deprecated examples.js structure
// textOrObj = eval(textOrObj).reduce(function (acc, schema) {
// function x (data, status) {
// return {
// schemaLabel: schema.name,
// schema: schema.schema,
// dataLabel: data.name,
// data: data.data,
// queryMap: data.queryMap,
// status: status
// };
// }
// return acc.concat(
// schema.passes.map(data => x(data, "conformant")),
// schema.fails.map(data => x(data, "nonconformant"))
// );
// }, []);
}
}
if (textOrObj.constructor !== Array)
textOrObj = [textOrObj];
var demos = textOrObj.reduce((acc, elt) => {
if ("action" in elt) {
// compatibility with test suite structure.
var action = elt.action;
var schemaLabel = action.schemaURL.substr(action.schemaURL.lastIndexOf('/')+1);
var dataLabel = elt["@id"];
var match = null;
var emptyGraph = "-- empty graph --";
if ("comment" in elt) {
if ((match = elt.comment.match(/^(.*?) \/ { (.*?) }$/))) {
schemaLabel = match[1]; dataLabel = match[2] || emptyGraph;
} else if ((match = elt.comment.match(/^(.*?) on { (.*?) }$/))) {
schemaLabel = match[1]; dataLabel = match[2] || emptyGraph;
} else if ((match = elt.comment.match(/^(.*?) as { (.*?) }$/))) {
schemaLabel = match[2]; dataLabel = match[1] || emptyGraph;
}
}
var queryMap = "map" in action ?
null :
ldToTurtle(action.focus, Caches.inputData.meta.termToLex) + "@" + ("shape" in action ? ldToTurtle(action.shape, Caches.inputSchema.meta.termToLex) : START_SHAPE_LABEL);
var queryMapURL = "map" in action ?
action.map :
null;
elt = Object.assign(
{
schemaLabel: schemaLabel,
schema: action.schema,
schemaURL: action.schemaURL || url,
// dataLabel: "comment" in elt ? elt.comment : (queryMap || dataURL),
dataLabel: dataLabel,
data: action.data,
dataURL: action.dataURL || DefaultBase
},
(queryMap ? { queryMap: queryMap } : { queryMapURL: queryMapURL }),
{ status: elt["@type"] === "sht:ValidationFailure" ? "nonconformant" : "conformant" }
);
if ("termResolver" in action || "termResolverURL" in action) {
elt.meta = action.termResolver;
elt.metaURL = action.termResolverURL || DefaultBase;
}
}
["schemaURL", "dataURL", "queryMapURL"].forEach(parm => {
if (parm in elt) {
elt[parm] = new URL(elt[parm], new URL(url, DefaultBase).href).href;
} else {
delete elt[parm];
}
});
return acc.concat(elt);
}, []);
prepareManifest(demos, url);
$("#manifestDrop").show(); // may have been hidden if no manifest loaded.
};
ret.parse = function (text, base) {
throw Error("should not try to parse manifest cache");
};
ret.getItems = function () {
throw Error("should not try to get manifest cache items");
};
return ret;
function maybeGET(obj, base, key, accept) {
if (obj[key] != null) {
// Take the passed data, guess base if not provided.
if (!(key + "URL" in obj))
obj[key + "URL"] = base;
obj[key] = Promise.resolve(obj[key]);
} else if (key + "URL" in obj) {
// absolutize the URL
obj[key + "URL"] = ret.meta.lexToTerm("<"+obj[key + "URL"]+">");
// Load the remote resource.
obj[key] = new Promise((resolve, reject) => {
$.ajax({
accepts: {
mycustomtype: accept
},
url: ret.meta.lexToTerm("<"+obj[key + "URL"]+">"),
dataType: "text"
}).then(text => {
resolve(text);
}).fail(e => {
results.append($("<pre/>").text(
"Error " + e.status + " " + e.statusText + " on GET " + obj[key + "URL"]
).addClass("error"));
reject(e);
});
});
} else {
// Ignore this parameter.
obj[key] = Promise.resolve(obj[key]);
}
}
}
function ldToTurtle (ld, termToLex) {
return typeof ld === "object" ? lit(ld) : termToLex(ld);
function lit (o) {
let ret = "\""+o["@value"].replace(/["\r\n\t]/g, (c) => {
return {'"': "\\\"", "\r": "\\r", "\n": "\\n", "\t": "\\t"}[c];
}) +"\"";
if ("@type" in o)
ret += "^^<" + o["@type"] + ">";
if ("@language" in o)
ret += "@" + o["@language"];
return ret;
}
}
function makeShapeMapCache (selection) {
var ret = _makeCache(selection);
ret.parse = function (text) {
removeEditMapPair(null);
$("#textMap").val(text);
copyTextMapToEditMap();
copyEditMapToFixedMap();
};
// ret.parse = function (text, base) { };
ret.getItems = function () {
throw Error("should not try to get manifest cache items");
};
return ret;
}
// controls for manifest buttons
function paintManifest (selector, list, func, listItems, side) {
$(selector).empty();
list.forEach(entry => {
var button = $("<button/>").text("..." + entry.label.substr(3)).attr("disabled", "disabled");
var li = $("<li/>").append(button);
$(selector).append(li);
if (entry.text === undefined) {
fetchOK(entry.url).catch(responseOrError => {
// leave a message in the schema or data block
return "# " + renderErrorMessage(
responseOrError instanceof Error
? { url: entry.url, status: -1, statusText: responseOrError.message }
: responseOrError,
side);
}).then(schemaLoaded);
} else {
schemaLoaded(entry.text);
}
function schemaLoaded (text) {
entry.text = text;
li.on("click", () => {
func(entry.name, entry, li, listItems, side);
});
listItems[side][sum(text)] = li;
button.text(entry.label).removeAttr("disabled");
}
});
}
function fetchOK (url) {
return fetch(url).then(responseOrError => {
if (!responseOrError.ok) {
throw responseOrError;
}
return responseOrError.text()
});
}
function renderErrorMessage (response, what) {
var message = "failed to load " + "queryMap" + " from <" + response.url + ">, got: " + response.status + " " + response.statusText;
results.append($("<pre/>").text(message).addClass("error"));
return message;
}
function clearData () {
// Clear out data textarea.
Caches.inputData.set("", DefaultBase);
$("#inputData .status").text(" ");
// Clear out every form of ShapeMap.
$("#textMap").val("").removeClass("error");
makeFreshEditMap();
$("#fixedMap").empty();
results.clear();
}
function clearAll () {
$("#results .status").hide();
Caches.inputSchema.set("", DefaultBase);
$(".inputShape").val("");
$("#inputSchema .status").text(" ");
$("#inputSchema li.selected").removeClass("selected");
clearData();
$("#inputData .passes, #inputData .fails").hide();
$("#inputData .passes p:first").text("");
$("#inputData .fails p:first").text("");
$("#inputData .passes ul, #inputData .fails ul").empty();
}
function pickSchema (name, schemaTest, elt, listItems, side) {
if ($(elt).hasClass("selected")) {
clearAll();
} else {
Caches.inputSchema.set(schemaTest.text, new URL((schemaTest.url || ""), DefaultBase).href);
Caches.inputSchema.url = undefined; // @@ crappyHack1
$("#inputSchema .status").text(name);
clearData();
var headings = {
"passes": "Passing:",
"fails": "Failing:",
"indeterminant": "Data:"
};
Object.keys(headings).forEach(function (key) {
if (key in schemaTest) {
$("#inputData ." + key + "").show();
$("#inputData ." + key + " p:first").text(headings[key]);
paintManifest("#inputData ." + key + " ul", schemaTest[key], pickData, listItems, "inputData");
} else {
$("#inputData ." + key + " ul").empty();
}
});
$("#inputSchema li.selected").removeClass("selected");
$(elt).addClass("selected");
try {
Caches.inputSchema.refresh();
} catch (e) {
failMessage(e, "parsing schema");
}
}
}
function pickData (name, dataTest, elt, listItems, side) {
clearData();
if ($(elt).hasClass("selected")) {
$(elt).removeClass("selected");
} else {
// Update data pane.
Caches.inputData.set(dataTest.text, new URL((dataTest.url || ""), DefaultBase).href);
Caches.inputData.url = undefined; // @@ crappyHack1
$("#inputData .status").text(name);
$("#inputData li.selected").removeClass("selected");
$(elt).addClass("selected");
try {
Caches.inputData.refresh();
} catch (e) {
failMessage(e, "parsing data");
}
// Update ShapeMap pane.
removeEditMapPair(null);
if (dataTest.entry.queryMap === undefined) {
fetchOK(dataTest.entry.queryMapURL).then(queryMapLoaded).catch(response => {
renderErrorMessage(response, "queryMap");
});
} else {
queryMapLoaded(dataTest.entry.queryMap);
}
function queryMapLoaded (text) {
dataTest.entry.queryMap = text;
try {
$("#textMap").val(JSON.parse(dataTest.entry.queryMap).map(entry => `<${entry.node}>@<${entry.shape}>`).join(",\n"));
} catch (e) {
$("#textMap").val(dataTest.entry.queryMap);
}
copyTextMapToEditMap();
// callValidator();
}
}
}
// Control results area content.
var results = (function () {
var resultsElt = document.querySelector("#results div");
var resultsSel = $("#results div");
return {
replace: function (text) {
return resultsSel.text(text);
},
append: function (text) {
return resultsSel.append(text);
},
clear: function () {
resultsSel.removeClass("passes fails error");
$("#results .status").text("").hide();
$("#shapeMap-tabs").removeAttr("title");
return resultsSel.text("");
},
start: function () {
resultsSel.removeClass("passes fails error");
$("#results").addClass("running");
},
finish: function () {
$("#results").removeClass("running");
var height = resultsSel.height();
resultsSel.height(1);
resultsSel.animate({height:height}, 100);
},
text: function () {
return $(resultsElt).text();
}
};
})();
// Validation UI
function disableResultsAndValidate (evt, done) {
if (new Date().getTime() - LastFailTime < 100) {
results.append(
$("<div/>").addClass("warning").append(
$("<h2/>").text("see shape map errors above"),
$("<button/>").text("validate (ctl-enter)").on("click", disableResultsAndValidate),
" again to continue."
)
);
return; // return if < 100ms since last error.
}
results.clear();
results.start();
setTimeout(function () {
copyEditMapToTextMap(); // will update if #editMap is dirty
callValidator(done);
}, 0);
}
function hasFocusNode () {
return $(".focus").map((idx, elt) => {
return $(elt).val();
}).get().some(str => {
return str.length > 0;
});
}
function callValidator (done) {
$("#fixedMap .pair").removeClass("passes fails");
$("#results .status").hide();
var currentAction = "parsing input schema";
try {
noStack(() => { Caches.inputSchema.refresh(); });
$("#schemaDialect").text(Caches.inputSchema.language);
if (hasFocusNode()) {
currentAction = "parsing input data";
$("#results .status").text("parsing data...").show();
var inputData = Caches.inputData.refresh(); // need prefixes for ShapeMap
// $("#shapeMap-tabs").tabs("option", "active", 2); // select fixedMap
currentAction = "parsing shape map";
var fixedMap = fixedShapeMapToTerms($("#fixedMap tr").map((idx, tr) => {
return {
node: Caches.inputData.meta.lexToTerm($(tr).find("input.focus").val()),
shape: Caches.inputSchema.meta.lexToTerm($(tr).find("input.inputShape").val())
};
}).get());
currentAction = "creating validator";
$("#results .status").text("creating validator...").show();
// var dataURL = "data:text/json," +
// JSON.stringify(
// ShEx.Util.AStoShExJ(
// ShEx.Util.canonicalize(
// Caches.inputSchema.refresh())));
var alreadLoaded = {
schema: Caches.inputSchema.refresh(),
url: Caches.inputSchema.url || DefaultBase
};
ShEx.Loader.load([alreadLoaded], [], [], []).then(loaded => {
var time;
var validator = ShEx.Validator.construct(
loaded.schema,
{ results: "api", regexModule: ShEx[$("#regexpEngine").val()] });
currentAction = "validating";
$("#results .status").text("validating...").show();
time = new Date();
var ret = validator.validate(inputData, fixedMap, LOG_PROGRESS ? makeConsoleTracker() : null);
time = new Date() - time;
$("#shapeMap-tabs").attr("title", "last validation: " + time + " ms")
// var dated = Object.assign({ _when: new Date().toISOString() }, ret);
$("#results .status").text("rendering results...").show();
ret.forEach(renderEntry);
// for debugging values and schema formats:
// try {
// var x = ShExUtil.valToValues(ret);
// // var x = ShExUtil.ShExJtoAS(valuesToSchema(valToValues(ret)));
// res = results.replace(JSON.stringify(x, null, " "));
// var y = ShExUtil.valuesToSchema(x);
// res = results.append(JSON.stringify(y, null, " "));
// } catch (e) {
// console.dir(e);
// }
finishRendering();
if (done) { done() }
}).catch(function (e) {
$("#results .status").text("validation errors:").show();
failMessage(e, currentAction);
console.error(e); // dump details to console.
if (done) { done(e) }
});
} else {
var outputLanguage = Caches.inputSchema.language === "ShExJ" ? "ShExC" : "ShExJ";
$("#results .status").
text("parsed "+Caches.inputSchema.language+" schema, generated "+outputLanguage+" ").
append($("<button>(copy to input)</button>").
css("border-radius", ".5em").
on("click", function () {
Caches.inputSchema.set($("#results div").text(), DefaultBase);
})).
append(":").
show();
var parsedSchema;
if (Caches.inputSchema.language === "ShExJ") {
new ShEx.Writer({simplifyParentheses: false}).writeSchema(Caches.inputSchema.parsed, (error, text) => {
if (error) {
$("#results .status").text("unwritable ShExJ schema:\n" + error).show();
// res.addClass("error");
} else {
results.append($("<pre/>").text(text).addClass("passes"));
}
});
} else {
var pre = $("<pre/>");
pre.text(JSON.stringify(ShEx.Util.AStoShExJ(ShEx.Util.canonicalize(Caches.inputSchema.parsed)), null, " ")).addClass("passes");
results.append(pre);
}
results.finish();
if (done) { done() }
}
function noStack (f) {
try {
f();
} catch (e) {
// The Parser error stack is uninteresting.
delete e.stack;
throw e;
}
}
} catch (e) {
failMessage(e, currentAction);
console.error(e); // dump details to console.
if (done) { done(e) }
}
function makeConsoleTracker () {
function padding (depth) { return (new Array(depth + 1)).join(" "); } // AKA " ".repeat(depth)
function sm (node, shape) {
return `${Caches.inputData.meta.termToLex(node)}@${Caches.inputSchema.meta.termToLex(shape)}`;
}
var logger = {
recurse: x => { console.log(`${padding(logger.depth)}↻ ${sm(x.node, x.shape)}`); return x; },
known: x => { console.log(`${padding(logger.depth)}↵ ${sm(x.node, x.shape)}`); return x; },
enter: (point, label) => { console.log(`${padding(logger.depth)}→ ${sm(point, label)}`); ++logger.depth; },
exit: (point, label, ret) => { --logger.depth; console.log(`${padding(logger.depth)}← ${sm(point, label)}`); },
depth: 0
};
return logger;
}
function renderEntry (entry) {
var fails = entry.status === "nonconformant";
var klass = fails ? "fails" : "passes";
var resultStr = fails ? "✗" : "✓";
var elt = null;
switch ($("#interface").val()) {
case "human":
elt = $("<div class='human'/>").append(
$("<span/>").text(resultStr),
$("<span/>").text(
`${Caches.inputSchema.meta.termToLex(entry.node)}@${fails ? "!" : ""}${Caches.inputData.meta.termToLex(entry.shape)}`
)).addClass(klass);
if (fails)
elt.append($("<pre>").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 = $("<pre/>").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 = $("<div/>").addClass("error");
div.append($("<h3/>").text("error " + action + ":\n"));
div.append($("<pre/>").text(e.message));
if (text)
div.append($("<pre/>").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($("<div/>").append(
$("<span/>").text("unrecognized ShapeMap:"),
$("<pre/>").text(JSON.stringify(pair))
).addClass("error"));
skip = true; // skip this entry.
break;
}
if (!skip) {
var spanElt = $("<tr/>", {class: "pair"});
var focusElt = $("<textarea/>", {
rows: '1',
type: 'text',
class: 'data focus'
}).text(node).on("change", markEditMapDirty);
var shapeElt = $("<input/>", {
type: 'text',
value: shape,
class: 'schema inputShape'
}).on("change", markEditMapDirty);
var addElt = $("<button/>", {
class: "addPair",
title: "add a node/shape pair"}).text("+");
var removeElt = $("<button/>", {
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 $("<td/>").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 = $("<tr/>", {class: "pair"
,"data-node": nodeTerm
,"data-shape": shapeTerm
});
var focusElt = $("<input/>", {
type: 'text',
value: node,
class: 'data focus',
disabled: "disabled"
});
var shapeElt = $("<input/>", {
type: 'text',
value: shape,
class: 'schema inputShape',
disabled: "disabled"
});
var removeElt = $("<button/>", {
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, $("<a/>")].map(elt => {
return $("<td/>").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($("<pre/>").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($("<pre/>").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 (<span id=\"schemaDialect\">ShEx</span>)").show();
$("#inputData .status").html("data (<span id=\"dataDialect\">Turtle</span>)").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 (<span id=\"schemaDialect\">ShEx</span>)").hide();
$("#inputData .status").html("data (<span id=\"dataDialect\">Turtle</span>)").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($("<pre/>").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($("<pre/>").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($("<pre/>").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)
}
}
)