--- a/playground/jsonld.js Mon Oct 24 15:16:52 2011 +0800
+++ b/playground/jsonld.js Thu Oct 27 15:02:49 2011 -0400
@@ -378,6 +378,12 @@
tmp = [input];
}
+ // merge context if it is an array
+ if(ctx.constructor === Array)
+ {
+ ctx = jsonld.mergeContexts({}, ctx);
+ }
+
for(var i in tmp)
{
// setup output context
@@ -416,50 +422,67 @@
*/
jsonld.mergeContexts = function(ctx1, ctx2)
{
+ // merge first context if it is an array
+ if(ctx1.constructor === Array)
+ {
+ ctx1 = jsonld.mergeContexts({}, ctx1);
+ }
+
// copy context to merged output
var merged = _clone(ctx1);
-
- // if the new context contains any IRIs that are in the merged context,
- // remove them from the merged context, they will be overwritten
- for(var key in ctx2)
+
+ if(ctx2.constructor === Array)
{
- // ignore special keys starting with '@'
- if(key.indexOf('@') !== 0)
+ // merge array of contexts in order
+ for(var i in ctx2)
{
- for(var mkey in merged)
+ merged = jsonld.mergeContexts(merged, ctx2[i]);
+ }
+ }
+ else
+ {
+ // if the new context contains any IRIs that are in the merged context,
+ // remove them from the merged context, they will be overwritten
+ for(var key in ctx2)
+ {
+ // ignore special keys starting with '@'
+ if(key.indexOf('@') !== 0)
{
- if(merged[mkey] === ctx2[key])
+ for(var mkey in merged)
{
- // FIXME: update related @coerce rules
- delete merged[mkey];
- break;
+ if(merged[mkey] === ctx2[key])
+ {
+ // FIXME: update related @coerce rules
+ delete merged[mkey];
+ break;
+ }
}
}
}
- }
-
- // merge contexts
- for(var key in ctx2)
- {
- // skip @coerce, to be merged below
- if(key !== '@coerce')
- {
- merged[key] = _clone(ctx2[key]);
- }
- }
- // merge @coerce
- if('@coerce' in ctx2)
- {
- if(!('@coerce' in merged))
+ // merge contexts
+ for(var key in ctx2)
{
- merged['@coerce'] = _clone(ctx2['@coerce']);
+ // skip @coerce, to be merged below
+ if(key !== '@coerce')
+ {
+ merged[key] = _clone(ctx2[key]);
+ }
}
- else
+
+ // merge @coerce
+ if('@coerce' in ctx2)
{
- for(var key in ctx2['@coerce'])
+ if(!('@coerce' in merged))
{
- merged['@coerce'][key] = ctx2['@coerce'][key];
+ merged['@coerce'] = _clone(ctx2['@coerce']);
+ }
+ else
+ {
+ for(var key in ctx2['@coerce'])
+ {
+ merged['@coerce'][key] = ctx2['@coerce'][key];
+ }
}
}
}
@@ -569,7 +592,7 @@
break;
}
}
- }
+ }m
if(quit)
{
break;
@@ -579,6 +602,130 @@
return rval;
};
+/**
+ * Resolves external @context URLs. Every @context URL in the given JSON-LD
+ * object is resolved using the given URL-resolver function. Once all of
+ * the @contexts have been resolved, the given result callback is invoked.
+ *
+ * @param input the JSON-LD input object (or array).
+ * @param resolver the resolver method that takes a URL and a callback that
+ * receives a JSON-LD serialized @context or null on error (with
+ * optional an error object as the second parameter).
+ * @param callback the callback to be invoked with the fully-resolved
+ * JSON-LD output (object or array) or null on error (with an
+ * optional error array as the second parameter).
+ */
+jsonld.resolve = function(input, resolver, callback)
+{
+ // find all @context URLs
+ var urls = {};
+ var findUrls = function(input, replace)
+ {
+ if(input.constructor === Array)
+ {
+ for(var i in input)
+ {
+ findUrls(input[i]);
+ }
+ }
+ else if(input.constructor === Object)
+ {
+ for(var key in input)
+ {
+ if(key === '@context')
+ {
+ // @context is an array that might contain URLs
+ if(input[key].constructor === Array)
+ {
+ var list = input[key];
+ for(var i in list)
+ {
+ if(list[i].constructor === String)
+ {
+ // replace w/resolved @context if appropriate
+ if(replace)
+ {
+ list[i] = urls[list[i]];
+ }
+ // unresolved @context found
+ else
+ {
+ urls[list[i]] = {};
+ }
+ }
+ }
+ }
+ else if(input[key].constructor === String)
+ {
+ // replace w/resolved @context if appropriate
+ if(replace)
+ {
+ input[key] = urls[input[key]];
+ }
+ // unresolved @context found
+ else
+ {
+ urls[input[key]] = {};
+ }
+ }
+ }
+ }
+ }
+ };
+ findUrls(input, false);
+
+ // state for resolving URLs
+ var count = Object.keys(urls).length;
+ var errors = null;
+
+ if(count === 0)
+ {
+ callback(input, errors);
+ }
+ else
+ {
+ // resolve all URLs
+ for(var url in urls)
+ {
+ resolver(url, function(result, error)
+ {
+ --count;
+
+ if(result === null)
+ {
+ errors = errors || [];
+ errors.push({ url: url, error: error });
+ }
+ else if(result.constructor === String)
+ {
+ try
+ {
+ urls[url] = JSON.parse(result);
+ }
+ catch(ex)
+ {
+ errors = errors || [];
+ errors.push({ url: url, error: ex });
+ }
+ }
+ else
+ {
+ urls[url] = result;
+ }
+
+ if(count === 0)
+ {
+ if(errors === null)
+ {
+ findUrls(input, true);
+ }
+ callback(input, errors);
+ }
+ });
+ }
+ }
+};
+
// TODO: organizational rewrite
/**
--- a/playground/playground.js Mon Oct 24 15:16:52 2011 +0800
+++ b/playground/playground.js Thu Oct 27 15:02:49 2011 -0400
@@ -3,6 +3,7 @@
* for Linked Data.
*
* @author Manu Sporny <msporny@digitalbazaar.com>
+ * @author Dave Longley
*/
(function($)
{
@@ -11,7 +12,7 @@
var playground = window.playground;
// set the active tab to the compacted view
- playground.activeTab = "tab-compacted";
+ playground.activeTab = 'tab-compacted';
// the counter is used to throttle colorization requests in milliseconds
playground.colorizeDelay = 500;
@@ -29,15 +30,14 @@
{
// replace each special HTML character in the string
return text.replace(/([&<>])/g, function (c) {
- return "&" + {
- "&": "amp",
- "<": "lt",
- ">": "gt"
- }[c] + ";";
+ return '&' + {
+ '&': 'amp',
+ '<': 'lt',
+ '>': 'gt'
+ }[c] + ';';
});
};
-
/**
* Get a query parameter by name.
*
@@ -57,7 +57,7 @@
/**
* Handle URL query parameters.
*
- * Checks "json-ld" and "frame" parameters. If they look like JSON then
+ * Checks 'json-ld' and 'frame' parameters. If they look like JSON then
* interpret as JSON strings else interpret as URLs of remote resources.
* Note: URLs must be CORS enabled to load due to browser same origin policy
* issues.
@@ -71,7 +71,7 @@
};
/**
- * Read a parameter as JSON or created an jQuery AJAX Deferred call
+ * Read a parameter as JSON or create an jQuery AJAX Deferred call
* to read the data.
*
* @param param a query parameter value.
@@ -85,11 +85,11 @@
// the ajax deferred or null
var rval = null;
- // check "json-ld" parameter
+ // check 'json-ld' parameter
if(param !== null)
{
hasQueryData = true;
- if(param.length == 0 || param[0] == "{" || param[0] == "[")
+ if(param.length == 0 || param[0] == '{' || param[0] == '[')
{
// param looks like JSON
queryData[fieldName] = param;
@@ -105,8 +105,8 @@
},
error: function(jqXHR, textStatus, errorThrown) {
// FIXME: better error handling
- $("#markup-errors")
- .text("Error loading " + msgName + " URL: " + param);
+ $('#resolve-errors')
+ .text('Error loading ' + msgName + ' URL: ' + param);
}
});
}
@@ -117,9 +117,9 @@
// build deferreds
var jsonLdDeferred = handleParameter(
- getParameterByName("json-ld"), "markup", "JSON-LD");
+ getParameterByName('json-ld'), 'markup', 'JSON-LD');
var frameDeferred = handleParameter(
- getParameterByName("frame"), "frame", "frame");
+ getParameterByName('frame'), 'frame', 'frame');
// wait for ajax if needed
// failures handled in AJAX calls
@@ -135,9 +135,9 @@
*/
playground.init = function()
{
- $("#tabs").tabs();
- $("#frame").hide();
- $("#tabs").bind("tabsselect", playground.tabSelected);
+ $('#tabs').tabs();
+ $('#frame').hide();
+ $('#tabs').bind('tabsselect', playground.tabSelected);
playground.processQueryParameters();
};
@@ -150,17 +150,17 @@
playground.tabSelected = function(event, ui)
{
playground.activeTab = ui.tab.id;
- if(ui.tab.id == "tab-framed")
+ if(ui.tab.id == 'tab-framed')
{
// if the 'frame' tab is selected, display the frame input textarea
- $("#markup").addClass("compressed");
- $("#frame").show();
+ $('#markup').addClass('compressed');
+ $('#frame').show();
}
else
{
// if the 'frame' tab is not selected, hide the frame input area
- $("#frame").hide();
- $("#markup").removeClass("compressed");
+ $('#frame').hide();
+ $('#markup').removeClass('compressed');
}
// perform processing on the data provided in the input boxes
@@ -169,6 +169,98 @@
// apply the syntax colorization
prettyPrint();
};
+
+ /**
+ * Resolves a JSON-LD @context url.
+ *
+ * @param url the url to resolve.
+ * @param callback the callback to call once the url has been resolved.
+ */
+ playground.resolveContext = function(url, callback)
+ {
+ var regex = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
+ if(!regex.test(url))
+ {
+ callback(null, 'Invalid URL');
+ }
+ else
+ {
+ // treat param as a URL
+ $.ajax({
+ url: url,
+ dataType: 'application/ld+json',
+ success: function(data, textStatus, jqXHR)
+ {
+ callback(data);
+ },
+ error: function(jqXHR, textStatus, errorThrown)
+ {
+ callback(null, errorThrown);
+ }
+ });
+ }
+ };
+
+ /**
+ * Performs the JSON-LD API action based on the active tab.
+ *
+ * @param input the JSON-LD object input or null no error.
+ * @param frame the JSON-LD frame to use.
+ */
+ playground.performAction = function(input, frame)
+ {
+ if(playground.activeTab == 'tab-normalized')
+ {
+ var normalized = jsonld.normalize(input);
+ $('#normalized').html(js_beautify(JSON.stringify(normalized)),
+ { 'indent_size': 3, 'brace_style': 'expand' });
+ }
+ else if(playground.activeTab == 'tab-expanded')
+ {
+ var expanded = jsonld.expand(input);
+ $('#expanded').html(js_beautify(JSON.stringify(expanded)),
+ { 'indent_size': 3, 'brace_style': 'expand' });
+ }
+ else if(playground.activeTab == 'tab-compacted')
+ {
+ var compacted = jsonld.compact(
+ input['@context'] || {}, input);
+ $('#compacted').html(js_beautify(JSON.stringify(compacted)),
+ { 'indent_size': 3, 'brace_style': 'expand' });
+ }
+ else if(playground.activeTab == 'tab-framed')
+ {
+ var framed = jsonld.frame(input, frame);
+ $('#framed').html(js_beautify(JSON.stringify(framed)),
+ { 'indent_size': 3, 'brace_style': 'expand' });
+ }
+ else if(playground.activeTab == 'tab-turtle')
+ {
+ var turtle = jsonld.turtle(input);
+ $('#turtle').html(playground.htmlEscape(turtle));
+ }
+
+ // generate a link for current data
+ var link = '?json-ld=' + encodeURIComponent(JSON.stringify(input));
+ if($('#frame').val().length > 0)
+ {
+ link += '&frame=' + encodeURIComponent(JSON.stringify(frame));
+ }
+ var permalink = '<a href="' + link + '">permalink</a>';
+ // size warning for huge links
+ if((window.location.protocol.length + 2 +
+ window.location.host.length + window.location.pathname.length +
+ link.length) > 2048)
+ {
+ permalink += ' (2KB+)';
+ }
+ $('#permalink')
+ .html(permalink)
+ .show();
+
+ // start the colorization delay
+ playground.checkColorizeDelay(true);
+ };
/**
* Process the JSON-LD markup that has been input and display the output
@@ -176,94 +268,64 @@
*/
playground.process = function()
{
- var input = null;
- var frame = null;
+ $('#markup-errors').text('');
+ $('#frame-errors').text('');
+ $('#resolve-errors').text('');
var errors = false;
// check to see if the JSON-LD markup is valid JSON
try
{
- $("#markup-errors").text("");
- input = JSON.parse($("#markup").val());
+ var input = JSON.parse($('#markup').val());
}
catch(e)
{
- $("#markup-errors").text("JSON markup - " + e);
+ $('#markup-errors').text('JSON markup - ' + e);
errors = true;
}
// check to see if the JSON-LD frame is valid JSON
try
{
- $("#frame-errors").text("");
- frame = JSON.parse($("#frame").val());
+ var frame = JSON.parse($('#frame').val());
}
catch(e)
{
- $("#frame-errors").text("JSON-LD frame - " + e);
+ $('#frame-errors').text('JSON-LD frame - ' + e);
errors = true;
}
- // if there are no errors, perform the action and display the output
- if(!errors)
+ // errors detected
+ if(errors)
{
- if(playground.activeTab == "tab-normalized")
- {
- var normalized = jsonld.normalize(input);
- $("#normalized").html(js_beautify(JSON.stringify(normalized)),
- { "indent_size": 3, "brace_style": "expand" });
- }
- else if(playground.activeTab == "tab-expanded")
- {
- var expanded = jsonld.expand(input);
- $("#expanded").html(js_beautify(JSON.stringify(expanded)),
- { "indent_size": 3, "brace_style": "expand" });
- }
- else if(playground.activeTab == "tab-compacted")
- {
- var compacted = jsonld.compact(
- input["@context"] || {}, input);
- $("#compacted").html(js_beautify(JSON.stringify(compacted)),
- { "indent_size": 3, "brace_style": "expand" });
- }
- else if(playground.activeTab == "tab-framed")
- {
- var framed = jsonld.frame(input, frame);
- $("#framed").html(js_beautify(JSON.stringify(framed)),
- { "indent_size": 3, "brace_style": "expand" });
- }
- else if(playground.activeTab == "tab-turtle")
- {
- var turtle = jsonld.turtle(input);
- $("#turtle").html(playground.htmlEscape(turtle));
- }
-
- // generate a link for current data
- var link = "?json-ld=" + encodeURIComponent(JSON.stringify(input));
- if($("#frame").val().length > 0)
- {
- link += "&frame=" + encodeURIComponent(JSON.stringify(frame));
- }
- var permalink = '<a href="' + link + '">permalink</a>';
- // size warning for huge links
- if((window.location.protocol.length + 2 +
- window.location.host.length + window.location.pathname.length +
- link.length) > 2048)
- {
- permalink += " (2KB+)"
- }
- $("#permalink")
- .html(permalink)
- .show();
+ $('#permalink').hide();
+
+ // start the colorization delay
+ playground.checkColorizeDelay(true);
}
+ // no errors, perform the action and display the output
else
{
- $("#permalink").hide();
+ // resolve external @context URLs and perform action
+ jsonld.resolve(
+ input,
+ playground.resolveContext,
+ function(input, errors)
+ {
+ if(errors)
+ {
+ // FIXME: better error handling
+ $('#resolve-errors').text(
+ 'Could not load @context URL: "' +
+ errors[0].url + '", ' + errors[0].error);
+ }
+ else
+ {
+ playground.performAction(input, frame);
+ }
+ });
}
-
- // Start the colorization delay
- playground.checkColorizeDelay(true);
- }
+ };
/**
* Performs a check on the colorize delay. If the delay hits 0, the
@@ -315,22 +377,22 @@
{
hasData = true;
// fill the markup box with the example
- $("#markup").val(js_beautify(
+ $('#markup').val(js_beautify(
data.markup,
- { "indent_size": 3, "brace_style": "expand" }));
+ { 'indent_size': 3, 'brace_style': 'expand' }));
}
if('frame' in data && data.frame !== null)
{
hasData = true;
// fill the frame input box with the example frame
- $("#frame").val(js_beautify(
+ $('#frame').val(js_beautify(
data.frame,
- { "indent_size": 3, "brace_style": "expand" }));
+ { 'indent_size': 3, 'brace_style': 'expand' }));
}
else
{
- $("#frame").val("{}");
+ $('#frame').val('{}');
}
if(hasData)
@@ -370,6 +432,28 @@
// populate with the example
playground.populateWithJSON(data);
};
-
+
+ // event handlers
+ $(document).ready(function()
+ {
+ // set up buttons to load examples
+ $('.button').each(function(idx)
+ {
+ var button = $(this);
+ button.click(function()
+ {
+ playground.populateWithExample(button.find('span').text());
+ });
+ });
+
+ // set up 'process' areas to process JSON-LD after typing
+ var processTimer = null;
+ $('.process').keyup(function()
+ {
+ clearTimeout(processTimer);
+ processTimer = setTimeout(playground.process, 500);
+ });
+ });
+
})(jQuery);