<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="shortcut icon" type="image/x-icon" href="http://sww.sas.com/favicon.ico" />
<title> SAS Studio Repository </title>
</head>
<script>
/**
* This allows a SAS Studio Repository to be loaded from another domain than SAS Studio.
* An one time id is generated by SAS Studio and then this proxy is called with the that id.
* If the content loads the content is passed back to the SAS Studio domain using the id and
* the window.postMessage() protocol. This ensure cross domain communication without having
* to modify the configuration of the hosting web server or container (this CORS).
*/
/* Resolve all URL parameters passed to the proxy. */
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi,
function (m, key, value) {
vars[key] = value;
});
return vars;
}
/* Resolve the current URL for any relative URLs contained in the repository. */
function getBaseUrl(url) {
var querypos = url.indexOf("?");
if (querypos > 0)
url = url.substring(0, querypos);
var parts = url.split("/");
var last = parts[parts.length - 1];
var pos = url.indexOf(last);
return url.substring(0, pos - 1);
}
/* Global parameters for the proxy. */
var objects = new Object();
var version = 7;
var taskCategories = null;
var snippetCategories = null;
var repositoryDocument = null;
var baseURL = getBaseUrl(window.location.href);
var parms = getUrlVars();
RegExp.escape = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
String.prototype.replaceAll = function (search, replace) {
return this.replace(new RegExp(RegExp.escape(search), 'g'), replace);
};
/* In addition to be the proxy the loader validates the XML integrity of the repository. */
/* Invalid XML is flagged in the loader page, but is not passed back to SAS Studio. */
function buildRepository(xmlhttp) {
try {
var s = new XMLSerializer();
parser = new DOMParser();
addNote("Starting validation of " + xmlhttp.responseURL);
repositoryDocument = parseXml(xmlhttp.responseText);
var categories = [];
/* Check for a top-level repository icon. If the URI is relative resolve it. */
try {
var repositoryIcon = repositoryDocument.getElementsByTagName("Icon")[0];
var iconUri = repositoryIcon.getAttribute("uri");
if (iconUri) {
if (iconUri.indexOf(".") == 0)
iconUri = baseURL + iconUri.substring(1);
//console.log(iconUri);
repositoryIcon.setAttribute("uri", iconUri);
var img = "<img width=16 height=16 src=\'" + iconUri + "'/>";
addNote("Repository icon is " + img);
}
} catch (e) {
// console.log("No repository icon element found.");
}
var documentation = null;
repositoryTags = repositoryDocument.getElementsByTagName("Repository");
if (repositoryTags.length == 1) {
documentation = new Array();
repositoryTag = repositoryTags[0];
for (i = 0; i < repositoryTag.childNodes.length; i++) {
if (repositoryTag.childNodes[i].nodeName == "Documentation")
documentation.push(repositoryTag.childNodes[i]);
}
// console.log(documentation);
}
/**
* See if the repository itself has documentation.
*/
if (documentation && documentation.length > 1) {
for (i = 0; i < documentation.length; i++) {
docUri = documentation[i].getAttribute("uri");
var docLabel = documentation[i].getAttribute("label");
if (!docLabel)
docLabel = docUri;
var docURL = docUri;
if (docURL.indexOf(".") == 0)
docURL = baseURL + docUri.substring(1);
var docLink = "<a target='_blank' href='" + docURL + "'>" + docLabel + "</a>";
addNote("A repository documentation link is located at " + docLink);
documentation[i].setAttribute("uri", docURL);
}
}
/* Validate all task category definitions. */
var tasks = repositoryDocument.getElementsByTagName("Tasks");
if (tasks.length == 1)
taskCategories = tasks[0].getElementsByTagName("Category");
if (taskCategories) {
var tasks = window.document.getElementById("tasks");
var html = "<h4>Task Categories:</h4><ul>";
for (i = 0; i < taskCategories.length; i++) {
html += validateCategory("Task", taskCategories[i]);
}
html += "</ul>";
tasks.innerHTML = html;
}
/* Validate all snippet category definitions. */
var snippets = repositoryDocument.getElementsByTagName("Snippets");
if (snippets.length == 1)
snippetCategories = snippets[0].getElementsByTagName("Category");
if (snippetCategories) {
var snippets = window.document.getElementById("snippets");
var html = "<h4>Snippet Categories:</h4><ul>";
for (i = 0; i < snippetCategories.length; i++) {
html += validateCategory("Snippet", snippetCategories[i]);
}
html += "</ul>";
snippets.innerHTML = html;
}
addNote("Task repository validation completed.");
/* Serialize the XML to make sure resolved URI are updated. */
processedXML = s.serializeToString(repositoryDocument);
objects["repository.xml"] = processedXML;
objects["event"] = "loaded";
objects["version"] = version;
objects["status"] = 200;
/* Pass payload back to SAS Studio. */
if (parms["target"]) {
objects.id = parms["id"];
objects.type = "proxy";
window.parent.postMessage(objects, parms["target"]);
}
} catch (e) {
addError("Fatal repository condition encountered.");
addError(e.toString());
}
}
/**
* Do all the tedious category validation in one place. For performance reasons we don't validate
* the category content itself. We leave to be done on the SAS Studio side.
*/
function validateCategory(type, node) {
var s = new XMLSerializer();
var label = node.getAttribute("label");
var location = node.getElementsByTagName("Location");
var uri = location[0].getAttribute("uri");
var resolvedURL = uri;
var validateURL = uri;
var iconUri = null;
var catLabel = "";
if (resolvedURL.indexOf(".") == 0)
resolvedURL = baseURL + uri.substring(1)
location[0].setAttribute("uri", resolvedURL);
var link = "<a target='_blank' href='" + resolvedURL + "'>" + resolvedURL + "</a>";
var html = "<li>" + label + " - " + link + "</li>";
addNote("Validating '" + label + "' " + type + " category.");
catLabel = label;
/**
* See if the category itself has documentation.
*/
var documentation = node.getElementsByTagName("Documentation");
if (documentation && documentation.length >= 1) {
for (var i = 0; i < documentation.length; i++) {
docUri = documentation[i].getAttribute("uri");
var docLabel = documentation[i].getAttribute("label");
if (!docLabel)
docLabel = docUri;
var docURL = docUri;
if (docURL.indexOf(".") == 0)
docURL = baseURL + docUri.substring(1);
var docLink = "<a target='_blank' href='" + docURL + "'>" + docLabel + "</a>";
addNote("A category documentation link is located at " + docLink);
documentation[i].setAttribute("uri", docURL);
}
}
var xhr = new XMLHttpRequest();
var taskBase = getBaseUrl(resolvedURL);
xhr.onreadystatechange = function () {
try {
if (xhr.readyState == 4 && xhr.status == 200) {
try {
doc = parseXml(xhr.responseText);
} catch (e) {
addError(resolvedURL + " failed to parse as XML.");
addError(e);
addWarning("Category '" + label + "' will be ignored.");
return html;
}
var tags = doc.getElementsByTagName(type);
addNote("'" + label + "' " + type + " category contains " + tags.length + " items.");
for (j = 0; j < tags.length; j++) {
var tag = tags[j];
if (tag.getElementsByTagName("Name").length == 0) {
addError("Required tag \"<b>Name</b>\" missing from <a href='" + validateURL + "'>" + validateURL + "</a>");
addError(type + " after \"<b>" + label + "</b>\" is invalid.");
addWarning("Entire category \"<b>" + catLabel + "</b>\" will be discarded.");
return html;
}
if (tag.getElementsByTagName("Template").length == 0) {
addError("Required tag \"<b>Template</b>\" missing from <a href='" + validateURL + "'>" + validateURL + "</a>");
addError(type + " after \"<b>" + label + "</b>\" is invalid.");
addWarning("Entire category \"<b>" + catLabel + "</b>\" will be discarded.");
return html;
}
nameTag = tag.getElementsByTagName("Name")[0];
templateTag = tag.getElementsByTagName("Template")[0];
label = nameTag.getAttribute("label");
templateUri = templateTag.getAttribute("uri");
iconUri = null;
var icon = tag.getElementsByTagName("Icon");
if (icon && icon.length == 1) {
iconTag = tag.getElementsByTagName("Icon")[0];
iconUri = iconTag.getAttribute("uri");
if (iconUri) {
if (iconUri.indexOf(".") == 0)
iconUri = taskBase + iconUri.substring(1);
iconTag.setAttribute("uri", iconUri);
}
}
var documentation = tag.getElementsByTagName("Documentation");
if (documentation && documentation.length >= 1) {
for (var i = 0; i < documentation.length; i++) {
docUri = documentation[i].getAttribute("uri");
var docLabel = documentation[i].getAttribute("label");
if (!docLabel)
docLabel = docUri;
var docURL = docUri;
if (docURL.indexOf(".") == 0)
docURL = taskBase + docUri.substring(1);
var docLink = "<a target='_blank' href='" + docURL + "'>" + docLabel + "</a>";
addNote("A '" + label + "' documentation link is located at " + docLink);
documentation[i].setAttribute("uri", docURL);
}
}
if (templateUri.indexOf(".") == 0)
templateUri = taskBase + templateUri.substring(1);
templateTag.setAttribute("uri", templateUri);
var note = "'" + label + "' contents is located at <a target='_blank' + href='" + templateUri + "'>" + templateUri + "</a>";
addNote(note);
if (iconUri) {
var img = "<img width=16 height=16 src=\'" + iconUri + "'/>";
addNote(type + " icon for " + label + " is " + img);
}
}
processedXML = s.serializeToString(doc);
objects[resolvedURL] = processedXML;
} else if (xhr.readyState == 4 && xhr.status == 404) {
addWarning(resolvedURL + " not found.");
xhr.abort();
}
} catch (e) {
console.log(e);
}
}
validateURL = resolvedURL;
xhr.open("GET", resolvedURL, false);
try {
xhr.send();
} catch (e) {
addWarning(e);
}
return html;
}
/**
* Format a validation note.
*/
function addNote(note) {
message = window.document.getElementById("message");
var html = "<div style='color:blue'>NOTE: " + note + "</div>";
message.innerHTML += html;
}
/**
* Format a validation warning.
*/
function addWarning(warning) {
message = window.document.getElementById("message");
var html = "<div style='color:green'>WARNING: " + warning + "</div>";
message.innerHTML += html;
}
/*
* Format a validation error.
*/
function addError(error) {
message = window.document.getElementById("message");
var html = "<div style='color:red'>ERROR: " + error + "</div>";
message.innerHTML += html;
}
/*
* Utility function for parsing some XML.
*/
function parseXml(xmlString) {
var parser = new DOMParser();
var dom = parser.parseFromString(xmlString, 'text/xml');
errors = dom.getElementsByTagName("parsererror");
if (errors.length > 0)
throw new Error(errors[0].innerHTML);
return dom;
}
/*
* The proxy sends and receives. The receive sends back an "echo".
* Receive sends back and "echo".
*/
function receiveData(event) {
event.data.type = "echo";
event.data.domain = window.domain;
event.source.postMessage(event.data, event.origin);
}
/*
* Proxy main equivalent.
*/
function onLoad() {
try {
if (window.addEventListener)
window.addEventListener("message", receiveData, false);
else
if (window.attachEvent)
window.attachEvent("onmessage", receiveData);
var root = false;
var resourceRoot = baseURL;
var div = window.document.getElementById("url");
div.innerHTML = "<li>" + baseURL + "</li>";
/**
* target is the generated ID for proxy loading - SAS Studio generates this.
*/
if (parms["target"]) {
/**
* Send back an event that the proxy was loaded and started processing.
*/
var object = new Object();
object.event = "onloadstart";
object.status = 200;
object.type = "proxy";
object.version = version;
object.id = parms["id"];
//console.log(object);
window.parent.postMessage(object, parms["target"]);
if (parms["noload"]) return;
}
/**
* If no uri is passed to the proxy, then the repository itself is loaded.
* If a uri is passed we are loading repository content.
*/
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
/* Successful load. */
try {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
if (root) {
buildRepository(xmlhttp);
} else {
var object = new Object();
object.event = "loaded";
object.status = 200;
object.type = "proxy";
object.version = version;
object.id = parms["id"];
//console.log(xmlhttp.responseText);
//console.log(object);
//console.log(baseURL);
var content = xmlhttp.responseText;
try {
content = content.replaceAll("{baseURL}", resourceRoot);
} catch (e) {
console.log("Error substituting {baseURL}.");
}
object.content = content;
window.parent.postMessage(object, parms["target"]);
}
}
/* Not so successful. */
else if (xmlhttp.readyState == 4 && xmlhttp.status == 404) {
//console.log( xmlhttp.responseURL + " (" + xmlhttp.statusText + ")" );
var object = new Object();
object.event = "error";
object.version = version;
object.status = xmlhttp.status;
object.message = xmlhttp.statusText;
object.url = xmlhttp.responseURL;
object.id = parms["id"];
object.type = "proxy";
console.log(object);
xmlhttp.abort();
window.parent.postMessage(object, parms["target"]);
}
} catch (e) {
console.log(e);
}
}
/* No URI - load repository itself. */
/* A URI specified - load content repository. */
uri = parms["uri"];
if (!uri) {
root = true;
uri = "./repository.xml";
} else {
resourceRoot = getBaseUrl(uri);
if (parms["target"])
uri += "?id=" + parms["id"];
//console.log(uri);
}
xmlhttp.open("GET", uri);
xmlhttp.send();
} catch (e) {
console.log(e);
}
}
</script>
<body onload="try { onLoad(); } catch (e) { console.log(e); }">
<h2>SAS Studio 3.5 Repository</h2>
<p>
Proxy loader and repository validation page.
<p>
<h4>Access URL:</h4>
<p>
<ul>
<div id="url"></div>
</ul>
<p>
<div id="tasks"></div>
<p>
<div id="snippets"></div>
<p>
<pre>
<div id="message"></div>
</pre>
</body>
</html>