├── README ├── images ├── gmail.jpg ├── gmail.png ├── logout.png ├── webid.png ├── yahoo.png ├── facebook.gif ├── favicon.ico ├── browserid_blue.png ├── apple-touch-icon.png ├── apple-touch-icon-114x114.png └── apple-touch-icon-72x72.png ├── stylesheets ├── style.css ├── layout.css ├── skeleton.css └── base.css ├── javascripts ├── tabs.js ├── jsonld-turtle.js ├── app.js ├── sha1.js └── jsonld.js ├── index.html └── COPYING /README: -------------------------------------------------------------------------------- 1 | Simple app builder 2 | -------------------------------------------------------------------------------- /images/gmail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/gmail.jpg -------------------------------------------------------------------------------- /images/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/gmail.png -------------------------------------------------------------------------------- /images/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/logout.png -------------------------------------------------------------------------------- /images/webid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/webid.png -------------------------------------------------------------------------------- /images/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/yahoo.png -------------------------------------------------------------------------------- /images/facebook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/facebook.gif -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/favicon.ico -------------------------------------------------------------------------------- /images/browserid_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/browserid_blue.png -------------------------------------------------------------------------------- /images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/apple-touch-icon.png -------------------------------------------------------------------------------- /images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/appbuilder/master/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /stylesheets/style.css: -------------------------------------------------------------------------------- 1 | .hide { display: none; } 2 | .status { color: rgb(23, 136, 23) ; font-weight:bold; } 3 | .alpha { vertical-align: super; font-size: small; color: grey; } 4 | 5 | -------------------------------------------------------------------------------- /javascripts/tabs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | 11 | $(document).ready(function() { 12 | 13 | /* Tabs Activiation 14 | ================================================== */ 15 | 16 | var tabs = $('ul.tabs'); 17 | 18 | tabs.each(function(i) { 19 | 20 | //Get all tabs 21 | var tab = $(this).find('> li > a'); 22 | tab.click(function(e) { 23 | 24 | //Get Location of tab's content 25 | var contentLocation = $(this).attr('href'); 26 | 27 | //Let go if not a hashed one 28 | if(contentLocation.charAt(0)=="#") { 29 | 30 | e.preventDefault(); 31 | 32 | //Make Tab Active 33 | tab.removeClass('active'); 34 | $(this).addClass('active'); 35 | 36 | //Show Tab Content & add active class 37 | $(contentLocation).show().addClass('active').siblings().hide().removeClass('active'); 38 | 39 | } 40 | }); 41 | }); 42 | }); -------------------------------------------------------------------------------- /stylesheets/layout.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | /* Table of Content 11 | ================================================== 12 | #Site Styles 13 | #Page Styles 14 | #Media Queries 15 | #Font-Face */ 16 | 17 | /* #Site Styles 18 | ================================================== */ 19 | 20 | /* #Page Styles 21 | ================================================== */ 22 | 23 | /* #Media Queries 24 | ================================================== */ 25 | 26 | /* Smaller than standard 960 (devices and browsers) */ 27 | @media only screen and (max-width: 959px) {} 28 | 29 | /* Tablet Portrait size to standard 960 (devices and browsers) */ 30 | @media only screen and (min-width: 768px) and (max-width: 959px) {} 31 | 32 | /* All Mobile Sizes (devices and browser) */ 33 | @media only screen and (max-width: 767px) {} 34 | 35 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ 36 | @media only screen and (min-width: 480px) and (max-width: 767px) {} 37 | 38 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ 39 | @media only screen and (max-width: 479px) {} 40 | 41 | 42 | /* #Font-Face 43 | ================================================== */ 44 | /* This is the proper syntax for an @font-face file 45 | Just create a "fonts" folder at the root, 46 | copy your FontName into code below and remove 47 | comment brackets */ 48 | 49 | /* @font-face { 50 | font-family: 'FontName'; 51 | src: url('../fonts/FontName.eot'); 52 | src: url('../fonts/FontName.eot?iefix') format('eot'), 53 | url('../fonts/FontName.woff') format('woff'), 54 | url('../fonts/FontName.ttf') format('truetype'), 55 | url('../fonts/FontName.svg#webfontZam02nTh') format('svg'); 56 | font-weight: normal; 57 | font-style: normal; } 58 | */ -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 22 | 23 | 24 | App Builder 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
58 |

App Builder Alpha

59 | 60 |
Loading...
61 |
62 | 84 | 85 |
86 |  BrowserID
87 |


88 |
89 |
90 |  WebID 91 |
92 | 93 |
94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /javascripts/jsonld-turtle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript implementation of TURTLE output for JSON-LD Forge project. 3 | * 4 | * @author Manu Sporny 5 | * 6 | * Copyright (c) 2011-2012 Digital Bazaar, Inc. All rights reserved. 7 | */ 8 | (function() 9 | { 10 | 11 | /** 12 | * Retrieves all of the properties that are a part of a JSON-LD object, 13 | * ignoring the "@id" key. 14 | * 15 | * @param obj the JSON-LD object - the last part of the triple. 16 | * 17 | * @return an array of cleaned keys for the JSON-LD object. 18 | */ 19 | function getProperties(obj) 20 | { 21 | var rval = []; 22 | 23 | // accumulate the names of all non-JSON-LD subjects 24 | for(var key in obj) 25 | { 26 | if(key != "@id") 27 | { 28 | rval.push(key); 29 | } 30 | } 31 | 32 | return rval; 33 | }; 34 | 35 | /** 36 | * Checks to see if the passed in IRI is a Blank Node. 37 | * 38 | * @param iri the IRI to check. 39 | * 40 | * @return true if the iri is a Blank Node, false otherwise. 41 | */ 42 | function isBnode(iri) 43 | { 44 | var bnodePrefix = "_:"; 45 | 46 | return (iri.substring(0, bnodePrefix.length) === bnodePrefix); 47 | }; 48 | 49 | /** 50 | * Converts an IRI to TURTLE format. If it is a regular scheme-based IRI, 51 | * angle brackets are placed around the value, otherwise, if the value is 52 | * a Blank Node, the value is used as-is. 53 | * 54 | * @param iri the IRI to convert to TURTLE format. 55 | * 56 | * @return the TURTLE-formatted IRI. 57 | */ 58 | function iriToTurtle(iri) 59 | { 60 | var rval = undefined; 61 | 62 | // place angle brackets around anything that is not a Blank Node 63 | if(isBnode(iri)) 64 | { 65 | rval = iri; 66 | } 67 | else 68 | { 69 | rval = "<" + iri + ">"; 70 | } 71 | 72 | return rval; 73 | }; 74 | 75 | /** 76 | * Converts the 'object' part of a 'subject', 'property', 'object' triple 77 | * into a text string. 78 | * 79 | * @param obj the object to convert to a string. 80 | * 81 | * @return the string representation of the object. 82 | */ 83 | function objectToString(obj) 84 | { 85 | var rval = undefined; 86 | 87 | if(obj instanceof Array) 88 | { 89 | // if the object is an array, convert each object in the list 90 | var firstItem = true; 91 | for(i in obj) 92 | { 93 | if(firstItem == true) 94 | { 95 | firstItem = false; 96 | rval = "\n "; 97 | } 98 | else 99 | { 100 | rval += ",\n "; 101 | } 102 | rval += objectToString(obj[i]); 103 | } 104 | } 105 | else if(obj instanceof Object) 106 | { 107 | // the object is an IRI, typed literal or language-tagged literal 108 | if("@value" in obj && "@type" in obj) 109 | { 110 | rval = "\"" + obj["@value"] + "\"^^<" + obj["@type"] + ">"; 111 | } 112 | else if("@value" in obj && "@language" in obj) 113 | { 114 | rval = "\"" + obj["@value"] + "\"@" + obj["@language"]; 115 | } 116 | else if("@id" in obj) 117 | { 118 | var iri = obj["@id"]; 119 | rval = iriToTurtle(iri); 120 | } 121 | } 122 | else 123 | { 124 | // the object is a plain literal 125 | rval = "\"" + obj + "\""; 126 | } 127 | 128 | return rval; 129 | }; 130 | 131 | /** 132 | * Converts JSON-LD input to a TURTLE formatted string. 133 | * 134 | * @param input the JSON-LD object as a JavaScript object. 135 | * 136 | * @return a TURTLE formatted string. 137 | */ 138 | jsonld.turtle = function(input) 139 | { 140 | var normalized = jsonld.normalize(input); 141 | var rval = ""; 142 | 143 | for(s in normalized) 144 | { 145 | // print out each key in the normalized array (the subjects) 146 | var subject = normalized[s]; 147 | var iri = subject["@id"]; 148 | 149 | // skip subjects with no properties (no triples to generate) 150 | if(Object.keys(subject).length === 1) 151 | { 152 | continue; 153 | } 154 | 155 | rval += iriToTurtle(iri) + "\n"; 156 | 157 | // get all properties and perform a count on them 158 | var properties = getProperties(subject); 159 | var numProperties = properties.length; 160 | 161 | // iterate through all properties and serialize them 162 | var count = numProperties; 163 | for(p in properties) 164 | { 165 | // serialize each property-object combination 166 | property = properties[p]; 167 | if(property == "@type") 168 | { 169 | rval += " "; 170 | } 171 | else 172 | { 173 | rval += " <" + property + "> "; 174 | } 175 | rval += objectToString(subject[property]); 176 | 177 | if(count == 1) 178 | { 179 | // if the item is the last item for this subject, end it with a '.' 180 | rval += ".\n"; 181 | } 182 | else 183 | { 184 | // if the item is the last item for this subject, end it with a ';' 185 | rval += ";\n"; 186 | } 187 | count -= 1; 188 | } 189 | } 190 | 191 | return rval; 192 | }; 193 | 194 | })(); 195 | 196 | -------------------------------------------------------------------------------- /javascripts/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var app = function() { this.load() }; 4 | app.prototype = { 5 | 6 | appURI: 'https://github.com/melvincarvalho/appbuilder', // DOAP 7 | webIDAuthURI : 'https://data.fm/user.js', 8 | loginType : 'loginMulti', 9 | //loginType : 'loginBrowserID', 10 | friends: [], 11 | statuses: [], 12 | user: null, 13 | 14 | // Facebook ID -- change or add more 15 | facebookAppID : '119467988130777', 16 | facebookAppURL : 'data.fm', 17 | 18 | 19 | // INIT 20 | load: function() { 21 | this.initLogin(); 22 | this.loadUser(); 23 | this.render(); 24 | }, 25 | 26 | // AUTHENTICATION 27 | initLogin: function() { 28 | $('#loginBrowserID').click(function () {document.app.loginBrowserID();}); 29 | $('#loginGmail').attr('href', 'https://data.fm/login?provider=Gmail&next=' + document.URL ); 30 | $('#loginYahoo').attr('href', 'https://data.fm/login?provider=Yahoo&next=' + document.URL ); 31 | $('#loginWebID').attr('href', 'https://data.fm/login?next=' + document.URL ); 32 | if (document.URL.indexOf(this.facebookAppURL) != -1) { 33 | $('#loginFacebook').click(function () {document.app.loginFacebook();}); 34 | } else { 35 | $('#loginFacebook').hide(); 36 | } 37 | }, 38 | 39 | loadUser: function() { 40 | this.status('Loading user...', true); 41 | // Non Facebook 42 | if (window.location.hash.length == 0) { 43 | var script = document.createElement('script'); 44 | script.src = this.getWebIDAuthURI() + '?callback=document.app.displayUser'; 45 | document.body.appendChild(script); 46 | // Facebook 47 | } else { 48 | var accessToken = window.location.hash.substring(1); 49 | var path = "https://graph.facebook.com/me?"; 50 | var queryParams = [accessToken, 'callback=document.app.displayUser']; 51 | var query = queryParams.join('&'); 52 | var url = path + query; 53 | 54 | var script = document.createElement('script'); 55 | script.src = url; 56 | document.body.appendChild(script); 57 | } 58 | }, 59 | 60 | // Multi login modal popup 61 | loginMulti: function(id) { 62 | $('#loginPopup').dialog({"title": "Sign in"}); 63 | }, 64 | 65 | // Called from fb button 66 | loginFacebook: function() { 67 | var appID = this.facebookAppID; 68 | if (window.location.hash.length == 0) { 69 | var path = 'https://www.facebook.com/dialog/oauth?'; 70 | var queryParams = ['client_id=' + appID, 71 | 'redirect_uri=' + window.location, 72 | 'response_type=token']; 73 | var query = queryParams.join('&'); 74 | var url = path + query; 75 | window.location = url; 76 | } else { 77 | } 78 | }, 79 | 80 | // Called from BrowserID button 81 | loginBrowserID: function() { 82 | navigator.id.get(function(assertion) { 83 | if (assertion) { 84 | 85 | var arr = assertion.split('.'); 86 | var f = JSON.parse(window.atob(arr[1])); 87 | var user = f['principal']['email']; 88 | document.getElementById('welcome').innerHTML = user + ''; 89 | document.app.user = 'mailto:' + user; 90 | $('#loginPopup').dialog('close'); 91 | document.app.loadRemote(); 92 | 93 | 94 | // This code will be invoked once the user has successfully 95 | // selected an email address they control to sign in with. 96 | } else { 97 | // something went wrong! the user isn't logged in. 98 | } 99 | }); 100 | }, 101 | 102 | // Displays users, callback to loadFriends, loadRemote 103 | displayUser: function(user) { 104 | this.status('Loading user...', false); 105 | var userName = document.getElementById('welcome'); 106 | if ( user.name ) { 107 | var greetingText = document.createTextNode('Greetings, ' + user.name + '.'); 108 | document.app.user = 'https://graph.facebook.com/' + user.id; 109 | userName.innerHTML = 'Greetings, ' + user.name + '';; 110 | this.loadFBFriends(); 111 | } else { 112 | document.app.user = user; 113 | var userName = document.getElementById('welcome'); 114 | if (user.substring(0,4) == 'dns:' ) { 115 | var signin = 'javascript:document.app.' + this.loginType + '()'; 116 | userName.innerHTML = 'IP: ' + document.app.user + ' ' + '' ; 117 | } else { 118 | userName.innerHTML = 'User: ' + document.app.user + ''; 119 | } 120 | } 121 | this.loadRemote(); 122 | }, 123 | 124 | logout: function() { 125 | this.user = null; 126 | var signin = 'javascript:document.app.' + this.loginType + '()'; 127 | var userName = document.getElementById('welcome'); 128 | userName.innerHTML = 'Sign In:  ' + '' ; 129 | this.render(); 130 | }, 131 | 132 | // DISCOVERY 133 | getWebIDAuthURI: function() { 134 | // Try localStorage first 135 | if ( window.localStorage.webIDAuthURI ) return window.localStorage.webIDAuthURI; 136 | 137 | // TODO: Search loacal path for user prefs 138 | 139 | // TODO: Discovery via HTTP user URI, Webfinger 140 | 141 | // TODO: Discover vid HTTP / DOAP 142 | 143 | // Fallback to default location 144 | return this.webIDAuthURI; 145 | }, 146 | 147 | 148 | // FRIENDS 149 | // Loads friends, callback to displayFriends 150 | loadFBFriends: function() { 151 | this.status('Loading friends...', true); 152 | var accessToken = window.location.hash.substring(1); 153 | var path = "https://graph.facebook.com/me/friends?"; 154 | var queryParams = [accessToken, 'callback=document.app.displayFriends']; 155 | var query = queryParams.join('&'); 156 | var url = path + query; 157 | 158 | // use jsonp to call the graph 159 | var script = document.createElement('script'); 160 | script.src = url; 161 | document.body.appendChild(script); 162 | }, 163 | 164 | // Displays friends 165 | displayFriends: function(data) { 166 | this.status('Loading friends...', false); 167 | var str; 168 | for (i=0; i 16) bkey = binb_sha1(bkey, key.length * 8); 54 | 55 | var ipad = Array(16), opad = Array(16); 56 | for(var i = 0; i < 16; i++) 57 | { 58 | ipad[i] = bkey[i] ^ 0x36363636; 59 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 60 | } 61 | 62 | var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8); 63 | return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160)); 64 | } 65 | 66 | /* 67 | * Convert a raw string to a hex string 68 | */ 69 | function rstr2hex(input) 70 | { 71 | try { hexcase } catch(e) { hexcase=0; } 72 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 73 | var output = ""; 74 | var x; 75 | for(var i = 0; i < input.length; i++) 76 | { 77 | x = input.charCodeAt(i); 78 | output += hex_tab.charAt((x >>> 4) & 0x0F) 79 | + hex_tab.charAt( x & 0x0F); 80 | } 81 | return output; 82 | } 83 | 84 | /* 85 | * Convert a raw string to a base-64 string 86 | */ 87 | function rstr2b64(input) 88 | { 89 | try { b64pad } catch(e) { b64pad=''; } 90 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 91 | var output = ""; 92 | var len = input.length; 93 | for(var i = 0; i < len; i += 3) 94 | { 95 | var triplet = (input.charCodeAt(i) << 16) 96 | | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) 97 | | (i + 2 < len ? input.charCodeAt(i+2) : 0); 98 | for(var j = 0; j < 4; j++) 99 | { 100 | if(i * 8 + j * 6 > input.length * 8) output += b64pad; 101 | else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); 102 | } 103 | } 104 | return output; 105 | } 106 | 107 | /* 108 | * Convert a raw string to an arbitrary string encoding 109 | */ 110 | function rstr2any(input, encoding) 111 | { 112 | var divisor = encoding.length; 113 | var remainders = Array(); 114 | var i, q, x, quotient; 115 | 116 | /* Convert to an array of 16-bit big-endian values, forming the dividend */ 117 | var dividend = Array(Math.ceil(input.length / 2)); 118 | for(i = 0; i < dividend.length; i++) 119 | { 120 | dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); 121 | } 122 | 123 | /* 124 | * Repeatedly perform a long division. The binary array forms the dividend, 125 | * the length of the encoding is the divisor. Once computed, the quotient 126 | * forms the dividend for the next step. We stop when the dividend is zero. 127 | * All remainders are stored for later use. 128 | */ 129 | while(dividend.length > 0) 130 | { 131 | quotient = Array(); 132 | x = 0; 133 | for(i = 0; i < dividend.length; i++) 134 | { 135 | x = (x << 16) + dividend[i]; 136 | q = Math.floor(x / divisor); 137 | x -= q * divisor; 138 | if(quotient.length > 0 || q > 0) 139 | quotient[quotient.length] = q; 140 | } 141 | remainders[remainders.length] = x; 142 | dividend = quotient; 143 | } 144 | 145 | /* Convert the remainders to the output string */ 146 | var output = ""; 147 | for(i = remainders.length - 1; i >= 0; i--) 148 | output += encoding.charAt(remainders[i]); 149 | 150 | /* Append leading zero equivalents */ 151 | var full_length = Math.ceil(input.length * 8 / 152 | (Math.log(encoding.length) / Math.log(2))) 153 | for(i = output.length; i < full_length; i++) 154 | output = encoding[0] + output; 155 | 156 | return output; 157 | } 158 | 159 | /* 160 | * Encode a string as utf-8. 161 | * For efficiency, this assumes the input is valid utf-16. 162 | */ 163 | function str2rstr_utf8(input) 164 | { 165 | var output = ""; 166 | var i = -1; 167 | var x, y; 168 | 169 | while(++i < input.length) 170 | { 171 | /* Decode utf-16 surrogate pairs */ 172 | x = input.charCodeAt(i); 173 | y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; 174 | if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) 175 | { 176 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); 177 | i++; 178 | } 179 | 180 | /* Encode output as utf-8 */ 181 | if(x <= 0x7F) 182 | output += String.fromCharCode(x); 183 | else if(x <= 0x7FF) 184 | output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 185 | 0x80 | ( x & 0x3F)); 186 | else if(x <= 0xFFFF) 187 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 188 | 0x80 | ((x >>> 6 ) & 0x3F), 189 | 0x80 | ( x & 0x3F)); 190 | else if(x <= 0x1FFFFF) 191 | output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 192 | 0x80 | ((x >>> 12) & 0x3F), 193 | 0x80 | ((x >>> 6 ) & 0x3F), 194 | 0x80 | ( x & 0x3F)); 195 | } 196 | return output; 197 | } 198 | 199 | /* 200 | * Encode a string as utf-16 201 | */ 202 | function str2rstr_utf16le(input) 203 | { 204 | var output = ""; 205 | for(var i = 0; i < input.length; i++) 206 | output += String.fromCharCode( input.charCodeAt(i) & 0xFF, 207 | (input.charCodeAt(i) >>> 8) & 0xFF); 208 | return output; 209 | } 210 | 211 | function str2rstr_utf16be(input) 212 | { 213 | var output = ""; 214 | for(var i = 0; i < input.length; i++) 215 | output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, 216 | input.charCodeAt(i) & 0xFF); 217 | return output; 218 | } 219 | 220 | /* 221 | * Convert a raw string to an array of big-endian words 222 | * Characters >255 have their high-byte silently ignored. 223 | */ 224 | function rstr2binb(input) 225 | { 226 | var output = Array(input.length >> 2); 227 | for(var i = 0; i < output.length; i++) 228 | output[i] = 0; 229 | for(var i = 0; i < input.length * 8; i += 8) 230 | output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); 231 | return output; 232 | } 233 | 234 | /* 235 | * Convert an array of big-endian words to a string 236 | */ 237 | function binb2rstr(input) 238 | { 239 | var output = ""; 240 | for(var i = 0; i < input.length * 32; i += 8) 241 | output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); 242 | return output; 243 | } 244 | 245 | /* 246 | * Calculate the SHA-1 of an array of big-endian words, and a bit length 247 | */ 248 | function binb_sha1(x, len) 249 | { 250 | /* append padding */ 251 | x[len >> 5] |= 0x80 << (24 - len % 32); 252 | x[((len + 64 >> 9) << 4) + 15] = len; 253 | 254 | var w = Array(80); 255 | var a = 1732584193; 256 | var b = -271733879; 257 | var c = -1732584194; 258 | var d = 271733878; 259 | var e = -1009589776; 260 | 261 | for(var i = 0; i < x.length; i += 16) 262 | { 263 | var olda = a; 264 | var oldb = b; 265 | var oldc = c; 266 | var oldd = d; 267 | var olde = e; 268 | 269 | for(var j = 0; j < 80; j++) 270 | { 271 | if(j < 16) w[j] = x[i + j]; 272 | else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); 273 | var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)), 274 | safe_add(safe_add(e, w[j]), sha1_kt(j))); 275 | e = d; 276 | d = c; 277 | c = bit_rol(b, 30); 278 | b = a; 279 | a = t; 280 | } 281 | 282 | a = safe_add(a, olda); 283 | b = safe_add(b, oldb); 284 | c = safe_add(c, oldc); 285 | d = safe_add(d, oldd); 286 | e = safe_add(e, olde); 287 | } 288 | return Array(a, b, c, d, e); 289 | 290 | } 291 | 292 | /* 293 | * Perform the appropriate triplet combination function for the current 294 | * iteration 295 | */ 296 | function sha1_ft(t, b, c, d) 297 | { 298 | if(t < 20) return (b & c) | ((~b) & d); 299 | if(t < 40) return b ^ c ^ d; 300 | if(t < 60) return (b & c) | (b & d) | (c & d); 301 | return b ^ c ^ d; 302 | } 303 | 304 | /* 305 | * Determine the appropriate additive constant for the current iteration 306 | */ 307 | function sha1_kt(t) 308 | { 309 | return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : 310 | (t < 60) ? -1894007588 : -899497514; 311 | } 312 | 313 | /* 314 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 315 | * to work around bugs in some JS interpreters. 316 | */ 317 | function safe_add(x, y) 318 | { 319 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 320 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 321 | return (msw << 16) | (lsw & 0xFFFF); 322 | } 323 | 324 | /* 325 | * Bitwise rotate a 32-bit number to the left. 326 | */ 327 | function bit_rol(num, cnt) 328 | { 329 | return (num << cnt) | (num >>> (32 - cnt)); 330 | } 331 | -------------------------------------------------------------------------------- /stylesheets/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | 11 | /* Table of Contents 12 | ================================================== 13 | #Base 960 Grid 14 | #Tablet (Portrait) 15 | #Mobile (Portrait) 16 | #Mobile (Landscape) 17 | #Clearing */ 18 | 19 | 20 | 21 | /* #Base 960 Grid 22 | ================================================== */ 23 | 24 | .container { position: relative; width: 960px; margin: 0 auto; padding: 0; } 25 | .column, .columns { float: left; display: inline; margin-left: 10px; margin-right: 10px; } 26 | .row { margin-bottom: 20px; } 27 | 28 | /* Nested Column Classes */ 29 | .column.alpha, .columns.alpha { margin-left: 0; } 30 | .column.omega, .columns.omega { margin-right: 0; } 31 | 32 | /* Base Grid */ 33 | .container .one.column { width: 40px; } 34 | .container .two.columns { width: 100px; } 35 | .container .three.columns { width: 160px; } 36 | .container .four.columns { width: 220px; } 37 | .container .five.columns { width: 280px; } 38 | .container .six.columns { width: 340px; } 39 | .container .seven.columns { width: 400px; } 40 | .container .eight.columns { width: 460px; } 41 | .container .nine.columns { width: 520px; } 42 | .container .ten.columns { width: 580px; } 43 | .container .eleven.columns { width: 640px; } 44 | .container .twelve.columns { width: 700px; } 45 | .container .thirteen.columns { width: 760px; } 46 | .container .fourteen.columns { width: 820px; } 47 | .container .fifteen.columns { width: 880px; } 48 | .container .sixteen.columns { width: 940px; } 49 | 50 | .container .one-third.column { width: 300px; } 51 | .container .two-thirds.column { width: 620px; } 52 | 53 | /* Offsets */ 54 | .container .offset-by-one { padding-left: 60px; } 55 | .container .offset-by-two { padding-left: 120px; } 56 | .container .offset-by-three { padding-left: 180px; } 57 | .container .offset-by-four { padding-left: 240px; } 58 | .container .offset-by-five { padding-left: 300px; } 59 | .container .offset-by-six { padding-left: 360px; } 60 | .container .offset-by-seven { padding-left: 420px; } 61 | .container .offset-by-eight { padding-left: 480px; } 62 | .container .offset-by-nine { padding-left: 540px; } 63 | .container .offset-by-ten { padding-left: 600px; } 64 | .container .offset-by-eleven { padding-left: 660px; } 65 | .container .offset-by-twelve { padding-left: 720px; } 66 | .container .offset-by-thirteen { padding-left: 780px; } 67 | .container .offset-by-fourteen { padding-left: 840px; } 68 | .container .offset-by-fifteen { padding-left: 900px; } 69 | 70 | 71 | 72 | /* #Tablet (Portrait) 73 | ================================================== */ 74 | 75 | /* Note: Design for a width of 768px */ 76 | 77 | @media only screen and (min-width: 768px) and (max-width: 959px) { 78 | .container { width: 768px; } 79 | .container .column, 80 | .container .columns { margin-left: 10px; margin-right: 10px; } 81 | .column.alpha, .columns.alpha { margin-left: 0; margin-right: 10px; } 82 | .column.omega, .columns.omega { margin-right: 0; margin-left: 10px; } 83 | 84 | .container .one.column { width: 28px; } 85 | .container .two.columns { width: 76px; } 86 | .container .three.columns { width: 124px; } 87 | .container .four.columns { width: 172px; } 88 | .container .five.columns { width: 220px; } 89 | .container .six.columns { width: 268px; } 90 | .container .seven.columns { width: 316px; } 91 | .container .eight.columns { width: 364px; } 92 | .container .nine.columns { width: 412px; } 93 | .container .ten.columns { width: 460px; } 94 | .container .eleven.columns { width: 508px; } 95 | .container .twelve.columns { width: 556px; } 96 | .container .thirteen.columns { width: 604px; } 97 | .container .fourteen.columns { width: 652px; } 98 | .container .fifteen.columns { width: 700px; } 99 | .container .sixteen.columns { width: 748px; } 100 | 101 | .container .one-third.column { width: 236px; } 102 | .container .two-thirds.column { width: 492px; } 103 | 104 | /* Offsets */ 105 | .container .offset-by-one { padding-left: 48px; } 106 | .container .offset-by-two { padding-left: 96px; } 107 | .container .offset-by-three { padding-left: 144px; } 108 | .container .offset-by-four { padding-left: 192px; } 109 | .container .offset-by-five { padding-left: 240px; } 110 | .container .offset-by-six { padding-left: 288px; } 111 | .container .offset-by-seven { padding-left: 336px; } 112 | .container .offset-by-eight { padding-left: 348px; } 113 | .container .offset-by-nine { padding-left: 432px; } 114 | .container .offset-by-ten { padding-left: 480px; } 115 | .container .offset-by-eleven { padding-left: 528px; } 116 | .container .offset-by-twelve { padding-left: 576px; } 117 | .container .offset-by-thirteen { padding-left: 624px; } 118 | .container .offset-by-fourteen { padding-left: 672px; } 119 | .container .offset-by-fifteen { padding-left: 720px; } 120 | } 121 | 122 | 123 | /* #Mobile (Portrait) 124 | ================================================== */ 125 | 126 | /* Note: Design for a width of 320px */ 127 | 128 | @media only screen and (max-width: 767px) { 129 | .container { width: 300px; } 130 | .columns, .column { margin: 0; } 131 | 132 | .container .one.column, 133 | .container .two.columns, 134 | .container .three.columns, 135 | .container .four.columns, 136 | .container .five.columns, 137 | .container .six.columns, 138 | .container .seven.columns, 139 | .container .eight.columns, 140 | .container .nine.columns, 141 | .container .ten.columns, 142 | .container .eleven.columns, 143 | .container .twelve.columns, 144 | .container .thirteen.columns, 145 | .container .fourteen.columns, 146 | .container .fifteen.columns, 147 | .container .sixteen.columns, 148 | .container .one-third.column, 149 | .container .two-thirds.column { width: 300px; } 150 | 151 | /* Offsets */ 152 | .container .offset-by-one, 153 | .container .offset-by-two, 154 | .container .offset-by-three, 155 | .container .offset-by-four, 156 | .container .offset-by-five, 157 | .container .offset-by-six, 158 | .container .offset-by-seven, 159 | .container .offset-by-eight, 160 | .container .offset-by-nine, 161 | .container .offset-by-ten, 162 | .container .offset-by-eleven, 163 | .container .offset-by-twelve, 164 | .container .offset-by-thirteen, 165 | .container .offset-by-fourteen, 166 | .container .offset-by-fifteen { padding-left: 0; } 167 | 168 | } 169 | 170 | 171 | /* #Mobile (Landscape) 172 | ================================================== */ 173 | 174 | /* Note: Design for a width of 480px */ 175 | 176 | @media only screen and (min-width: 480px) and (max-width: 767px) { 177 | .container { width: 420px; } 178 | .columns, .column { margin: 0; } 179 | 180 | .container .one.column, 181 | .container .two.columns, 182 | .container .three.columns, 183 | .container .four.columns, 184 | .container .five.columns, 185 | .container .six.columns, 186 | .container .seven.columns, 187 | .container .eight.columns, 188 | .container .nine.columns, 189 | .container .ten.columns, 190 | .container .eleven.columns, 191 | .container .twelve.columns, 192 | .container .thirteen.columns, 193 | .container .fourteen.columns, 194 | .container .fifteen.columns, 195 | .container .sixteen.columns, 196 | .container .one-third.column, 197 | .container .two-thirds.column { width: 420px; } 198 | } 199 | 200 | 201 | /* #Clearing 202 | ================================================== */ 203 | 204 | /* Self Clearing Goodness */ 205 | .container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; } 206 | 207 | /* Use clearfix class on parent to clear nested columns, 208 | or wrap each row of columns in a
*/ 209 | .clearfix:before, 210 | .clearfix:after, 211 | .row:before, 212 | .row:after { 213 | content: '\0020'; 214 | display: block; 215 | overflow: hidden; 216 | visibility: hidden; 217 | width: 0; 218 | height: 0; } 219 | .row:after, 220 | .clearfix:after { 221 | clear: both; } 222 | .row, 223 | .clearfix { 224 | zoom: 1; } 225 | 226 | /* You can also use a
to clear columns */ 227 | .clear { 228 | clear: both; 229 | display: block; 230 | overflow: hidden; 231 | visibility: hidden; 232 | width: 0; 233 | height: 0; 234 | } 235 | 236 | -------------------------------------------------------------------------------- /stylesheets/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | 11 | /* Table of Content 12 | ================================================== 13 | #Reset & Basics 14 | #Basic Styles 15 | #Site Styles 16 | #Typography 17 | #Links 18 | #Lists 19 | #Images 20 | #Buttons 21 | #Tabs 22 | #Forms 23 | #Misc */ 24 | 25 | 26 | /* #Reset & Basics (Inspired by E. Meyers) 27 | ================================================== */ 28 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 29 | margin: 0; 30 | padding: 0; 31 | border: 0; 32 | font-size: 100%; 33 | font: inherit; 34 | vertical-align: baseline; } 35 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 36 | display: block; } 37 | body { 38 | line-height: 1; } 39 | ol, ul { 40 | list-style: none; } 41 | blockquote, q { 42 | quotes: none; } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; } 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; } 50 | 51 | 52 | /* #Basic Styles 53 | ================================================== */ 54 | body { 55 | background: #fff; 56 | font: 14px/21px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 57 | color: #444; 58 | -webkit-font-smoothing: antialiased; /* Fix for webkit rendering */ 59 | -webkit-text-size-adjust: 100%; 60 | } 61 | 62 | 63 | /* #Typography 64 | ================================================== */ 65 | h1, h2, h3, h4, h5, h6 { 66 | color: #181818; 67 | font-family: "Georgia", "Times New Roman", Helvetica, Arial, sans-serif; 68 | font-weight: normal; } 69 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } 70 | h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;} 71 | h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; } 72 | h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; } 73 | h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; } 74 | h5 { font-size: 17px; line-height: 24px; } 75 | h6 { font-size: 14px; line-height: 21px; } 76 | .subheader { color: #777; } 77 | 78 | p { margin: 0 0 20px 0; } 79 | p img { margin: 0; } 80 | p.lead { font-size: 21px; line-height: 27px; color: #777; } 81 | 82 | em { font-style: italic; } 83 | strong { font-weight: bold; color: #333; } 84 | small { font-size: 80%; } 85 | 86 | /* Blockquotes */ 87 | blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; } 88 | blockquote { margin: 0 0 20px; padding: 9px 20px 0 19px; border-left: 1px solid #ddd; } 89 | blockquote cite { display: block; font-size: 12px; color: #555; } 90 | blockquote cite:before { content: "\2014 \0020"; } 91 | blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: #555; } 92 | 93 | hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; } 94 | 95 | 96 | /* #Links 97 | ================================================== */ 98 | a, a:visited { color: #333; text-decoration: underline; outline: 0; } 99 | a:hover, a:focus { color: #000; } 100 | p a, p a:visited { line-height: inherit; } 101 | 102 | 103 | /* #Lists 104 | ================================================== */ 105 | ul, ol { margin-bottom: 20px; } 106 | ul { list-style: none outside; } 107 | ol { list-style: decimal; } 108 | ol, ul.square, ul.circle, ul.disc { margin-left: 30px; } 109 | ul.square { list-style: square outside; } 110 | ul.circle { list-style: circle outside; } 111 | ul.disc { list-style: disc outside; } 112 | ul ul, ul ol, 113 | ol ol, ol ul { margin: 4px 0 5px 30px; font-size: 90%; } 114 | ul ul li, ul ol li, 115 | ol ol li, ol ul li { margin-bottom: 6px; } 116 | li { line-height: 18px; margin-bottom: 12px; } 117 | ul.large li { line-height: 21px; } 118 | li p { line-height: 21px; } 119 | 120 | /* #Images 121 | ================================================== */ 122 | 123 | img.scale-with-grid { 124 | max-width: 100%; 125 | height: auto; } 126 | 127 | 128 | /* #Buttons 129 | ================================================== */ 130 | 131 | a.button, 132 | button, 133 | input[type="submit"], 134 | input[type="reset"], 135 | input[type="button"] { 136 | background: #eee; /* Old browsers */ 137 | background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */ 138 | background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */ 139 | background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */ 140 | background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */ 141 | background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */ 142 | background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */ 143 | border: 1px solid #aaa; 144 | border-top: 1px solid #ccc; 145 | border-left: 1px solid #ccc; 146 | padding: 4px 12px; 147 | -moz-border-radius: 3px; 148 | -webkit-border-radius: 3px; 149 | border-radius: 3px; 150 | color: #444; 151 | display: inline-block; 152 | font-size: 11px; 153 | font-weight: bold; 154 | text-decoration: none; 155 | text-shadow: 0 1px rgba(255, 255, 255, .75); 156 | cursor: pointer; 157 | margin-bottom: 20px; 158 | line-height: 21px; 159 | font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; } 160 | 161 | a.button:hover, 162 | button:hover, 163 | input[type="submit"]:hover, 164 | input[type="reset"]:hover, 165 | input[type="button"]:hover { 166 | color: #222; 167 | background: #ddd; /* Old browsers */ 168 | background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */ 169 | background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */ 170 | background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */ 171 | background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */ 172 | background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */ 173 | background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */ 174 | border: 1px solid #888; 175 | border-top: 1px solid #aaa; 176 | border-left: 1px solid #aaa; } 177 | 178 | a.button:active, 179 | button:active, 180 | input[type="submit"]:active, 181 | input[type="reset"]:active, 182 | input[type="button"]:active { 183 | border: 1px solid #666; 184 | background: #ccc; /* Old browsers */ 185 | background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */ 186 | background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */ 187 | background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */ 188 | background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */ 189 | background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */ 190 | background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ } 191 | 192 | .button.full-width, 193 | button.full-width, 194 | input[type="submit"].full-width, 195 | input[type="reset"].full-width, 196 | input[type="button"].full-width { 197 | width: 100%; 198 | padding-left: 0 !important; 199 | padding-right: 0 !important; 200 | text-align: center; } 201 | 202 | 203 | /* #Tabs (activate in tabs.js) 204 | ================================================== */ 205 | ul.tabs { 206 | display: block; 207 | margin: 0 0 20px 0; 208 | padding: 0; 209 | border-bottom: solid 1px #ddd; } 210 | ul.tabs li { 211 | display: block; 212 | width: auto; 213 | height: 30px; 214 | padding: 0; 215 | float: left; 216 | margin-bottom: 0; } 217 | ul.tabs li a { 218 | display: block; 219 | text-decoration: none; 220 | width: auto; 221 | height: 29px; 222 | padding: 0px 20px; 223 | line-height: 30px; 224 | border: solid 1px #ddd; 225 | border-width: 1px 1px 0 0; 226 | margin: 0; 227 | background: #f5f5f5; 228 | font-size: 13px; } 229 | ul.tabs li a.active { 230 | background: #fff; 231 | height: 30px; 232 | position: relative; 233 | top: -4px; 234 | padding-top: 4px; 235 | border-left-width: 1px; 236 | margin: 0 0 0 -1px; 237 | color: #111; 238 | -moz-border-radius-topleft: 2px; 239 | -webkit-border-top-left-radius: 2px; 240 | border-top-left-radius: 2px; 241 | -moz-border-radius-topright: 2px; 242 | -webkit-border-top-right-radius: 2px; 243 | border-top-right-radius: 2px; } 244 | ul.tabs li:first-child a.active { 245 | margin-left: 0; } 246 | ul.tabs li:first-child a { 247 | border-width: 1px 1px 0 1px; 248 | -moz-border-radius-topleft: 2px; 249 | -webkit-border-top-left-radius: 2px; 250 | border-top-left-radius: 2px; } 251 | ul.tabs li:last-child a { 252 | -moz-border-radius-topright: 2px; 253 | -webkit-border-top-right-radius: 2px; 254 | border-top-right-radius: 2px; } 255 | 256 | ul.tabs-content { margin: 0; display: block; } 257 | ul.tabs-content > li { display:none; } 258 | ul.tabs-content > li.active { display: block; } 259 | 260 | /* Clearfixing tabs for beautiful stacking */ 261 | ul.tabs:before, 262 | ul.tabs:after { 263 | content: '\0020'; 264 | display: block; 265 | overflow: hidden; 266 | visibility: hidden; 267 | width: 0; 268 | height: 0; } 269 | ul.tabs:after { 270 | clear: both; } 271 | ul.tabs { 272 | zoom: 1; } 273 | 274 | 275 | /* #Forms 276 | ================================================== */ 277 | 278 | form { 279 | margin-bottom: 20px; } 280 | fieldset { 281 | margin-bottom: 20px; } 282 | input[type="text"], 283 | input[type="password"], 284 | input[type="email"], 285 | textarea, 286 | select { 287 | border: 1px solid #ccc; 288 | padding: 6px 4px; 289 | outline: none; 290 | -moz-border-radius: 2px; 291 | -webkit-border-radius: 2px; 292 | border-radius: 2px; 293 | font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 294 | color: #777; 295 | margin: 0; 296 | max-width: 100%; 297 | margin-bottom: 20px; 298 | background: #fff; } 299 | select { 300 | padding: 0; } 301 | input[type="text"]:focus, 302 | input[type="password"]:focus, 303 | input[type="email"]:focus, 304 | textarea:focus { 305 | border: 1px solid #aaa; 306 | color: #444; 307 | -moz-box-shadow: 0 0 3px rgba(0,0,0,.2); 308 | -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2); 309 | box-shadow: 0 0 3px rgba(0,0,0,.2); } 310 | textarea { 311 | min-height: 60px; } 312 | label, 313 | legend { 314 | display: block; 315 | font-weight: bold; 316 | font-size: 13px; } 317 | input[type="checkbox"] { 318 | display: inline; } 319 | label span, 320 | legend span { 321 | font-weight: normal; 322 | font-size: 13px; 323 | color: #444; } 324 | 325 | /* #Misc 326 | ================================================== */ 327 | .remove-bottom { margin-bottom: 0 !important; } 328 | .half-bottom { margin-bottom: 10px !important; } 329 | .add-bottom { margin-bottom: 20px !important; } 330 | 331 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /javascripts/jsonld.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript implementation of JSON-LD. 3 | * 4 | * @author Dave Longley 5 | * 6 | * Copyright (c) 2011 Digital Bazaar, Inc. All rights reserved. 7 | */ 8 | (function() 9 | { 10 | 11 | // used by Exception 12 | var _setMembers = function(self, obj) 13 | { 14 | self.stack = ''; 15 | for(var key in obj) 16 | { 17 | self[key] = obj[key]; 18 | } 19 | }; 20 | 21 | // define jsonld 22 | if(typeof(window) !== 'undefined') 23 | { 24 | var jsonld = window.jsonld = window.jsonld || {}; 25 | Exception = function(obj) 26 | { 27 | _setMembers(this, obj); 28 | }; 29 | 30 | // define js 1.8.5 Object.keys method unless present 31 | if(!Object.keys) 32 | { 33 | Object.keys = function(o) 34 | { 35 | if(o !== Object(o)) 36 | { 37 | throw new TypeError('Object.keys called on non-object'); 38 | } 39 | var rval = []; 40 | for(var p in o) 41 | { 42 | if(Object.prototype.hasOwnProperty.call(o, p)) 43 | { 44 | rval.push(p); 45 | } 46 | } 47 | return rval; 48 | } 49 | } 50 | } 51 | // define node.js module 52 | else if(typeof(module) !== 'undefined' && module.exports) 53 | { 54 | var jsonld = {}; 55 | module.exports = jsonld; 56 | Exception = function(obj) 57 | { 58 | _setMembers(this, obj); 59 | this.stack = new Error().stack; 60 | }; 61 | } 62 | 63 | /* 64 | * Globals and helper functions. 65 | */ 66 | var ns = 67 | { 68 | xsd: 'http://www.w3.org/2001/XMLSchema#' 69 | }; 70 | 71 | var xsd = 72 | { 73 | 'boolean': ns.xsd + 'boolean', 74 | 'double': ns.xsd + 'double', 75 | 'integer': ns.xsd + 'integer' 76 | }; 77 | 78 | /** 79 | * Sets a subject's property to the given object value. If a value already 80 | * exists, it will be appended to an array. 81 | * 82 | * @param s the subject. 83 | * @param p the property. 84 | * @param o the object. 85 | */ 86 | var _setProperty = function(s, p, o) 87 | { 88 | if(p in s) 89 | { 90 | if(s[p].constructor === Array) 91 | { 92 | s[p].push(o); 93 | } 94 | else 95 | { 96 | s[p] = [s[p], o]; 97 | } 98 | } 99 | else 100 | { 101 | s[p] = o; 102 | } 103 | }; 104 | 105 | /** 106 | * Clones an object, array, or string/number. If cloning an object, the keys 107 | * will be sorted. 108 | * 109 | * @param value the value to clone. 110 | * 111 | * @return the cloned value. 112 | */ 113 | var _clone = function(value) 114 | { 115 | var rval; 116 | 117 | if(value.constructor === Object) 118 | { 119 | rval = {}; 120 | var keys = Object.keys(value).sort(); 121 | for(var i in keys) 122 | { 123 | var key = keys[i]; 124 | rval[key] = _clone(value[key]); 125 | } 126 | } 127 | else if(value.constructor === Array) 128 | { 129 | rval = []; 130 | for(var i in value) 131 | { 132 | rval[i] = _clone(value[i]); 133 | } 134 | } 135 | else 136 | { 137 | rval = value; 138 | } 139 | 140 | return rval; 141 | }; 142 | 143 | /** 144 | * Gets the keywords from a context. 145 | * 146 | * @param ctx the context. 147 | * 148 | * @return the keywords. 149 | */ 150 | var _getKeywords = function(ctx) 151 | { 152 | // TODO: reduce calls to this function by caching keywords in processor 153 | // state 154 | 155 | var rval = 156 | { 157 | '@id': '@id', 158 | '@language': '@language', 159 | '@literal': '@literal', 160 | '@type': '@type' 161 | }; 162 | 163 | if(ctx) 164 | { 165 | // gather keyword aliases from context 166 | var keywords = {}; 167 | for(var key in ctx) 168 | { 169 | if(ctx[key].constructor === String && ctx[key] in rval) 170 | { 171 | keywords[ctx[key]] = key; 172 | } 173 | } 174 | 175 | // overwrite keywords 176 | for(var key in keywords) 177 | { 178 | rval[key] = keywords[key]; 179 | } 180 | } 181 | 182 | return rval; 183 | }; 184 | 185 | /** 186 | * Gets the iri associated with a term. 187 | * 188 | * @param ctx the context. 189 | * @param term the term. 190 | * 191 | * @return the iri or NULL. 192 | */ 193 | var _getTermIri = function(ctx, term) 194 | { 195 | var rval = null; 196 | if(term in ctx) 197 | { 198 | if(ctx[term].constructor === String) 199 | { 200 | rval = ctx[term]; 201 | } 202 | else if(ctx[term].constructor === Object && '@id' in ctx[term]) 203 | { 204 | rval = ctx[term]['@id']; 205 | } 206 | } 207 | return rval; 208 | }; 209 | 210 | /** 211 | * Compacts an IRI into a term or prefix if it can be. IRIs will not be 212 | * compacted to relative IRIs if they match the given context's default 213 | * vocabulary. 214 | * 215 | * @param ctx the context to use. 216 | * @param iri the IRI to compact. 217 | * @param usedCtx a context to update if a value was used from "ctx". 218 | * 219 | * @return the compacted IRI as a term or prefix or the original IRI. 220 | */ 221 | var _compactIri = function(ctx, iri, usedCtx) 222 | { 223 | var rval = null; 224 | 225 | // check the context for a term that could shorten the IRI 226 | // (give preference to terms over prefixes) 227 | for(var key in ctx) 228 | { 229 | // skip special context keys (start with '@') 230 | if(key.length > 0 && key[0] !== '@') 231 | { 232 | // compact to a term 233 | if(iri === _getTermIri(ctx, key)) 234 | { 235 | rval = key; 236 | if(usedCtx !== null) 237 | { 238 | usedCtx[key] = _clone(ctx[key]); 239 | } 240 | break; 241 | } 242 | } 243 | } 244 | 245 | // term not found, if term is @type, use keyword 246 | if(rval === null && iri === '@type') 247 | { 248 | rval = _getKeywords(ctx)['@type']; 249 | } 250 | 251 | // term not found, check the context for a prefix 252 | if(rval === null) 253 | { 254 | for(var key in ctx) 255 | { 256 | // skip special context keys (start with '@') 257 | if(key.length > 0 && key[0] !== '@') 258 | { 259 | // see if IRI begins with the next IRI from the context 260 | var ctxIri = _getTermIri(ctx, key); 261 | if(ctxIri !== null) 262 | { 263 | var idx = iri.indexOf(ctxIri); 264 | 265 | // compact to a prefix 266 | if(idx === 0 && iri.length > ctxIri.length) 267 | { 268 | rval = key + ':' + iri.substr(ctxIri.length); 269 | if(usedCtx !== null) 270 | { 271 | usedCtx[key] = _clone(ctx[key]); 272 | } 273 | break; 274 | } 275 | } 276 | } 277 | } 278 | } 279 | 280 | // could not compact IRI 281 | if(rval === null) 282 | { 283 | rval = iri; 284 | } 285 | 286 | return rval; 287 | }; 288 | 289 | /** 290 | * Expands a term into an absolute IRI. The term may be a regular term, a 291 | * prefix, a relative IRI, or an absolute IRI. In any case, the associated 292 | * absolute IRI will be returned. 293 | * 294 | * @param ctx the context to use. 295 | * @param term the term to expand. 296 | * @param usedCtx a context to update if a value was used from "ctx". 297 | * 298 | * @return the expanded term as an absolute IRI. 299 | */ 300 | var _expandTerm = function(ctx, term, usedCtx) 301 | { 302 | var rval = term; 303 | 304 | // get JSON-LD keywords 305 | var keywords = _getKeywords(ctx); 306 | 307 | // 1. If the property has a colon, it is a prefix or an absolute IRI: 308 | var idx = term.indexOf(':'); 309 | if(idx !== -1) 310 | { 311 | // get the potential prefix 312 | var prefix = term.substr(0, idx); 313 | 314 | // expand term if prefix is in context, otherwise leave it be 315 | if(prefix in ctx) 316 | { 317 | // prefix found, expand property to absolute IRI 318 | var iri = _getTermIri(ctx, prefix); 319 | rval = iri + term.substr(idx + 1); 320 | if(usedCtx !== null) 321 | { 322 | usedCtx[prefix] = _clone(ctx[prefix]); 323 | } 324 | } 325 | } 326 | // 2. If the property is in the context, then it's a term. 327 | else if(term in ctx) 328 | { 329 | rval = _getTermIri(ctx, term); 330 | if(usedCtx !== null) 331 | { 332 | usedCtx[term] = _clone(ctx[term]); 333 | } 334 | } 335 | // 3. The property is a keyword. 336 | else 337 | { 338 | for(var key in keywords) 339 | { 340 | if(term === keywords[key]) 341 | { 342 | rval = key; 343 | break; 344 | } 345 | } 346 | } 347 | 348 | return rval; 349 | }; 350 | 351 | /** 352 | * Sorts the keys in a context. 353 | * 354 | * @param ctx the context to sort. 355 | * 356 | * @return the sorted context. 357 | */ 358 | var _sortContextKeys = function(ctx) 359 | { 360 | // sort keys 361 | var rval = {}; 362 | var keys = Object.keys(ctx).sort(); 363 | for(var k in keys) 364 | { 365 | var key = keys[k]; 366 | rval[key] = ctx[key]; 367 | } 368 | return rval; 369 | }; 370 | 371 | /** 372 | * Gets whether or not a value is a reference to a subject (or a subject with 373 | * no properties). 374 | * 375 | * @param value the value to check. 376 | * 377 | * @return true if the value is a reference to a subject, false if not. 378 | */ 379 | var _isReference = function(value) 380 | { 381 | // Note: A value is a reference to a subject if all of these hold true: 382 | // 1. It is an Object. 383 | // 2. It is has an @id key. 384 | // 3. It has only 1 key. 385 | return (value !== null && 386 | value.constructor === Object && 387 | '@id' in value && 388 | Object.keys(value).length === 1); 389 | }; 390 | 391 | /** 392 | * Gets whether or not a value is a subject with properties. 393 | * 394 | * @param value the value to check. 395 | * 396 | * @return true if the value is a subject with properties, false if not. 397 | */ 398 | var _isSubject = function(value) 399 | { 400 | var rval = false; 401 | 402 | // Note: A value is a subject if all of these hold true: 403 | // 1. It is an Object. 404 | // 2. It is not a literal. 405 | // 3. It has more than 1 key OR any existing key is not '@id'. 406 | if(value !== null && value.constructor === Object && !('@literal' in value)) 407 | { 408 | var keyCount = Object.keys(value).length; 409 | rval = (keyCount > 1 || !('@id' in value)); 410 | } 411 | 412 | return rval; 413 | }; 414 | 415 | /* 416 | * JSON-LD API. 417 | */ 418 | 419 | /** 420 | * Normalizes a JSON-LD object. 421 | * 422 | * @param input the JSON-LD object to normalize. 423 | * 424 | * @return the normalized JSON-LD object. 425 | */ 426 | jsonld.normalize = function(input) 427 | { 428 | return new Processor().normalize(input); 429 | }; 430 | 431 | /** 432 | * Removes the context from a JSON-LD object, expanding it to full-form. 433 | * 434 | * @param input the JSON-LD object to remove the context from. 435 | * 436 | * @return the context-neutral JSON-LD object. 437 | */ 438 | jsonld.expand = function(input) 439 | { 440 | return new Processor().expand({}, null, input); 441 | }; 442 | 443 | /** 444 | * Expands the given JSON-LD object and then compacts it using the 445 | * given context. 446 | * 447 | * @param ctx the new context to use. 448 | * @param input the input JSON-LD object. 449 | * 450 | * @return the output JSON-LD object. 451 | */ 452 | jsonld.compact = function(ctx, input) 453 | { 454 | var rval = null; 455 | 456 | // TODO: should context simplification be optional? (ie: remove context 457 | // entries that are not used in the output) 458 | 459 | if(input !== null) 460 | { 461 | // fully expand input 462 | input = jsonld.expand(input); 463 | 464 | var tmp; 465 | if(input.constructor === Array) 466 | { 467 | rval = []; 468 | tmp = input; 469 | } 470 | else 471 | { 472 | tmp = [input]; 473 | } 474 | 475 | // merge context if it is an array 476 | if(ctx.constructor === Array) 477 | { 478 | ctx = jsonld.mergeContexts({}, ctx); 479 | } 480 | 481 | for(var i in tmp) 482 | { 483 | // setup output context 484 | var ctxOut = {}; 485 | 486 | // compact 487 | var out = new Processor().compact(_clone(ctx), null, tmp[i], ctxOut); 488 | 489 | // add context if used 490 | if(Object.keys(ctxOut).length > 0) 491 | { 492 | // sort context keys 493 | ctxOut = _sortContextKeys(ctxOut); 494 | 495 | // sort keys 496 | var keys = Object.keys(out); 497 | keys.sort(); 498 | 499 | // put @context first 500 | keys.unshift('@context'); 501 | out['@context'] = ctxOut; 502 | 503 | // order keys in output 504 | var ordered = {}; 505 | for(var k in keys) 506 | { 507 | var key = keys[k]; 508 | ordered[key] = out[key]; 509 | } 510 | out = ordered; 511 | } 512 | 513 | if(rval === null) 514 | { 515 | rval = out; 516 | } 517 | else 518 | { 519 | rval.push(out); 520 | } 521 | } 522 | } 523 | 524 | return rval; 525 | }; 526 | 527 | /** 528 | * Merges one context with another. 529 | * 530 | * @param ctx1 the context to overwrite/append to. 531 | * @param ctx2 the new context to merge onto ctx1. 532 | * 533 | * @return the merged context. 534 | */ 535 | jsonld.mergeContexts = function(ctx1, ctx2) 536 | { 537 | // merge first context if it is an array 538 | if(ctx1.constructor === Array) 539 | { 540 | ctx1 = jsonld.mergeContexts({}, ctx1); 541 | } 542 | 543 | // copy context to merged output 544 | var merged = _clone(ctx1); 545 | 546 | if(ctx2.constructor === Array) 547 | { 548 | // merge array of contexts in order 549 | for(var i in ctx2) 550 | { 551 | merged = jsonld.mergeContexts(merged, ctx2[i]); 552 | } 553 | } 554 | else 555 | { 556 | // if the new context contains any IRIs that are in the merged context, 557 | // remove them from the merged context, they will be overwritten 558 | for(var key in ctx2) 559 | { 560 | // ignore special keys starting with '@' 561 | if(key.indexOf('@') !== 0) 562 | { 563 | for(var mkey in merged) 564 | { 565 | if(merged[mkey] === ctx2[key]) 566 | { 567 | // FIXME: update related coerce rules 568 | delete merged[mkey]; 569 | break; 570 | } 571 | } 572 | } 573 | } 574 | 575 | // merge contexts 576 | for(var key in ctx2) 577 | { 578 | merged[key] = _clone(ctx2[key]); 579 | } 580 | } 581 | 582 | return merged; 583 | }; 584 | 585 | /** 586 | * Expands a term into an absolute IRI. The term may be a regular term, a 587 | * prefix, a relative IRI, or an absolute IRI. In any case, the associated 588 | * absolute IRI will be returned. 589 | * 590 | * @param ctx the context to use. 591 | * @param term the term to expand. 592 | * 593 | * @return the expanded term as an absolute IRI. 594 | */ 595 | jsonld.expandTerm = _expandTerm; 596 | 597 | /** 598 | * Compacts an IRI into a term or prefix if it can be. IRIs will not be 599 | * compacted to relative IRIs if they match the given context's default 600 | * vocabulary. 601 | * 602 | * @param ctx the context to use. 603 | * @param iri the IRI to compact. 604 | * 605 | * @return the compacted IRI as a term or prefix or the original IRI. 606 | */ 607 | jsonld.compactIri = function(ctx, iri) 608 | { 609 | return _compactIri(ctx, iri, null); 610 | }; 611 | 612 | /** 613 | * Frames JSON-LD input. 614 | * 615 | * @param input the JSON-LD input. 616 | * @param frame the frame to use. 617 | * @param options framing options to use. 618 | * 619 | * @return the framed output. 620 | */ 621 | jsonld.frame = function(input, frame, options) 622 | { 623 | return new Processor().frame(input, frame, options); 624 | }; 625 | 626 | /** 627 | * Generates triples given a JSON-LD input. Each triple that is generated 628 | * results in a call to the given callback. The callback takes 3 parameters: 629 | * subject, property, and object. If the callback returns false then this 630 | * method will stop generating triples and return. If the callback is null, 631 | * then an array with triple objects containing "s", "p", "o" properties will 632 | * be returned. 633 | * 634 | * The object or "o" property will be a JSON-LD formatted object. 635 | * 636 | * @param input the JSON-LD input. 637 | * @param callback the triple callback. 638 | * 639 | * @return an array of triple objects if callback is null, null otherwise. 640 | */ 641 | jsonld.toTriples = function(input, callback) 642 | { 643 | var rval = null; 644 | 645 | // normalize input 646 | var normalized = jsonld.normalize(input); 647 | 648 | // setup default callback 649 | callback = callback || null; 650 | if(callback === null) 651 | { 652 | rval = []; 653 | callback = function(s, p, o) 654 | { 655 | rval.push({'s': s, 'p': p, 'o': o}); 656 | }; 657 | } 658 | 659 | // generate triples 660 | var quit = false; 661 | for(var i1 in normalized) 662 | { 663 | var e = normalized[i1]; 664 | var s = e['@id']; 665 | for(var p in e) 666 | { 667 | if(p !== '@id') 668 | { 669 | var obj = e[p]; 670 | if(obj.constructor !== Array) 671 | { 672 | obj = [obj]; 673 | } 674 | for(var i2 in obj) 675 | { 676 | quit = (callback(s, p, obj[i2]) === false); 677 | if(quit) 678 | { 679 | break; 680 | } 681 | } 682 | if(quit) 683 | { 684 | break; 685 | } 686 | } 687 | } 688 | if(quit) 689 | { 690 | break; 691 | } 692 | } 693 | 694 | return rval; 695 | }; 696 | 697 | /** 698 | * Resolves external @context URLs. Every @context URL in the given JSON-LD 699 | * object is resolved using the given URL-resolver function. Once all of 700 | * the @contexts have been resolved, the given result callback is invoked. 701 | * 702 | * @param input the JSON-LD input object (or array). 703 | * @param resolver the resolver method that takes a URL and a callback that 704 | * receives a JSON-LD serialized @context or null on error (with 705 | * optional an error object as the second parameter). 706 | * @param callback the callback to be invoked with the fully-resolved 707 | * JSON-LD output (object or array) or null on error (with an 708 | * optional error array as the second parameter). 709 | */ 710 | jsonld.resolve = function(input, resolver, callback) 711 | { 712 | // find all @context URLs 713 | var urls = {}; 714 | var findUrls = function(input, replace) 715 | { 716 | if(input.constructor === Array) 717 | { 718 | for(var i in input) 719 | { 720 | findUrls(input[i]); 721 | } 722 | } 723 | else if(input.constructor === Object) 724 | { 725 | for(var key in input) 726 | { 727 | if(key === '@context') 728 | { 729 | // @context is an array that might contain URLs 730 | if(input[key].constructor === Array) 731 | { 732 | var list = input[key]; 733 | for(var i in list) 734 | { 735 | if(list[i].constructor === String) 736 | { 737 | // replace w/resolved @context if appropriate 738 | if(replace) 739 | { 740 | list[i] = urls[list[i]]; 741 | } 742 | // unresolved @context found 743 | else 744 | { 745 | urls[list[i]] = {}; 746 | } 747 | } 748 | } 749 | } 750 | else if(input[key].constructor === String) 751 | { 752 | // replace w/resolved @context if appropriate 753 | if(replace) 754 | { 755 | input[key] = urls[input[key]]; 756 | } 757 | // unresolved @context found 758 | else 759 | { 760 | urls[input[key]] = {}; 761 | } 762 | } 763 | } 764 | } 765 | } 766 | }; 767 | findUrls(input, false); 768 | 769 | // state for resolving URLs 770 | var count = Object.keys(urls).length; 771 | var errors = null; 772 | 773 | if(count === 0) 774 | { 775 | callback(input, errors); 776 | } 777 | else 778 | { 779 | // resolve all URLs 780 | for(var url in urls) 781 | { 782 | resolver(url, function(result, error) 783 | { 784 | --count; 785 | 786 | if(result === null) 787 | { 788 | errors = errors || []; 789 | errors.push({ url: url, error: error }); 790 | } 791 | else 792 | { 793 | try 794 | { 795 | if(result.constructor === String) 796 | { 797 | urls[url] = JSON.parse(result)['@context']; 798 | } 799 | else 800 | { 801 | urls[url] = result['@context']; 802 | } 803 | } 804 | catch(ex) 805 | { 806 | errors = errors || []; 807 | errors.push({ url: url, error: ex }); 808 | } 809 | } 810 | 811 | if(count === 0) 812 | { 813 | if(errors === null) 814 | { 815 | findUrls(input, true); 816 | } 817 | callback(input, errors); 818 | } 819 | }); 820 | } 821 | } 822 | }; 823 | 824 | // TODO: organizational rewrite 825 | 826 | /** 827 | * Constructs a new JSON-LD processor. 828 | */ 829 | var Processor = function() 830 | { 831 | }; 832 | 833 | /** 834 | * Recursively compacts a value. This method will compact IRIs to prefixes or 835 | * terms and do reverse type coercion to compact a value. 836 | * 837 | * @param ctx the context to use. 838 | * @param property the property that points to the value, NULL for none. 839 | * @param value the value to compact. 840 | * @param usedCtx a context to update if a value was used from "ctx". 841 | * 842 | * @return the compacted value. 843 | */ 844 | Processor.prototype.compact = function(ctx, property, value, usedCtx) 845 | { 846 | var rval; 847 | 848 | // get JSON-LD keywords 849 | var keywords = _getKeywords(ctx); 850 | 851 | if(value === null) 852 | { 853 | // return null, but check coerce type to add to usedCtx 854 | rval = null; 855 | this.getCoerceType(ctx, property, usedCtx); 856 | } 857 | else if(value.constructor === Array) 858 | { 859 | // recursively add compacted values to array 860 | rval = []; 861 | for(var i in value) 862 | { 863 | rval.push(this.compact(ctx, property, value[i], usedCtx)); 864 | } 865 | } 866 | // graph literal/disjoint graph 867 | else if( 868 | value.constructor === Object && 869 | '@id' in value && value['@id'].constructor === Array) 870 | { 871 | rval = {}; 872 | rval[keywords['@id']] = this.compact( 873 | ctx, property, value['@id'], usedCtx); 874 | } 875 | // recurse if value is a subject 876 | else if(_isSubject(value)) 877 | { 878 | // recursively handle sub-properties that aren't a sub-context 879 | rval = {}; 880 | for(var key in value) 881 | { 882 | if(value[key] !== '@context') 883 | { 884 | // set object to compacted property, only overwrite existing 885 | // properties if the property actually compacted 886 | var p = _compactIri(ctx, key, usedCtx); 887 | if(p !== key || !(p in rval)) 888 | { 889 | // FIXME: clean old values from the usedCtx here ... or just 890 | // change usedCtx to be built at the end of processing? 891 | rval[p] = this.compact(ctx, key, value[key], usedCtx); 892 | } 893 | } 894 | } 895 | } 896 | else 897 | { 898 | // get coerce type 899 | var coerce = this.getCoerceType(ctx, property, usedCtx); 900 | 901 | // get type from value, to ensure coercion is valid 902 | var type = null; 903 | if(value.constructor === Object) 904 | { 905 | // type coercion can only occur if language is not specified 906 | if(!('@language' in value)) 907 | { 908 | // type must match coerce type if specified 909 | if('@type' in value) 910 | { 911 | type = value['@type']; 912 | } 913 | // type is ID (IRI) 914 | else if('@id' in value) 915 | { 916 | type = '@id'; 917 | } 918 | // can be coerced to any type 919 | else 920 | { 921 | type = coerce; 922 | } 923 | } 924 | } 925 | // type can be coerced to anything 926 | else if(value.constructor === String) 927 | { 928 | type = coerce; 929 | } 930 | 931 | // types that can be auto-coerced from a JSON-builtin 932 | if(coerce === null && 933 | (type === xsd['boolean'] || type === xsd['integer'] || 934 | type === xsd['double'])) 935 | { 936 | coerce = type; 937 | } 938 | 939 | // do reverse type-coercion 940 | if(coerce !== null) 941 | { 942 | // type is only null if a language was specified, which is an error 943 | // if type coercion is specified 944 | if(type === null) 945 | { 946 | throw { 947 | message: 'Cannot coerce type when a language is specified. ' + 948 | 'The language information would be lost.' 949 | }; 950 | } 951 | // if the value type does not match the coerce type, it is an error 952 | else if(type !== coerce) 953 | { 954 | throw new Exception({ 955 | message: 'Cannot coerce type because the type does ' + 956 | 'not match.', 957 | type: type, 958 | expected: coerce 959 | }); 960 | } 961 | // do reverse type-coercion 962 | else 963 | { 964 | if(value.constructor === Object) 965 | { 966 | if('@id' in value) 967 | { 968 | rval = value['@id']; 969 | } 970 | else if('@literal' in value) 971 | { 972 | rval = value['@literal']; 973 | } 974 | } 975 | else 976 | { 977 | rval = value; 978 | } 979 | 980 | // do basic JSON types conversion 981 | if(coerce === xsd['boolean']) 982 | { 983 | rval = (rval === 'true' || rval != 0); 984 | } 985 | else if(coerce === xsd['double']) 986 | { 987 | rval = parseFloat(rval); 988 | } 989 | else if(coerce === xsd['integer']) 990 | { 991 | rval = parseInt(rval); 992 | } 993 | } 994 | } 995 | // no type-coercion, just change keywords/copy value 996 | else if(value.constructor === Object) 997 | { 998 | rval = {}; 999 | for(var key in value) 1000 | { 1001 | rval[keywords[key]] = value[key]; 1002 | } 1003 | } 1004 | else 1005 | { 1006 | rval = _clone(value); 1007 | } 1008 | 1009 | // compact IRI 1010 | if(type === '@id') 1011 | { 1012 | if(rval.constructor === Object) 1013 | { 1014 | rval[keywords['@id']] = _compactIri( 1015 | ctx, rval[keywords['@id']], usedCtx); 1016 | } 1017 | else 1018 | { 1019 | rval = _compactIri(ctx, rval, usedCtx); 1020 | } 1021 | } 1022 | } 1023 | 1024 | return rval; 1025 | }; 1026 | 1027 | /** 1028 | * Recursively expands a value using the given context. Any context in 1029 | * the value will be removed. 1030 | * 1031 | * @param ctx the context. 1032 | * @param property the property that points to the value, NULL for none. 1033 | * @param value the value to expand. 1034 | * 1035 | * @return the expanded value. 1036 | */ 1037 | Processor.prototype.expand = function(ctx, property, value) 1038 | { 1039 | var rval; 1040 | 1041 | // TODO: add data format error detection? 1042 | 1043 | // value is null, nothing to expand 1044 | if(value === null) 1045 | { 1046 | rval = null; 1047 | } 1048 | // if no property is specified and the value is a string (this means the 1049 | // value is a property itself), expand to an IRI 1050 | else if(property === null && value.constructor === String) 1051 | { 1052 | rval = _expandTerm(ctx, value, null); 1053 | } 1054 | else if(value.constructor === Array) 1055 | { 1056 | // recursively add expanded values to array 1057 | rval = []; 1058 | for(var i in value) 1059 | { 1060 | rval.push(this.expand(ctx, property, value[i])); 1061 | } 1062 | } 1063 | else if(value.constructor === Object) 1064 | { 1065 | // if value has a context, use it 1066 | if('@context' in value) 1067 | { 1068 | ctx = jsonld.mergeContexts(ctx, value['@context']); 1069 | } 1070 | 1071 | // recursively handle sub-properties that aren't a sub-context 1072 | rval = {}; 1073 | for(var key in value) 1074 | { 1075 | // preserve frame keywords 1076 | if(key === '@embed' || key === '@explicit' || 1077 | key === '@default' || key === '@omitDefault') 1078 | { 1079 | _setProperty(rval, key, _clone(value[key])); 1080 | } 1081 | else if(key !== '@context') 1082 | { 1083 | // set object to expanded property 1084 | _setProperty( 1085 | rval, _expandTerm(ctx, key, null), 1086 | this.expand(ctx, key, value[key])); 1087 | } 1088 | } 1089 | } 1090 | else 1091 | { 1092 | // do type coercion 1093 | var coerce = this.getCoerceType(ctx, property, null); 1094 | 1095 | // get JSON-LD keywords 1096 | var keywords = _getKeywords(ctx); 1097 | 1098 | // automatic coercion for basic JSON types 1099 | if(coerce === null && 1100 | (value.constructor === Number || value.constructor === Boolean)) 1101 | { 1102 | if(value.constructor === Boolean) 1103 | { 1104 | coerce = xsd['boolean']; 1105 | } 1106 | else if(('' + value).indexOf('.') == -1) 1107 | { 1108 | coerce = xsd['integer']; 1109 | } 1110 | else 1111 | { 1112 | coerce = xsd['double']; 1113 | } 1114 | } 1115 | 1116 | // special-case expand @id and @type (skips '@id' expansion) 1117 | if(property === keywords['@id'] || property === keywords['@type']) 1118 | { 1119 | rval = _expandTerm(ctx, value, null); 1120 | } 1121 | // coerce to appropriate type 1122 | else if(coerce !== null) 1123 | { 1124 | rval = {}; 1125 | 1126 | // expand ID (IRI) 1127 | if(coerce === '@id') 1128 | { 1129 | rval['@id'] = _expandTerm(ctx, value, null); 1130 | } 1131 | // other type 1132 | else 1133 | { 1134 | rval['@type'] = coerce; 1135 | if(coerce === xsd['double']) 1136 | { 1137 | // do special JSON-LD double format 1138 | value = value.toExponential(6).replace( 1139 | /(e(?:\+|-))([0-9])$/, '$10$2'); 1140 | } 1141 | rval['@literal'] = '' + value; 1142 | } 1143 | } 1144 | // nothing to coerce 1145 | else 1146 | { 1147 | rval = '' + value; 1148 | } 1149 | } 1150 | 1151 | return rval; 1152 | }; 1153 | 1154 | /** 1155 | * Normalizes a JSON-LD object. 1156 | * 1157 | * @param input the JSON-LD object to normalize. 1158 | * 1159 | * @return the normalized JSON-LD object. 1160 | */ 1161 | Processor.prototype.normalize = function(input) 1162 | { 1163 | var rval = []; 1164 | 1165 | // TODO: validate context 1166 | 1167 | if(input !== null) 1168 | { 1169 | // create name generator state 1170 | this.ng = 1171 | { 1172 | tmp: null, 1173 | c14n: null 1174 | }; 1175 | 1176 | // expand input 1177 | var expanded = this.expand({}, null, input); 1178 | 1179 | // assign names to unnamed bnodes 1180 | this.nameBlankNodes(expanded); 1181 | 1182 | // flatten 1183 | var subjects = {}; 1184 | _flatten(null, null, expanded, subjects); 1185 | 1186 | // append subjects with sorted properties to array 1187 | for(var key in subjects) 1188 | { 1189 | var s = subjects[key]; 1190 | var sorted = {}; 1191 | var keys = Object.keys(s).sort(); 1192 | for(var i in keys) 1193 | { 1194 | var k = keys[i]; 1195 | sorted[k] = s[k]; 1196 | } 1197 | rval.push(sorted); 1198 | } 1199 | 1200 | // canonicalize blank nodes 1201 | this.canonicalizeBlankNodes(rval); 1202 | 1203 | // sort output 1204 | rval.sort(function(a, b) 1205 | { 1206 | return _compare(a['@id'], b['@id']); 1207 | }); 1208 | } 1209 | 1210 | return rval; 1211 | }; 1212 | 1213 | /** 1214 | * Gets the coerce type for the given property. 1215 | * 1216 | * @param ctx the context to use. 1217 | * @param property the property to get the coerced type for. 1218 | * @param usedCtx a context to update if a value was used from "ctx". 1219 | * 1220 | * @return the coerce type, null for none. 1221 | */ 1222 | Processor.prototype.getCoerceType = function(ctx, property, usedCtx) 1223 | { 1224 | var rval = null; 1225 | 1226 | // get expanded property 1227 | var p = _expandTerm(ctx, property, null); 1228 | 1229 | // built-in type coercion JSON-LD-isms 1230 | if(p === '@id' || p === '@type') 1231 | { 1232 | rval = '@id'; 1233 | } 1234 | else 1235 | { 1236 | // look up compacted property for a coercion type 1237 | p = _compactIri(ctx, p, null); 1238 | if(p in ctx && ctx[p].constructor === Object && '@type' in ctx[p]) 1239 | { 1240 | // property found, return expanded type 1241 | var type = ctx[p]['@type']; 1242 | rval = _expandTerm(ctx, type, usedCtx); 1243 | if(usedCtx !== null) 1244 | { 1245 | usedCtx[p] = _clone(ctx[p]); 1246 | } 1247 | } 1248 | } 1249 | 1250 | return rval; 1251 | }; 1252 | 1253 | var _isBlankNodeIri = function(v) 1254 | { 1255 | return v.indexOf('_:') === 0; 1256 | }; 1257 | 1258 | var _isNamedBlankNode = function(v) 1259 | { 1260 | // look for "_:" at the beginning of the subject 1261 | return ( 1262 | v.constructor === Object && '@id' in v && _isBlankNodeIri(v['@id'])); 1263 | }; 1264 | 1265 | var _isBlankNode = function(v) 1266 | { 1267 | // look for a subject with no ID or a blank node ID 1268 | return (_isSubject(v) && (!('@id' in v) || _isNamedBlankNode(v))); 1269 | }; 1270 | 1271 | /** 1272 | * Compares two values. 1273 | * 1274 | * @param v1 the first value. 1275 | * @param v2 the second value. 1276 | * 1277 | * @return -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2. 1278 | */ 1279 | var _compare = function(v1, v2) 1280 | { 1281 | var rval = 0; 1282 | 1283 | if(v1.constructor === Array && v2.constructor === Array) 1284 | { 1285 | for(var i = 0; i < v1.length && rval === 0; ++i) 1286 | { 1287 | rval = _compare(v1[i], v2[i]); 1288 | } 1289 | } 1290 | else 1291 | { 1292 | rval = (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0)); 1293 | } 1294 | 1295 | return rval; 1296 | }; 1297 | 1298 | /** 1299 | * Compares two keys in an object. If the key exists in one object 1300 | * and not the other, the object with the key is less. If the key exists in 1301 | * both objects, then the one with the lesser value is less. 1302 | * 1303 | * @param o1 the first object. 1304 | * @param o2 the second object. 1305 | * @param key the key. 1306 | * 1307 | * @return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2. 1308 | */ 1309 | var _compareObjectKeys = function(o1, o2, key) 1310 | { 1311 | var rval = 0; 1312 | if(key in o1) 1313 | { 1314 | if(key in o2) 1315 | { 1316 | rval = _compare(o1[key], o2[key]); 1317 | } 1318 | else 1319 | { 1320 | rval = -1; 1321 | } 1322 | } 1323 | else if(key in o2) 1324 | { 1325 | rval = 1; 1326 | } 1327 | return rval; 1328 | }; 1329 | 1330 | /** 1331 | * Compares two object values. 1332 | * 1333 | * @param o1 the first object. 1334 | * @param o2 the second object. 1335 | * 1336 | * @return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2. 1337 | */ 1338 | var _compareObjects = function(o1, o2) 1339 | { 1340 | var rval = 0; 1341 | 1342 | if(o1.constructor === String) 1343 | { 1344 | if(o2.constructor !== String) 1345 | { 1346 | rval = -1; 1347 | } 1348 | else 1349 | { 1350 | rval = _compare(o1, o2); 1351 | } 1352 | } 1353 | else if(o2.constructor === String) 1354 | { 1355 | rval = 1; 1356 | } 1357 | else 1358 | { 1359 | rval = _compareObjectKeys(o1, o2, '@literal'); 1360 | if(rval === 0) 1361 | { 1362 | if('@literal' in o1) 1363 | { 1364 | rval = _compareObjectKeys(o1, o2, '@type'); 1365 | if(rval === 0) 1366 | { 1367 | rval = _compareObjectKeys(o1, o2, '@language'); 1368 | } 1369 | } 1370 | // both are '@id' objects 1371 | else 1372 | { 1373 | rval = _compare(o1['@id'], o2['@id']); 1374 | } 1375 | } 1376 | } 1377 | 1378 | return rval; 1379 | }; 1380 | 1381 | /** 1382 | * Compares the object values between two bnodes. 1383 | * 1384 | * @param a the first bnode. 1385 | * @param b the second bnode. 1386 | * 1387 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 1388 | */ 1389 | var _compareBlankNodeObjects = function(a, b) 1390 | { 1391 | var rval = 0; 1392 | 1393 | /* 1394 | 3. For each property, compare sorted object values. 1395 | 3.1. The bnode with fewer objects is first. 1396 | 3.2. For each object value, compare only literals and non-bnodes. 1397 | 3.2.1. The bnode with fewer non-bnodes is first. 1398 | 3.2.2. The bnode with a string object is first. 1399 | 3.2.3. The bnode with the alphabetically-first string is first. 1400 | 3.2.4. The bnode with a @literal is first. 1401 | 3.2.5. The bnode with the alphabetically-first @literal is first. 1402 | 3.2.6. The bnode with the alphabetically-first @type is first. 1403 | 3.2.7. The bnode with a @language is first. 1404 | 3.2.8. The bnode with the alphabetically-first @language is first. 1405 | 3.2.9. The bnode with the alphabetically-first @id is first. 1406 | */ 1407 | 1408 | for(var p in a) 1409 | { 1410 | // skip IDs (IRIs) 1411 | if(p !== '@id') 1412 | { 1413 | // step #3.1 1414 | var lenA = (a[p].constructor === Array) ? a[p].length : 1; 1415 | var lenB = (b[p].constructor === Array) ? b[p].length : 1; 1416 | rval = _compare(lenA, lenB); 1417 | 1418 | // step #3.2.1 1419 | if(rval === 0) 1420 | { 1421 | // normalize objects to an array 1422 | var objsA = a[p]; 1423 | var objsB = b[p]; 1424 | if(objsA.constructor !== Array) 1425 | { 1426 | objsA = [objsA]; 1427 | objsB = [objsB]; 1428 | } 1429 | 1430 | // compare non-bnodes (remove bnodes from comparison) 1431 | objsA = objsA.filter(function(e) {return !_isNamedBlankNode(e);}); 1432 | objsB = objsB.filter(function(e) {return !_isNamedBlankNode(e);}); 1433 | rval = _compare(objsA.length, objsB.length); 1434 | } 1435 | 1436 | // steps #3.2.2-3.2.9 1437 | if(rval === 0) 1438 | { 1439 | objsA.sort(_compareObjects); 1440 | objsB.sort(_compareObjects); 1441 | for(var i = 0; i < objsA.length && rval === 0; ++i) 1442 | { 1443 | rval = _compareObjects(objsA[i], objsB[i]); 1444 | } 1445 | } 1446 | 1447 | if(rval !== 0) 1448 | { 1449 | break; 1450 | } 1451 | } 1452 | } 1453 | 1454 | return rval; 1455 | }; 1456 | 1457 | /** 1458 | * Creates a blank node name generator using the given prefix for the 1459 | * blank nodes. 1460 | * 1461 | * @param prefix the prefix to use. 1462 | * 1463 | * @return the blank node name generator. 1464 | */ 1465 | var _createNameGenerator = function(prefix) 1466 | { 1467 | var count = -1; 1468 | var ng = { 1469 | next: function() 1470 | { 1471 | ++count; 1472 | return ng.current(); 1473 | }, 1474 | current: function() 1475 | { 1476 | return '_:' + prefix + count; 1477 | }, 1478 | inNamespace: function(iri) 1479 | { 1480 | return iri.indexOf('_:' + prefix) === 0; 1481 | } 1482 | }; 1483 | return ng; 1484 | }; 1485 | 1486 | /** 1487 | * Populates a map of all named subjects from the given input and an array 1488 | * of all unnamed bnodes (includes embedded ones). 1489 | * 1490 | * @param input the input (must be expanded, no context). 1491 | * @param subjects the subjects map to populate. 1492 | * @param bnodes the bnodes array to populate. 1493 | */ 1494 | var _collectSubjects = function(input, subjects, bnodes) 1495 | { 1496 | if(input === null) 1497 | { 1498 | // nothing to collect 1499 | } 1500 | else if(input.constructor === Array) 1501 | { 1502 | for(var i in input) 1503 | { 1504 | _collectSubjects(input[i], subjects, bnodes); 1505 | } 1506 | } 1507 | else if(input.constructor === Object) 1508 | { 1509 | if('@id' in input) 1510 | { 1511 | // graph literal/disjoint graph 1512 | if(input['@id'].constructor == Array) 1513 | { 1514 | _collectSubjects(input['@id'], subjects, bnodes); 1515 | } 1516 | // named subject 1517 | else if(_isSubject(input)) 1518 | { 1519 | subjects[input['@id']] = input; 1520 | } 1521 | } 1522 | // unnamed blank node 1523 | else if(_isBlankNode(input)) 1524 | { 1525 | bnodes.push(input); 1526 | } 1527 | 1528 | // recurse through subject properties 1529 | for(var key in input) 1530 | { 1531 | _collectSubjects(input[key], subjects, bnodes); 1532 | } 1533 | } 1534 | }; 1535 | 1536 | /** 1537 | * Flattens the given value into a map of unique subjects. It is assumed that 1538 | * all blank nodes have been uniquely named before this call. Array values for 1539 | * properties will be sorted. 1540 | * 1541 | * @param parent the value's parent, NULL for none. 1542 | * @param parentProperty the property relating the value to the parent. 1543 | * @param value the value to flatten. 1544 | * @param subjects the map of subjects to write to. 1545 | */ 1546 | var _flatten = function(parent, parentProperty, value, subjects) 1547 | { 1548 | var flattened = null; 1549 | 1550 | if(value === null) 1551 | { 1552 | // drop null values 1553 | } 1554 | else if(value.constructor === Array) 1555 | { 1556 | // list of objects or a disjoint graph 1557 | for(var i in value) 1558 | { 1559 | _flatten(parent, parentProperty, value[i], subjects); 1560 | } 1561 | } 1562 | else if(value.constructor === Object) 1563 | { 1564 | // already-expanded value or special-case reference-only @type 1565 | if('@literal' in value || parentProperty === '@type') 1566 | { 1567 | flattened = _clone(value); 1568 | } 1569 | // graph literal/disjoint graph 1570 | else if(value['@id'].constructor === Array) 1571 | { 1572 | // cannot flatten embedded graph literals 1573 | if(parent !== null) 1574 | { 1575 | throw { 1576 | message: 'Embedded graph literals cannot be flattened.' 1577 | }; 1578 | } 1579 | 1580 | // top-level graph literal 1581 | for(var idx in value['@id']) 1582 | { 1583 | _flatten(parent, parentProperty, value['@id'][idx], subjects); 1584 | } 1585 | } 1586 | // regular subject 1587 | else 1588 | { 1589 | // create or fetch existing subject 1590 | var subject; 1591 | if(value['@id'] in subjects) 1592 | { 1593 | // FIXME: '@id' might be a graph literal (as {}) 1594 | subject = subjects[value['@id']]; 1595 | } 1596 | else 1597 | { 1598 | // FIXME: '@id' might be a graph literal (as {}) 1599 | subject = {'@id': value['@id']}; 1600 | subjects[value['@id']] = subject; 1601 | } 1602 | flattened = {'@id': subject['@id']}; 1603 | 1604 | // flatten embeds 1605 | for(var key in value) 1606 | { 1607 | var v = value[key]; 1608 | 1609 | // drop null values, skip @id (it is already set above) 1610 | if(v !== null && key !== '@id') 1611 | { 1612 | if(key in subject) 1613 | { 1614 | if(subject[key].constructor !== Array) 1615 | { 1616 | subject[key] = [subject[key]]; 1617 | } 1618 | } 1619 | else 1620 | { 1621 | subject[key] = []; 1622 | } 1623 | 1624 | _flatten(subject[key], key, v, subjects); 1625 | if(subject[key].length === 1) 1626 | { 1627 | // convert subject[key] to object if it has only 1 1628 | subject[key] = subject[key][0]; 1629 | } 1630 | } 1631 | } 1632 | } 1633 | } 1634 | // string value 1635 | else 1636 | { 1637 | flattened = value; 1638 | } 1639 | 1640 | // add flattened value to parent 1641 | if(flattened !== null && parent !== null) 1642 | { 1643 | if(parent.constructor === Array) 1644 | { 1645 | // do not add duplicate IRIs for the same property 1646 | var duplicate = false; 1647 | if(flattened.constructor === Object && '@id' in flattened) 1648 | { 1649 | duplicate = (parent.filter(function(e) 1650 | { 1651 | return (e.constructor === Object && '@id' in e && 1652 | e['@id'] === flattened['@id']); 1653 | }).length > 0); 1654 | } 1655 | if(!duplicate) 1656 | { 1657 | parent.push(flattened); 1658 | } 1659 | } 1660 | else 1661 | { 1662 | parent[parentProperty] = flattened; 1663 | } 1664 | } 1665 | }; 1666 | 1667 | 1668 | /** 1669 | * Assigns unique names to blank nodes that are unnamed in the given input. 1670 | * 1671 | * @param input the input to assign names to. 1672 | */ 1673 | Processor.prototype.nameBlankNodes = function(input) 1674 | { 1675 | // create temporary blank node name generator 1676 | var ng = this.ng.tmp = _createNameGenerator('tmp'); 1677 | 1678 | // collect subjects and unnamed bnodes 1679 | var subjects = {}; 1680 | var bnodes = []; 1681 | _collectSubjects(input, subjects, bnodes); 1682 | 1683 | // uniquely name all unnamed bnodes 1684 | for(var i in bnodes) 1685 | { 1686 | var bnode = bnodes[i]; 1687 | if(!('@id' in bnode)) 1688 | { 1689 | // generate names until one is unique 1690 | while(ng.next() in subjects){} 1691 | bnode['@id'] = ng.current(); 1692 | subjects[ng.current()] = bnode; 1693 | } 1694 | } 1695 | }; 1696 | 1697 | /** 1698 | * Renames a blank node, changing its references, etc. The method assumes 1699 | * that the given name is unique. 1700 | * 1701 | * @param b the blank node to rename. 1702 | * @param id the new name to use. 1703 | */ 1704 | Processor.prototype.renameBlankNode = function(b, id) 1705 | { 1706 | var old = b['@id']; 1707 | 1708 | // update bnode IRI 1709 | b['@id'] = id; 1710 | 1711 | // update subjects map 1712 | var subjects = this.subjects; 1713 | subjects[id] = subjects[old]; 1714 | delete subjects[old]; 1715 | 1716 | // update reference and property lists 1717 | this.edges.refs[id] = this.edges.refs[old]; 1718 | this.edges.props[id] = this.edges.props[old]; 1719 | delete this.edges.refs[old]; 1720 | delete this.edges.props[old]; 1721 | 1722 | // update references to this bnode 1723 | var refs = this.edges.refs[id].all; 1724 | for(var i in refs) 1725 | { 1726 | var iri = refs[i].s; 1727 | if(iri === old) 1728 | { 1729 | iri = id; 1730 | } 1731 | var ref = subjects[iri]; 1732 | var props = this.edges.props[iri].all; 1733 | for(var i2 in props) 1734 | { 1735 | if(props[i2].s === old) 1736 | { 1737 | props[i2].s = id; 1738 | 1739 | // normalize property to array for single code-path 1740 | var p = props[i2].p; 1741 | var tmp = (ref[p].constructor === Object) ? [ref[p]] : 1742 | (ref[p].constructor === Array) ? ref[p] : []; 1743 | for(var n in tmp) 1744 | { 1745 | if(tmp[n].constructor === Object && 1746 | '@id' in tmp[n] && tmp[n]['@id'] === old) 1747 | { 1748 | tmp[n]['@id'] = id; 1749 | } 1750 | } 1751 | } 1752 | } 1753 | } 1754 | 1755 | // update references from this bnode 1756 | var props = this.edges.props[id].all; 1757 | for(var i in props) 1758 | { 1759 | var iri = props[i].s; 1760 | refs = this.edges.refs[iri].all; 1761 | for(var r in refs) 1762 | { 1763 | if(refs[r].s === old) 1764 | { 1765 | refs[r].s = id; 1766 | } 1767 | } 1768 | } 1769 | }; 1770 | 1771 | /** 1772 | * Canonically names blank nodes in the given input. 1773 | * 1774 | * @param input the flat input graph to assign names to. 1775 | */ 1776 | Processor.prototype.canonicalizeBlankNodes = function(input) 1777 | { 1778 | // create serialization state 1779 | this.renamed = {}; 1780 | this.mappings = {}; 1781 | this.serializations = {}; 1782 | 1783 | // collect subjects and bnodes from flat input graph 1784 | var edges = this.edges = 1785 | { 1786 | refs: {}, 1787 | props: {} 1788 | }; 1789 | var subjects = this.subjects = {}; 1790 | var bnodes = []; 1791 | for(var i in input) 1792 | { 1793 | var iri = input[i]['@id']; 1794 | subjects[iri] = input[i]; 1795 | edges.refs[iri] = 1796 | { 1797 | all: [], 1798 | bnodes: [] 1799 | }; 1800 | edges.props[iri] = 1801 | { 1802 | all: [], 1803 | bnodes: [] 1804 | }; 1805 | if(_isBlankNodeIri(iri)) 1806 | { 1807 | bnodes.push(input[i]); 1808 | } 1809 | } 1810 | 1811 | // collect edges in the graph 1812 | this.collectEdges(); 1813 | 1814 | // create canonical blank node name generator 1815 | var c14n = this.ng.c14n = _createNameGenerator('c14n'); 1816 | var ngTmp = this.ng.tmp; 1817 | 1818 | // rename all bnodes that happen to be in the c14n namespace 1819 | // and initialize serializations 1820 | for(var i in bnodes) 1821 | { 1822 | var bnode = bnodes[i]; 1823 | var iri = bnode['@id']; 1824 | if(c14n.inNamespace(iri)) 1825 | { 1826 | // generate names until one is unique 1827 | while(ngTmp.next() in subjects){}; 1828 | this.renameBlankNode(bnode, ngTmp.current()); 1829 | iri = bnode['@id']; 1830 | } 1831 | this.serializations[iri] = 1832 | { 1833 | 'props': null, 1834 | 'refs': null 1835 | }; 1836 | } 1837 | 1838 | // keep sorting and naming blank nodes until they are all named 1839 | var resort = true; 1840 | var self = this; 1841 | while(bnodes.length > 0) 1842 | { 1843 | if(resort) 1844 | { 1845 | resort = false; 1846 | bnodes.sort(function(a, b) 1847 | { 1848 | return self.deepCompareBlankNodes(a, b); 1849 | }); 1850 | } 1851 | 1852 | // name all bnodes according to the first bnode's relation mappings 1853 | var bnode = bnodes.shift(); 1854 | var iri = bnode['@id']; 1855 | var dirs = ['props', 'refs']; 1856 | for(var d in dirs) 1857 | { 1858 | var dir = dirs[d]; 1859 | 1860 | // if no serialization has been computed, name only the first node 1861 | if(this.serializations[iri][dir] === null) 1862 | { 1863 | var mapping = {}; 1864 | mapping[iri] = 's1'; 1865 | } 1866 | else 1867 | { 1868 | mapping = this.serializations[iri][dir].m; 1869 | } 1870 | 1871 | // sort keys by value to name them in order 1872 | var keys = Object.keys(mapping); 1873 | keys.sort(function(a, b) 1874 | { 1875 | return _compare(mapping[a], mapping[b]); 1876 | }); 1877 | 1878 | // name bnodes in mapping 1879 | var renamed = []; 1880 | for(var i in keys) 1881 | { 1882 | var iriK = keys[i]; 1883 | if(!c14n.inNamespace(iri) && iriK in subjects) 1884 | { 1885 | this.renameBlankNode(subjects[iriK], c14n.next()); 1886 | renamed.push(iriK); 1887 | } 1888 | } 1889 | 1890 | // only keep non-canonically named bnodes 1891 | var tmp = bnodes; 1892 | bnodes = []; 1893 | for(var i in tmp) 1894 | { 1895 | var b = tmp[i]; 1896 | var iriB = b['@id']; 1897 | if(!c14n.inNamespace(iriB)) 1898 | { 1899 | // mark serializations related to the named bnodes as dirty 1900 | for(var i2 in renamed) 1901 | { 1902 | if(this.markSerializationDirty(iriB, renamed[i2], dir)) 1903 | { 1904 | // resort if a serialization was marked dirty 1905 | resort = true; 1906 | } 1907 | } 1908 | bnodes.push(b); 1909 | } 1910 | } 1911 | } 1912 | } 1913 | 1914 | // sort property lists that now have canonically-named bnodes 1915 | for(var key in edges.props) 1916 | { 1917 | if(edges.props[key].bnodes.length > 0) 1918 | { 1919 | var bnode = subjects[key]; 1920 | for(var p in bnode) 1921 | { 1922 | if(p.indexOf('@') !== 0 && bnode[p].constructor === Array) 1923 | { 1924 | bnode[p].sort(_compareObjects); 1925 | } 1926 | } 1927 | } 1928 | } 1929 | }; 1930 | 1931 | /** 1932 | * A MappingBuilder is used to build a mapping of existing blank node names 1933 | * to a form for serialization. The serialization is used to compare blank 1934 | * nodes against one another to determine a sort order. 1935 | */ 1936 | MappingBuilder = function() 1937 | { 1938 | this.count = 1; 1939 | this.processed = {}; 1940 | this.mapping = {}; 1941 | this.adj = {}; 1942 | this.keyStack = [{ keys: ['s1'], idx: 0 }]; 1943 | this.done = {}; 1944 | this.s = ''; 1945 | }; 1946 | 1947 | /** 1948 | * Copies this MappingBuilder. 1949 | * 1950 | * @return the MappingBuilder copy. 1951 | */ 1952 | MappingBuilder.prototype.copy = function() 1953 | { 1954 | var rval = new MappingBuilder(); 1955 | rval.count = this.count; 1956 | rval.processed = _clone(this.processed); 1957 | rval.mapping = _clone(this.mapping); 1958 | rval.adj = _clone(this.adj); 1959 | rval.keyStack = _clone(this.keyStack); 1960 | rval.done = _clone(this.done); 1961 | rval.s = this.s; 1962 | return rval; 1963 | }; 1964 | 1965 | /** 1966 | * Maps the next name to the given bnode IRI if the bnode IRI isn't already in 1967 | * the mapping. If the given bnode IRI is canonical, then it will be given 1968 | * a shortened form of the same name. 1969 | * 1970 | * @param iri the blank node IRI to map the next name to. 1971 | * 1972 | * @return the mapped name. 1973 | */ 1974 | MappingBuilder.prototype.mapNode = function(iri) 1975 | { 1976 | if(!(iri in this.mapping)) 1977 | { 1978 | if(iri.indexOf('_:c14n') === 0) 1979 | { 1980 | this.mapping[iri] = 'c' + iri.substr(6); 1981 | } 1982 | else 1983 | { 1984 | this.mapping[iri] = 's' + this.count++; 1985 | } 1986 | } 1987 | return this.mapping[iri]; 1988 | }; 1989 | 1990 | /** 1991 | * Serializes the properties of the given bnode for its relation serialization. 1992 | * 1993 | * @param b the blank node. 1994 | * 1995 | * @return the serialized properties. 1996 | */ 1997 | var _serializeProperties = function(b) 1998 | { 1999 | var rval = ''; 2000 | 2001 | var first = true; 2002 | for(var p in b) 2003 | { 2004 | if(p !== '@id') 2005 | { 2006 | if(first) 2007 | { 2008 | first = false; 2009 | } 2010 | else 2011 | { 2012 | rval += '|'; 2013 | } 2014 | 2015 | // property 2016 | rval += '<' + p + '>'; 2017 | 2018 | // object(s) 2019 | var objs = (b[p].constructor === Array) ? b[p] : [b[p]]; 2020 | for(var oi in objs) 2021 | { 2022 | var o = objs[oi]; 2023 | if(o.constructor === Object) 2024 | { 2025 | // ID (IRI) 2026 | if('@id' in o) 2027 | { 2028 | if(_isBlankNodeIri(o['@id'])) 2029 | { 2030 | rval += '_:'; 2031 | } 2032 | else 2033 | { 2034 | rval += '<' + o['@id'] + '>'; 2035 | } 2036 | } 2037 | // literal 2038 | else 2039 | { 2040 | rval += '"' + o['@literal'] + '"'; 2041 | 2042 | // type literal 2043 | if('@type' in o) 2044 | { 2045 | rval += '^^<' + o['@type'] + '>'; 2046 | } 2047 | // language literal 2048 | else if('@language' in o) 2049 | { 2050 | rval += '@' + o['@language']; 2051 | } 2052 | } 2053 | } 2054 | // plain literal 2055 | else 2056 | { 2057 | rval += '"' + o + '"'; 2058 | } 2059 | } 2060 | } 2061 | } 2062 | 2063 | return rval; 2064 | }; 2065 | 2066 | /** 2067 | * Recursively increments the relation serialization for a mapping. 2068 | * 2069 | * @param subjects the subjects in the graph. 2070 | * @param edges the edges in the graph. 2071 | */ 2072 | MappingBuilder.prototype.serialize = function(subjects, edges) 2073 | { 2074 | if(this.keyStack.length > 0) 2075 | { 2076 | // continue from top of key stack 2077 | var next = this.keyStack.pop(); 2078 | for(; next.idx < next.keys.length; ++next.idx) 2079 | { 2080 | var k = next.keys[next.idx]; 2081 | if(!(k in this.adj)) 2082 | { 2083 | this.keyStack.push(next); 2084 | break; 2085 | } 2086 | 2087 | if(k in this.done) 2088 | { 2089 | // mark cycle 2090 | this.s += '_' + k; 2091 | } 2092 | else 2093 | { 2094 | // mark key as serialized 2095 | this.done[k] = true; 2096 | 2097 | // serialize top-level key and its details 2098 | var s = k; 2099 | var adj = this.adj[k]; 2100 | var iri = adj.i; 2101 | if(iri in subjects) 2102 | { 2103 | var b = subjects[iri]; 2104 | 2105 | // serialize properties 2106 | s += '[' + _serializeProperties(b) + ']'; 2107 | 2108 | // serialize references 2109 | var first = true; 2110 | s += '['; 2111 | var refs = edges.refs[iri].all; 2112 | for(var r in refs) 2113 | { 2114 | if(first) 2115 | { 2116 | first = false; 2117 | } 2118 | else 2119 | { 2120 | s += '|'; 2121 | } 2122 | s += '<' + refs[r].p + '>'; 2123 | s += _isBlankNodeIri(refs[r].s) ? 2124 | '_:' : ('<' + refs[r].s + '>'); 2125 | } 2126 | s += ']'; 2127 | } 2128 | 2129 | // serialize adjacent node keys 2130 | s += adj.k.join(''); 2131 | this.s += s; 2132 | this.keyStack.push({ keys: adj.k, idx: 0 }); 2133 | this.serialize(subjects, edges); 2134 | } 2135 | } 2136 | } 2137 | }; 2138 | 2139 | /** 2140 | * Marks a relation serialization as dirty if necessary. 2141 | * 2142 | * @param iri the IRI of the bnode to check. 2143 | * @param changed the old IRI of the bnode that changed. 2144 | * @param dir the direction to check ('props' or 'refs'). 2145 | * 2146 | * @return true if marked dirty, false if not. 2147 | */ 2148 | Processor.prototype.markSerializationDirty = function(iri, changed, dir) 2149 | { 2150 | var rval = false; 2151 | 2152 | var s = this.serializations[iri]; 2153 | if(s[dir] !== null && changed in s[dir].m) 2154 | { 2155 | s[dir] = null; 2156 | rval = true; 2157 | } 2158 | 2159 | return rval; 2160 | }; 2161 | 2162 | /** 2163 | * Rotates the elements in an array one position. 2164 | * 2165 | * @param a the array. 2166 | */ 2167 | var _rotate = function(a) 2168 | { 2169 | a.unshift.apply(a, a.splice(1, a.length)); 2170 | }; 2171 | 2172 | /** 2173 | * Compares two serializations for the same blank node. If the two 2174 | * serializations aren't complete enough to determine if they are equal (or if 2175 | * they are actually equal), 0 is returned. 2176 | * 2177 | * @param s1 the first serialization. 2178 | * @param s2 the second serialization. 2179 | * 2180 | * @return -1 if s1 < s2, 0 if s1 == s2 (or indeterminate), 1 if s1 > v2. 2181 | */ 2182 | var _compareSerializations = function(s1, s2) 2183 | { 2184 | var rval = 0; 2185 | 2186 | if(s1.length == s2.length) 2187 | { 2188 | rval = _compare(s1, s2); 2189 | } 2190 | else if(s1.length > s2.length) 2191 | { 2192 | rval = _compare(s1.substr(0, s2.length), s2); 2193 | } 2194 | else 2195 | { 2196 | rval = _compare(s1, s2.substr(0, s1.length)); 2197 | } 2198 | 2199 | return rval; 2200 | }; 2201 | 2202 | /** 2203 | * Recursively serializes adjacent bnode combinations for a bnode. 2204 | * 2205 | * @param s the serialization to update. 2206 | * @param iri the IRI of the bnode being serialized. 2207 | * @param siri the serialization name for the bnode IRI. 2208 | * @param mb the MappingBuilder to use. 2209 | * @param dir the edge direction to use ('props' or 'refs'). 2210 | * @param mapped all of the already-mapped adjacent bnodes. 2211 | * @param notMapped all of the not-yet mapped adjacent bnodes. 2212 | */ 2213 | Processor.prototype.serializeCombos = function( 2214 | s, iri, siri, mb, dir, mapped, notMapped) 2215 | { 2216 | // handle recursion 2217 | if(notMapped.length > 0) 2218 | { 2219 | // copy mapped nodes 2220 | mapped = _clone(mapped); 2221 | 2222 | // map first bnode in list 2223 | mapped[mb.mapNode(notMapped[0].s)] = notMapped[0].s; 2224 | 2225 | // recurse into remaining possible combinations 2226 | var original = mb.copy(); 2227 | notMapped = notMapped.slice(1); 2228 | var rotations = Math.max(1, notMapped.length); 2229 | for(var r = 0; r < rotations; ++r) 2230 | { 2231 | var m = (r === 0) ? mb : original.copy(); 2232 | this.serializeCombos(s, iri, siri, m, dir, mapped, notMapped); 2233 | 2234 | // rotate not-mapped for next combination 2235 | _rotate(notMapped); 2236 | } 2237 | } 2238 | // no more adjacent bnodes to map, update serialization 2239 | else 2240 | { 2241 | var keys = Object.keys(mapped).sort(); 2242 | mb.adj[siri] = { i: iri, k: keys, m: mapped }; 2243 | mb.serialize(this.subjects, this.edges); 2244 | 2245 | // optimize away mappings that are already too large 2246 | if(s[dir] === null || _compareSerializations(mb.s, s[dir].s) <= 0) 2247 | { 2248 | // recurse into adjacent values 2249 | for(var i in keys) 2250 | { 2251 | var k = keys[i]; 2252 | this.serializeBlankNode(s, mapped[k], mb, dir); 2253 | } 2254 | 2255 | // update least serialization if new one has been found 2256 | mb.serialize(this.subjects, this.edges); 2257 | if(s[dir] === null || 2258 | (_compareSerializations(mb.s, s[dir].s) <= 0 && 2259 | mb.s.length >= s[dir].s.length)) 2260 | { 2261 | s[dir] = { s: mb.s, m: mb.mapping }; 2262 | } 2263 | } 2264 | } 2265 | }; 2266 | 2267 | /** 2268 | * Computes the relation serialization for the given blank node IRI. 2269 | * 2270 | * @param s the serialization to update. 2271 | * @param iri the current bnode IRI to be mapped. 2272 | * @param mb the MappingBuilder to use. 2273 | * @param dir the edge direction to use ('props' or 'refs'). 2274 | */ 2275 | Processor.prototype.serializeBlankNode = function(s, iri, mb, dir) 2276 | { 2277 | // only do mapping if iri not already processed 2278 | if(!(iri in mb.processed)) 2279 | { 2280 | // iri now processed 2281 | mb.processed[iri] = true; 2282 | var siri = mb.mapNode(iri); 2283 | 2284 | // copy original mapping builder 2285 | var original = mb.copy(); 2286 | 2287 | // split adjacent bnodes on mapped and not-mapped 2288 | var adj = this.edges[dir][iri].bnodes; 2289 | var mapped = {}; 2290 | var notMapped = []; 2291 | for(var i in adj) 2292 | { 2293 | if(adj[i].s in mb.mapping) 2294 | { 2295 | mapped[mb.mapping[adj[i].s]] = adj[i].s; 2296 | } 2297 | else 2298 | { 2299 | notMapped.push(adj[i]); 2300 | } 2301 | } 2302 | 2303 | /* 2304 | // TODO: sort notMapped using ShallowCompare 2305 | var self = this; 2306 | notMapped.sort(function(a, b) 2307 | { 2308 | var rval = self.shallowCompareBlankNodes( 2309 | self.subjects[a.s], self.subjects[b.s]); 2310 | return rval; 2311 | }); 2312 | 2313 | var same = false; 2314 | var prev = null; 2315 | for(var i in notMapped) 2316 | { 2317 | var curr = this.subjects[notMapped[i].s]; 2318 | if(prev !== null) 2319 | { 2320 | if(this.shallowCompareBlankNodes(prev, curr) === 0) 2321 | { 2322 | same = true; 2323 | } 2324 | else 2325 | { 2326 | if(!same) 2327 | { 2328 | mapped[mb.mapNode(prev['@id'])] = prev['@id']; 2329 | delete notMapped[i - 1]; 2330 | } 2331 | if(i === notMapped.length - 1) 2332 | { 2333 | mapped[mb.mapNode(curr['@id'])]; 2334 | delete notMapped[i]; 2335 | } 2336 | same = false; 2337 | } 2338 | } 2339 | prev = curr; 2340 | }*/ 2341 | 2342 | // TODO: ensure this optimization does not alter canonical order 2343 | 2344 | // if the current bnode already has a serialization, reuse it 2345 | /*var hint = (iri in this.serializations) ? 2346 | this.serializations[iri][dir] : null; 2347 | if(hint !== null) 2348 | { 2349 | var hm = hint.m; 2350 | notMapped.sort(function(a, b) 2351 | { 2352 | return _compare(hm[a.s], hm[b.s]); 2353 | }); 2354 | for(var i in notMapped) 2355 | { 2356 | mapped[mb.mapNode(notMapped[i].s)] = notMapped[i].s; 2357 | } 2358 | notMapped = []; 2359 | }*/ 2360 | 2361 | // loop over possible combinations 2362 | var combos = Math.max(1, notMapped.length); 2363 | for(var i = 0; i < combos; ++i) 2364 | { 2365 | var m = (i === 0) ? mb : original.copy(); 2366 | this.serializeCombos(s, iri, siri, m, dir, mapped, notMapped); 2367 | } 2368 | } 2369 | }; 2370 | 2371 | /** 2372 | * Compares two blank nodes for equivalence. 2373 | * 2374 | * @param a the first blank node. 2375 | * @param b the second blank node. 2376 | * 2377 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2378 | */ 2379 | Processor.prototype.deepCompareBlankNodes = function(a, b) 2380 | { 2381 | var rval = 0; 2382 | 2383 | // compare IRIs 2384 | var iriA = a['@id']; 2385 | var iriB = b['@id']; 2386 | if(iriA === iriB) 2387 | { 2388 | rval = 0; 2389 | } 2390 | else 2391 | { 2392 | // do shallow compare first 2393 | rval = this.shallowCompareBlankNodes(a, b); 2394 | 2395 | // deep comparison is necessary 2396 | if(rval === 0) 2397 | { 2398 | // compare property edges and then reference edges 2399 | var dirs = ['props', 'refs']; 2400 | for(var i = 0; rval === 0 && i < dirs.length; ++i) 2401 | { 2402 | // recompute 'a' and 'b' serializations as necessary 2403 | var dir = dirs[i]; 2404 | var sA = this.serializations[iriA]; 2405 | var sB = this.serializations[iriB]; 2406 | if(sA[dir] === null) 2407 | { 2408 | var mb = new MappingBuilder(); 2409 | if(dir === 'refs') 2410 | { 2411 | // keep same mapping and count from 'props' serialization 2412 | mb.mapping = _clone(sA['props'].m); 2413 | mb.count = Object.keys(mb.mapping).length + 1; 2414 | } 2415 | this.serializeBlankNode(sA, iriA, mb, dir); 2416 | } 2417 | if(sB[dir] === null) 2418 | { 2419 | var mb = new MappingBuilder(); 2420 | if(dir === 'refs') 2421 | { 2422 | // keep same mapping and count from 'props' serialization 2423 | mb.mapping = _clone(sB['props'].m); 2424 | mb.count = Object.keys(mb.mapping).length + 1; 2425 | } 2426 | this.serializeBlankNode(sB, iriB, mb, dir); 2427 | } 2428 | 2429 | // compare serializations 2430 | rval = _compare(sA[dir].s, sB[dir].s); 2431 | } 2432 | } 2433 | } 2434 | 2435 | return rval; 2436 | }; 2437 | 2438 | /** 2439 | * Performs a shallow sort comparison on the given bnodes. 2440 | * 2441 | * @param a the first bnode. 2442 | * @param b the second bnode. 2443 | * 2444 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2445 | */ 2446 | Processor.prototype.shallowCompareBlankNodes = function(a, b) 2447 | { 2448 | var rval = 0; 2449 | 2450 | /* ShallowSort Algorithm (when comparing two bnodes): 2451 | 1. Compare the number of properties. 2452 | 1.1. The bnode with fewer properties is first. 2453 | 2. Compare alphabetically sorted-properties. 2454 | 2.1. The bnode with the alphabetically-first property is first. 2455 | 3. For each property, compare object values. 2456 | 4. Compare the number of references. 2457 | 4.1. The bnode with fewer references is first. 2458 | 5. Compare sorted references. 2459 | 5.1. The bnode with the reference iri (vs. bnode) is first. 2460 | 5.2. The bnode with the alphabetically-first reference iri is first. 2461 | 5.3. The bnode with the alphabetically-first reference property is first. 2462 | */ 2463 | var pA = Object.keys(a); 2464 | var pB = Object.keys(b); 2465 | 2466 | // step #1 2467 | rval = _compare(pA.length, pB.length); 2468 | 2469 | // step #2 2470 | if(rval === 0) 2471 | { 2472 | rval = _compare(pA.sort(), pB.sort()); 2473 | } 2474 | 2475 | // step #3 2476 | if(rval === 0) 2477 | { 2478 | rval = _compareBlankNodeObjects(a, b); 2479 | } 2480 | 2481 | // step #4 2482 | if(rval === 0) 2483 | { 2484 | var edgesA = this.edges.refs[a['@id']].all; 2485 | var edgesB = this.edges.refs[b['@id']].all; 2486 | rval = _compare(edgesA.length, edgesB.length); 2487 | } 2488 | 2489 | // step #5 2490 | if(rval === 0) 2491 | { 2492 | for(var i = 0; i < edgesA.length && rval === 0; ++i) 2493 | { 2494 | rval = this.compareEdges(edgesA[i], edgesB[i]); 2495 | } 2496 | } 2497 | 2498 | return rval; 2499 | }; 2500 | 2501 | /** 2502 | * Compares two edges. Edges with an IRI (vs. a bnode ID) come first, then 2503 | * alphabetically-first IRIs, then alphabetically-first properties. If a blank 2504 | * node has been canonically named, then blank nodes will be compared after 2505 | * properties (with a preference for canonically named over non-canonically 2506 | * named), otherwise they won't be. 2507 | * 2508 | * @param a the first edge. 2509 | * @param b the second edge. 2510 | * 2511 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2512 | */ 2513 | Processor.prototype.compareEdges = function(a, b) 2514 | { 2515 | var rval = 0; 2516 | 2517 | var bnodeA = _isBlankNodeIri(a.s); 2518 | var bnodeB = _isBlankNodeIri(b.s); 2519 | var c14n = this.ng.c14n; 2520 | 2521 | // if not both bnodes, one that is a bnode is greater 2522 | if(bnodeA != bnodeB) 2523 | { 2524 | rval = bnodeA ? 1 : -1; 2525 | } 2526 | else 2527 | { 2528 | if(!bnodeA) 2529 | { 2530 | rval = _compare(a.s, b.s); 2531 | } 2532 | if(rval === 0) 2533 | { 2534 | rval = _compare(a.p, b.p); 2535 | } 2536 | 2537 | // do bnode IRI comparison if canonical naming has begun 2538 | if(rval === 0 && c14n !== null) 2539 | { 2540 | var c14nA = c14n.inNamespace(a.s); 2541 | var c14nB = c14n.inNamespace(b.s); 2542 | if(c14nA != c14nB) 2543 | { 2544 | rval = c14nA ? 1 : -1; 2545 | } 2546 | else if(c14nA) 2547 | { 2548 | rval = _compare(a.s, b.s); 2549 | } 2550 | } 2551 | } 2552 | 2553 | return rval; 2554 | }; 2555 | 2556 | /** 2557 | * Populates the given reference map with all of the subject edges in the 2558 | * graph. The references will be categorized by the direction of the edges, 2559 | * where 'props' is for properties and 'refs' is for references to a subject as 2560 | * an object. The edge direction categories for each IRI will be sorted into 2561 | * groups 'all' and 'bnodes'. 2562 | */ 2563 | Processor.prototype.collectEdges = function() 2564 | { 2565 | var refs = this.edges.refs; 2566 | var props = this.edges.props; 2567 | 2568 | // collect all references and properties 2569 | for(var iri in this.subjects) 2570 | { 2571 | var subject = this.subjects[iri]; 2572 | for(var key in subject) 2573 | { 2574 | if(key !== '@id') 2575 | { 2576 | // normalize to array for single codepath 2577 | var object = subject[key]; 2578 | var tmp = (object.constructor !== Array) ? [object] : object; 2579 | for(var i in tmp) 2580 | { 2581 | var o = tmp[i]; 2582 | if(o.constructor === Object && '@id' in o && 2583 | o['@id'] in this.subjects) 2584 | { 2585 | var objIri = o['@id']; 2586 | 2587 | // map object to this subject 2588 | refs[objIri].all.push({ s: iri, p: key }); 2589 | 2590 | // map this subject to object 2591 | props[iri].all.push({ s: objIri, p: key }); 2592 | } 2593 | } 2594 | } 2595 | } 2596 | } 2597 | 2598 | // create sorted categories 2599 | var self = this; 2600 | for(var iri in refs) 2601 | { 2602 | refs[iri].all.sort(function(a, b) { return self.compareEdges(a, b); }); 2603 | refs[iri].bnodes = refs[iri].all.filter(function(edge) { 2604 | return _isBlankNodeIri(edge.s); 2605 | }); 2606 | } 2607 | for(var iri in props) 2608 | { 2609 | props[iri].all.sort(function(a, b) { return self.compareEdges(a, b); }); 2610 | props[iri].bnodes = props[iri].all.filter(function(edge) { 2611 | return _isBlankNodeIri(edge.s); 2612 | }); 2613 | } 2614 | }; 2615 | 2616 | /** 2617 | * Returns true if the given input is a subject and has one of the given types 2618 | * in the given frame. 2619 | * 2620 | * @param input the input. 2621 | * @param frame the frame with types to look for. 2622 | * 2623 | * @return true if the input has one of the given types. 2624 | */ 2625 | var _isType = function(input, frame) 2626 | { 2627 | var rval = false; 2628 | 2629 | // check if type(s) are specified in frame and input 2630 | var type = '@type'; 2631 | if('@type' in frame && 2632 | input.constructor === Object && type in input) 2633 | { 2634 | var tmp = (input[type].constructor === Array) ? 2635 | input[type] : [input[type]]; 2636 | var types = (frame[type].constructor === Array) ? 2637 | frame[type] : [frame[type]]; 2638 | for(var t = 0; t < types.length && !rval; ++t) 2639 | { 2640 | type = types[t]; 2641 | for(var i in tmp) 2642 | { 2643 | if(tmp[i] === type) 2644 | { 2645 | rval = true; 2646 | break; 2647 | } 2648 | } 2649 | } 2650 | } 2651 | 2652 | return rval; 2653 | }; 2654 | 2655 | /** 2656 | * Returns true if the given input matches the given frame via duck-typing. 2657 | * 2658 | * @param input the input. 2659 | * @param frame the frame to check against. 2660 | * 2661 | * @return true if the input matches the frame. 2662 | */ 2663 | var _isDuckType = function(input, frame) 2664 | { 2665 | var rval = false; 2666 | 2667 | // frame must not have a specific type 2668 | var type = '@type'; 2669 | if(!(type in frame)) 2670 | { 2671 | // get frame properties that must exist on input 2672 | var props = Object.keys(frame).filter(function(e) 2673 | { 2674 | // filter non-keywords 2675 | return e.indexOf('@') !== 0; 2676 | }); 2677 | if(props.length === 0) 2678 | { 2679 | // input always matches if there are no properties 2680 | rval = true; 2681 | } 2682 | // input must be a subject with all the given properties 2683 | else if(input.constructor === Object && '@id' in input) 2684 | { 2685 | rval = true; 2686 | for(var i in props) 2687 | { 2688 | if(!(props[i] in input)) 2689 | { 2690 | rval = false; 2691 | break; 2692 | } 2693 | } 2694 | } 2695 | } 2696 | 2697 | return rval; 2698 | }; 2699 | 2700 | /** 2701 | * Subframes a value. 2702 | * 2703 | * @param subjects a map of subjects in the graph. 2704 | * @param value the value to subframe. 2705 | * @param frame the frame to use. 2706 | * @param embeds a map of previously embedded subjects, used to prevent cycles. 2707 | * @param autoembed true if auto-embed is on, false if not. 2708 | * @param parent the parent object. 2709 | * @param parentKey the parent key. 2710 | * @param options the framing options. 2711 | * 2712 | * @return the framed input. 2713 | */ 2714 | var _subframe = function( 2715 | subjects, value, frame, embeds, autoembed, parent, parentKey, options) 2716 | { 2717 | // get existing embed entry 2718 | var iri = value['@id']; 2719 | var embed = (iri in embeds) ? embeds[iri] : null; 2720 | 2721 | // determine if value should be embedded or referenced, 2722 | // embed is ON if: 2723 | // 1. The frame OR default option specifies @embed as ON, AND 2724 | // 2. There is no existing embed OR it is an autoembed, AND 2725 | // autoembed mode is off. 2726 | var embedOn = ( 2727 | (('@embed' in frame && frame['@embed']) || 2728 | (!('@embed' in frame) && options.defaults.embedOn)) && 2729 | (embed === null || (embed.autoembed && !autoembed))); 2730 | 2731 | if(!embedOn) 2732 | { 2733 | // not embedding, so only use subject IRI as reference 2734 | value = {'@id': value['@id']}; 2735 | } 2736 | else 2737 | { 2738 | // create new embed entry 2739 | if(embed === null) 2740 | { 2741 | embed = {}; 2742 | embeds[iri] = embed; 2743 | } 2744 | // replace the existing embed with a reference 2745 | else if(embed.parent !== null) 2746 | { 2747 | if(embed.parent[embed.key].constructor === Array) 2748 | { 2749 | // find and replace embed in array 2750 | var objs = embed.parent[embed.key]; 2751 | for(var i in objs) 2752 | { 2753 | if(objs[i].constructor === Object && '@id' in objs[i] && 2754 | objs[i]['@id'] === iri) 2755 | { 2756 | objs[i] = {'@id': value['@id']}; 2757 | break; 2758 | } 2759 | } 2760 | } 2761 | else 2762 | { 2763 | embed.parent[embed.key] = {'@id': value['@id']}; 2764 | } 2765 | 2766 | // recursively remove any dependent dangling embeds 2767 | var removeDependents = function(iri) 2768 | { 2769 | var iris = Object.keys(embeds); 2770 | for(var i in iris) 2771 | { 2772 | i = iris[i]; 2773 | if(i in embeds && embeds[i].parent !== null && 2774 | embeds[i].parent['@id'] === iri) 2775 | { 2776 | delete embeds[i]; 2777 | removeDependents(i); 2778 | } 2779 | } 2780 | }; 2781 | removeDependents(iri); 2782 | } 2783 | 2784 | // update embed entry 2785 | embed.autoembed = autoembed; 2786 | embed.parent = parent; 2787 | embed.key = parentKey; 2788 | 2789 | // check explicit flag 2790 | var explicitOn = ( 2791 | frame['@explicit'] === true || options.defaults.explicitOn); 2792 | if(explicitOn) 2793 | { 2794 | // remove keys from the value that aren't in the frame 2795 | for(key in value) 2796 | { 2797 | // do not remove @id or any frame key 2798 | if(key !== '@id' && !(key in frame)) 2799 | { 2800 | delete value[key]; 2801 | } 2802 | } 2803 | } 2804 | 2805 | // iterate over keys in value 2806 | var keys = Object.keys(value); 2807 | for(i in keys) 2808 | { 2809 | // skip keywords 2810 | var key = keys[i]; 2811 | if(key.indexOf('@') !== 0) 2812 | { 2813 | // get the subframe if available 2814 | if(key in frame) 2815 | { 2816 | var f = frame[key]; 2817 | var _autoembed = false; 2818 | } 2819 | // use a catch-all subframe to preserve data from graph 2820 | else 2821 | { 2822 | var f = (value[key].constructor === Array) ? [] : {}; 2823 | var _autoembed = true; 2824 | } 2825 | 2826 | // build input and do recursion 2827 | var v = value[key]; 2828 | var input = (v.constructor === Array) ? v : [v]; 2829 | for(var n in input) 2830 | { 2831 | // replace reference to subject w/embedded subject 2832 | if(input[n].constructor === Object && 2833 | '@id' in input[n] && 2834 | input[n]['@id'] in subjects) 2835 | { 2836 | input[n] = subjects[input[n]['@id']]; 2837 | } 2838 | } 2839 | value[key] = _frame( 2840 | subjects, input, f, embeds, _autoembed, value, key, options); 2841 | } 2842 | } 2843 | 2844 | // iterate over frame keys to add any missing values 2845 | for(key in frame) 2846 | { 2847 | // skip keywords and non-null keys in value 2848 | if(key.indexOf('@') !== 0 && (!(key in value) || value[key] === null)) 2849 | { 2850 | var f = frame[key]; 2851 | 2852 | // add empty array to value 2853 | if(f.constructor === Array) 2854 | { 2855 | value[key] = []; 2856 | } 2857 | // add default value to value 2858 | else 2859 | { 2860 | // use first subframe if frame is an array 2861 | if(f.constructor === Array) 2862 | { 2863 | f = (f.length > 0) ? f[0] : {}; 2864 | } 2865 | 2866 | // determine if omit default is on 2867 | var omitOn = ( 2868 | f['@omitDefault'] === true || options.defaults.omitDefaultOn); 2869 | if(!omitOn) 2870 | { 2871 | if('@default' in f) 2872 | { 2873 | // use specified default value 2874 | value[key] = f['@default']; 2875 | } 2876 | else 2877 | { 2878 | // built-in default value is: null 2879 | value[key] = null; 2880 | } 2881 | } 2882 | } 2883 | } 2884 | } 2885 | } 2886 | 2887 | return value; 2888 | }; 2889 | 2890 | /** 2891 | * Recursively frames the given input according to the given frame. 2892 | * 2893 | * @param subjects a map of subjects in the graph. 2894 | * @param input the input to frame. 2895 | * @param frame the frame to use. 2896 | * @param embeds a map of previously embedded subjects, used to prevent cycles. 2897 | * @param autoembed true if auto-embed is on, false if not. 2898 | * @param parent the parent object (for subframing), null for none. 2899 | * @param parentKey the parent key (for subframing), null for none. 2900 | * @param options the framing options. 2901 | * 2902 | * @return the framed input. 2903 | */ 2904 | var _frame = function( 2905 | subjects, input, frame, embeds, autoembed, parent, parentKey, options) 2906 | { 2907 | var rval = null; 2908 | 2909 | // prepare output, set limit, get array of frames 2910 | var limit = -1; 2911 | var frames; 2912 | if(frame.constructor === Array) 2913 | { 2914 | rval = []; 2915 | frames = frame; 2916 | if(frames.length === 0) 2917 | { 2918 | frames.push({}); 2919 | } 2920 | } 2921 | else 2922 | { 2923 | frames = [frame]; 2924 | limit = 1; 2925 | } 2926 | 2927 | // iterate over frames adding input matches to list 2928 | var values = []; 2929 | for(var i = 0; i < frames.length && limit !== 0; ++i) 2930 | { 2931 | // get next frame 2932 | frame = frames[i]; 2933 | if(frame.constructor !== Object) 2934 | { 2935 | throw { 2936 | message: 'Invalid JSON-LD frame. ' + 2937 | 'Frame must be an object or an array.', 2938 | frame: frame 2939 | }; 2940 | } 2941 | 2942 | // create array of values for each frame 2943 | values[i] = []; 2944 | for(var n = 0; n < input.length && limit !== 0; ++n) 2945 | { 2946 | // add input to list if it matches frame specific type or duck-type 2947 | var next = input[n]; 2948 | if(_isType(next, frame) || _isDuckType(next, frame)) 2949 | { 2950 | values[i].push(next); 2951 | --limit; 2952 | } 2953 | } 2954 | } 2955 | 2956 | // for each matching value, add it to the output 2957 | for(var i1 in values) 2958 | { 2959 | for(var i2 in values[i1]) 2960 | { 2961 | frame = frames[i1]; 2962 | var value = values[i1][i2]; 2963 | 2964 | // if value is a subject, do subframing 2965 | if(_isSubject(value)) 2966 | { 2967 | value = _subframe( 2968 | subjects, value, frame, embeds, autoembed, 2969 | parent, parentKey, options); 2970 | } 2971 | 2972 | // add value to output 2973 | if(rval === null) 2974 | { 2975 | rval = value; 2976 | } 2977 | else 2978 | { 2979 | // determine if value is a reference to an embed 2980 | var isRef = (_isReference(value) && value['@id'] in embeds); 2981 | 2982 | // push any value that isn't a parentless reference 2983 | if(!(parent === null && isRef)) 2984 | { 2985 | rval.push(value); 2986 | } 2987 | } 2988 | } 2989 | } 2990 | 2991 | return rval; 2992 | }; 2993 | 2994 | /** 2995 | * Frames JSON-LD input. 2996 | * 2997 | * @param input the JSON-LD input. 2998 | * @param frame the frame to use. 2999 | * @param options framing options to use. 3000 | * 3001 | * @return the framed output. 3002 | */ 3003 | Processor.prototype.frame = function(input, frame, options) 3004 | { 3005 | var rval; 3006 | 3007 | // normalize input 3008 | input = jsonld.normalize(input); 3009 | 3010 | // save frame context 3011 | var ctx = null; 3012 | if('@context' in frame) 3013 | { 3014 | ctx = _clone(frame['@context']); 3015 | 3016 | // remove context from frame 3017 | frame = jsonld.expand(frame); 3018 | } 3019 | else if(frame.constructor === Array) 3020 | { 3021 | // save first context in the array 3022 | if(frame.length > 0 && '@context' in frame[0]) 3023 | { 3024 | ctx = _clone(frame[0]['@context']); 3025 | } 3026 | 3027 | // expand all elements in the array 3028 | var tmp = []; 3029 | for(var i in frame) 3030 | { 3031 | tmp.push(jsonld.expand(frame[i])); 3032 | } 3033 | frame = tmp; 3034 | } 3035 | 3036 | // create framing options 3037 | // TODO: merge in options from function parameter 3038 | options = 3039 | { 3040 | defaults: 3041 | { 3042 | embedOn: true, 3043 | explicitOn: false, 3044 | omitDefaultOn: false 3045 | } 3046 | }; 3047 | 3048 | // build map of all subjects 3049 | var subjects = {}; 3050 | for(var i in input) 3051 | { 3052 | subjects[input[i]['@id']] = input[i]; 3053 | } 3054 | 3055 | // frame input 3056 | rval = _frame(subjects, input, frame, {}, false, null, null, options); 3057 | 3058 | // apply context 3059 | if(ctx !== null && rval !== null) 3060 | { 3061 | rval = jsonld.compact(ctx, rval); 3062 | } 3063 | 3064 | return rval; 3065 | }; 3066 | 3067 | })(); 3068 | --------------------------------------------------------------------------------