├── www ├── root │ ├── test │ │ ├── cert.php │ │ ├── x509.php │ │ └── webid.php │ ├── webid.js.php │ ├── favicon.ico │ ├── json │ │ ├── .htaccess │ │ └── handler.php │ ├── common │ │ ├── images │ │ │ ├── redo.gif │ │ │ ├── cancel.gif │ │ │ ├── check.gif │ │ │ ├── favicon.ico │ │ │ ├── pencil.gif │ │ │ ├── facebiblio.png │ │ │ ├── loginWebID.png │ │ │ ├── rdf_flyer.24.gif │ │ │ ├── load_bigroller.gif │ │ │ └── loginFacebook.png │ │ ├── js │ │ │ ├── manage.js │ │ │ └── common.js │ │ └── css │ │ │ ├── common.css │ │ │ └── blueprint.css │ ├── logout.php │ ├── runtime.php │ ├── user.js.php │ ├── proxy.php │ ├── .well-known │ │ ├── host-meta.php │ │ └── webfinger.php │ ├── yadis.php │ ├── create.php │ ├── rp_auth.php │ ├── s.php │ ├── index.php │ ├── rp_callback.php │ ├── login.php │ ├── manage.php │ └── help.php ├── wildcard │ ├── PATCH.php │ ├── common │ ├── MKCOL.php │ ├── index.php │ ├── input.php │ ├── PUT.php │ ├── .htaccess │ ├── POST.php │ ├── index.rdf.php │ ├── DELETE.php │ ├── output.php │ ├── exception.py │ ├── swobjects.py │ ├── index.html.php │ ├── runtime.php │ ├── SPARQL.py │ └── GET.php ├── inc │ ├── runtime.php │ ├── empty.php │ ├── 401.php │ ├── 403-404.php │ ├── contrib │ │ ├── skin.html.php │ │ └── jsonld.php │ ├── footer.php │ ├── webid.lib.php │ ├── header.php │ ├── app.lib.php │ ├── runtime.inc.php │ ├── util.lib.php │ └── rdf.lib.php └── .htaccess ├── Makefile ├── conf ├── php.ini ├── common.conf ├── ssl.conf └── httpd.conf └── LICENSE /www/root/test/cert.php: -------------------------------------------------------------------------------- 1 | x509.php -------------------------------------------------------------------------------- /www/root/webid.js.php: -------------------------------------------------------------------------------- 1 | user.js.php -------------------------------------------------------------------------------- /www/wildcard/PATCH.php: -------------------------------------------------------------------------------- 1 | POST.php -------------------------------------------------------------------------------- /www/wildcard/common: -------------------------------------------------------------------------------- 1 | ../root/common/ -------------------------------------------------------------------------------- /www/root/favicon.ico: -------------------------------------------------------------------------------- 1 | common/images/favicon.ico -------------------------------------------------------------------------------- /www/inc/runtime.php: -------------------------------------------------------------------------------- 1 | $v) { 6 | sess($k, null); 7 | } 8 | header('Location: /'); 9 | exit; 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | USER_SITE := $(shell python -c 'import site; print site.USER_SITE') 2 | 3 | install: ${USER_SITE}/rdf.pth 4 | 5 | ${USER_SITE}/rdf.pth: 6 | mkdir -p ${USER_SITE} 7 | echo ${PWD}/lib/python > $@ 8 | -------------------------------------------------------------------------------- /www/root/runtime.php: -------------------------------------------------------------------------------- 1 | load($i_uri); 8 | } 9 | 10 | require_once('../wildcard/GET.php'); 11 | -------------------------------------------------------------------------------- /www/inc/empty.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | 6 | AddHandler fcgid-script .php 7 | AddHandler fcgid-script .py 8 | DirectoryIndex index.php 9 | 10 | Options +ExecCGI 11 | 12 | 13 | Options +ExecCGI 14 | 15 | 16 | -------------------------------------------------------------------------------- /conf/common.conf: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | Alias /assets /home/data.fm/www/assets 4 | 5 | AllowOverride All 6 | 7 | 8 | #FcgidMaxProcessesPerClass 16 9 | #FcgidMaxRequestsPerProcess 10000 10 | IPCCommTimeout 300 11 | MaxRequestLen 1310720 12 | 13 | RewriteEngine On 14 | SuexecUserGroup data.fm data.fm 15 | -------------------------------------------------------------------------------- /conf/ssl.conf: -------------------------------------------------------------------------------- 1 | # $Id: ssl.conf -1 $ 2 | 3 | SSLEngine on 4 | SSLCertificateChainFile /etc/pki/tls/certs/StartSSLCAClass2.pem 5 | SSLCertificateFile /etc/pki/tls/certs/data.fm.cer 6 | SSLCertificateKeyFile /etc/pki/tls/private/data.fm.key 7 | #SSLOptions +StdEnvVars +ExportCertData 8 | SSLVerifyClient optional_no_ca 9 | 10 | 11 | AuthType WebID 12 | Require everyone 13 | AuthWebIDAuthoritative off 14 | 15 | -------------------------------------------------------------------------------- /www/root/.well-known/host-meta.php: -------------------------------------------------------------------------------- 1 | '; 6 | ?> 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/root/.well-known/webfinger.php: -------------------------------------------------------------------------------- 1 | '; 6 | $i_q = isset($_GET['q']) ? $_GET['q'] : exit(); 7 | if (false !== ($x = strpos($i_q, ':'))) 8 | $i_q = substr($i_q, $x+1); 9 | ?> 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /www/root/yadis.php: -------------------------------------------------------------------------------- 1 | '; 12 | ?> 13 | 17 | 18 | 19 | http://specs.openid.net/auth/2.0/return_to 20 | /rp_callback 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /www/wildcard/index.php: -------------------------------------------------------------------------------- 1 | $claim, 18 | 'lookup' => array( 19 | 'uri' => $i_uri, 20 | 'triples' => $g->size(), 21 | 'results' => $query 22 | ), 23 | 'verified' => webid_verify() 24 | ); 25 | print_r($r); 26 | -------------------------------------------------------------------------------- /www/root/create.php: -------------------------------------------------------------------------------- 1 | <#owner> <$_user>"; 18 | $turtle .= "; <#aclRead> "; 19 | $turtle .= "; <#aclWrite> "; 20 | $turtle .= '.'; 21 | 22 | //TODO: locking 23 | if (sites\is_available($i_name)) { 24 | @mkdir($_ENV['CLOUD_DATA'].'/'.substr($domain_uri, 4)); 25 | $sites->append('turtle', $turtle); 26 | } 27 | 28 | header('Location: /manage'); 29 | -------------------------------------------------------------------------------- /www/inc/401.php: -------------------------------------------------------------------------------- 1 | 10 |
11 | You must login to access this URL 12 |
13 | 14 |
15 | If you just installed a new SSL certificate, try restarting your browser to trigger its selection 16 |
17 | 18 |
19 | Firefox 3.5+, currently shipping Safari, IE, and Chrome builds are known to work 20 |
21 | 22 | 10 |
The requested URI is inaccessible or does not exist.
11 |
12 |
'; 26 | TAG(__FILE__, __LINE__, '$Id$'); 27 | defined('FOOTER') || include_once('footer.php'); 28 | -------------------------------------------------------------------------------- /www/inc/contrib/skin.html.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 |
19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /www/root/rp_auth.php: -------------------------------------------------------------------------------- 1 | $_SERVER['REMOTE_ADDR'], 14 | 'continueUrl' => REQUEST_BASE.'/rp_callback', 15 | 'identifier' => strtolower($i_provider).'.com', 16 | ); 17 | $q = http('POST', $url, json_encode($q)); 18 | if ($q->status == 200) { 19 | $q = json_decode($q->body); 20 | if (isset($q->authUri)) { 21 | header('Location: '.$q->authUri); 22 | exit; 23 | } 24 | } else { 25 | $q = json_decode($q->body); 26 | if (isset($q->error)) 27 | echo $q->error->message; 28 | } 29 | -------------------------------------------------------------------------------- /www/wildcard/input.php: -------------------------------------------------------------------------------- 1 | 'turtle', 14 | '/n3' => 'turtle', 15 | '/nt' => 'ntriples', 16 | '/rdf+n3' => 'turtle', 17 | '/rdf+nt' => 'ntriples', 18 | '/rdf+xml' => 'rdfxml', 19 | '/rdf' => 'rdfxml', 20 | '/html' => 'rdfa', 21 | '/xhtml' => 'rdfa', 22 | '/rss+xml' => 'rss-tag-soup', 23 | '/rss' => 'rss-tag-soup', 24 | '/json' => 'json', 25 | '/json-ld' => 'json-ld', 26 | ); 27 | 28 | // negotiation: process HTTP Content-Type 29 | $_input = ''; 30 | foreach ($_content_type_map as $needle=>$input) { 31 | if (strstr($_content_type, $needle) !== FALSE) { 32 | $_input = $input; 33 | break; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /www/root/s.php: -------------------------------------------------------------------------------- 1 | false); 25 | if (strlen($i_name)) { 26 | $r['id'] = $i_name; 27 | $r['available'] = \sites\is_available($i_name); 28 | } 29 | echo json_encode($r); 30 | } elseif ($_method == 'DELETE') { 31 | header('Content-Type: text/javascript'); 32 | if ($i_name && substr($i_name, -1*strlen($_ENV['CLOUD_BASE']))!=$_ENV['CLOUD_BASE']) { 33 | $i_name = $i_name . $_ENV['CLOUD_BASE']; 34 | } 35 | $r = $sites->remove_any("dns:$i_name"); 36 | echo 'cloud.refresh();'; 37 | } 38 | -------------------------------------------------------------------------------- /www/wildcard/PUT.php: -------------------------------------------------------------------------------- 1 | clobber && $g->exists()) 31 | httpStatusExit(409, 'Resource Exists', null, 'First DELETE the resource or set X-Options: clobber'); 32 | $g->truncate(); 33 | if (!empty($_input) && $g->append($_input, $_data)) { 34 | librdf_php_last_log_level() && httpStatusExit(400, 'Bad Request', null, librdf_php_last_log_message()); 35 | $g->save(); 36 | } else { 37 | httpStatusExit(406, 'Content-Type Not Acceptable'); 38 | } 39 | 40 | @header('X-Triples: '.$g->size()); 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010 by Joe Presbrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /www/root/common/css/common.css: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | 3 | body { line-height: 1.25; } 4 | a { text-decoration: underline; vertical-align: top; } 5 | hr { background-color: silver; } 6 | 7 | input { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius:3px; } 8 | div { border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius:10px; } 9 | 10 | address { color: gray; } 11 | #codeID { float: left; } 12 | #codeID a { color: gray; } 13 | #codeTime { float: right; } 14 | 15 | input[type=submit], input[type=button] { 16 | border: 1px solid black; 17 | border-collapse: collapse; 18 | } 19 | #login input[type=submit] { 20 | background-color: #6078AB; 21 | color: white; 22 | font-weight: bold; 23 | } 24 | div.span-icon { 25 | height: 16px; 26 | width: 16px; 27 | vertical-align: middle; 28 | } 29 | #identity { float: right; } 30 | #identity a { text-decoration: none; } 31 | #login { float: right; clear: right; } 32 | #status { float: left; text-align: center; width: 32px; } 33 | #status img { vertical-align: middle; } 34 | #title { float: left; } 35 | #welcome { float: left; clear: left; } 36 | 37 | 38 | .box { border-style: outset; } 39 | .cleft { clear: left; } 40 | .cright { clear: right; } 41 | -------------------------------------------------------------------------------- /www/wildcard/.htaccess: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | # headers: HTTP Access-Control 4 | Header set "Access-Control-Allow-Credentials" "true" 5 | Header set "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS" 6 | 7 | # headers: HTTP Caching 8 | #Header set "Cache-Control" "no-store, no-cache, must-revalidate, post-check=0, pre-check=0" 9 | #Header set "Pragma" "no-cache" 10 | 11 | # headers: SPARQL 12 | Header set "MS-Author-Via" "SPARQL" 13 | 14 | # rewrite 15 | 16 | RewriteEngine On 17 | #RewriteMap lowercase int:tolower 18 | #RewriteRule ^/(.*)$ /srv/clouds/${lowercase:%{SERVER_NAME}}/$1 19 | 20 | RewriteCond %{REQUEST_METHOD} =GET [OR] 21 | RewriteCond %{REQUEST_METHOD} =HEAD [OR] 22 | RewriteCond %{REQUEST_METHOD} =OPTIONS 23 | RewriteCond %{REQUEST_URI} !/common/ 24 | RewriteRule ^(.*)$ GET.php [L] 25 | 26 | RewriteCond %{REQUEST_METHOD} =POST 27 | RewriteCond %{HTTP:Content-Type} =application/sparql-query 28 | RewriteRule ^(.*)$ SPARQL.py [L] 29 | 30 | RewriteCond %{REQUEST_METHOD} =MKCOL [OR] 31 | RewriteCond %{REQUEST_METHOD} =PATCH [OR] 32 | RewriteCond %{REQUEST_METHOD} =POST [OR] 33 | RewriteCond %{REQUEST_METHOD} =PUT [OR] 34 | RewriteCond %{REQUEST_METHOD} =DELETE 35 | RewriteRule ^(.*)$ %{REQUEST_METHOD}.php [L] 36 | 37 | # drop all other Apache responses 38 | #RewriteRule .* - [L,R=501] 39 | -------------------------------------------------------------------------------- /conf/httpd.conf: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | MaxRequestsPerProcess 1000 4 | 5 | 6 | ServerName data.fm 7 | Include /home/data.fm/conf/common.conf 8 | DocumentRoot /home/data.fm/www/root 9 | 10 | 11 | ServerName data.fm 12 | Include /home/data.fm/conf/common.conf 13 | Include /home/data.fm/conf/ssl.conf 14 | DocumentRoot /home/data.fm/www/root 15 | LogLevel info 16 | 17 | 18 | 19 | ServerName www.data.fm 20 | Include /home/data.fm/conf/common.conf 21 | RewriteRule (.*) http://data.fm$1 [R,L] 22 | 23 | 24 | ServerName www.data.fm 25 | Include /home/data.fm/conf/common.conf 26 | Include /home/data.fm/conf/ssl.conf 27 | RewriteRule (.*) https://data.fm$1 [R,L] 28 | 29 | 30 | 31 | ServerName cloud.data.fm 32 | ServerAlias *.data.fm 33 | Include /home/data.fm/conf/common.conf 34 | DocumentRoot /home/data.fm/www/wildcard 35 | 36 | 37 | ServerName cloud.data.fm 38 | ServerAlias *.data.fm 39 | Include /home/data.fm/conf/common.conf 40 | Include /home/data.fm/conf/ssl.conf 41 | DocumentRoot /home/data.fm/www/wildcard 42 | 43 | -------------------------------------------------------------------------------- /www/root/index.php: -------------------------------------------------------------------------------- 1 | editui) { 13 | ?> 14 |
15 | Login with WebID, Facebook,
Gmail, AOL, or Yahoo:

16 |
17 | 18 |

19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 | patch_json($_data) || 1)) { 38 | librdf_php_last_log_level() && httpStatusExit(400, 'Bad Request', null, librdf_php_last_log_message()); 39 | $g->save(); 40 | } 41 | } elseif (!empty($_input) && ($g->append($_input, $_data) || 1)) { 42 | librdf_php_last_log_level() && httpStatusExit(400, 'Bad Request', null, librdf_php_last_log_message()); 43 | $g->save(); 44 | } else { 45 | httpStatusExit(406, 'Content-Type Not Acceptable'); 46 | } 47 | 48 | @header('X-Triples: '.$g->size()); 49 | -------------------------------------------------------------------------------- /www/inc/footer.php: -------------------------------------------------------------------------------- 1 | coderev) { 21 | ?> 22 |
23 |
24 | 30 | 37 | generated in s 38 | 1?'ies':'y', substr($sparql_t, 0, 6))?> 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /www/root/rp_callback.php: -------------------------------------------------------------------------------- 1 | $_SERVER['REMOTE_ADDR']); 13 | $q['requestUri'] = $continueUrl; 14 | $q['postBody'] = $response; 15 | $q = http('POST', 'https://www.googleapis.com/identitytoolkit/v1/relyingparty/verifyAssertion?key='.GAPIKEY, json_encode($q)); 16 | if ($q->status == 200) 17 | return json_decode($q->body, true); 18 | return array(); 19 | } 20 | 21 | $result = verify(REQUEST_URI, @file_get_contents('php://input')); 22 | $email = isset($result['verifiedEmail']) ? strtolower($result['verifiedEmail']) : ''; 23 | $name = isset($result['displayName']) ? $result['displayName'] : ''; 24 | $firstName = isset($result['firstName']) ? $result['firstName'] : ''; 25 | $lastName = isset($result['lastName']) ? $result['lastName'] : ''; 26 | if (strlen($email)) { 27 | sess('u:id', "mailto:$email"); 28 | sess('u:link', "mailto:$email"); 29 | sess('u:name', strlen($name) ? $name : $email); 30 | } 31 | 32 | $next = sess('next', null); 33 | if (!is_string($next) || !strlen($next)) 34 | $next = '/login'; 35 | ?> 36 | 45 | -------------------------------------------------------------------------------- /www/wildcard/index.rdf.php: -------------------------------------------------------------------------------- 1 | '; 42 | $mtime = filemtime("$_filename/$item"); 43 | $size = filesize("$_filename/$item"); 44 | $g->append('turtle', "@prefix p: . <$item_elt> a $item_type ; p:mtime $mtime ; p:size $size ."); 45 | } 46 | -------------------------------------------------------------------------------- /www/wildcard/DELETE.php: -------------------------------------------------------------------------------- 1 | remove_any($any_s) : 0; 44 | header('X-Triples: '.$r); 45 | if ($r) 46 | $g->save(); 47 | exit; 48 | } 49 | 50 | if (is_dir($_filename)) { 51 | if ($_options->recursive) 52 | rrmdir($_filename); 53 | else 54 | rmdir($_filename); 55 | } elseif (file_exists($_filename)) { 56 | unlink($_filename); 57 | } else { 58 | $g = new \RDF\Graph('', $_filename, '', ''); 59 | if ($g->exists()) { 60 | $g->delete(); 61 | } else { 62 | httpStatusExit(404, 'Not Found'); 63 | } 64 | } 65 | 66 | if (file_exists($_filename)) 67 | httpStatusExit(409, 'Conflict'); 68 | -------------------------------------------------------------------------------- /www/root/login.php: -------------------------------------------------------------------------------- 1 | NULL)); 20 | echo ""; 21 | } elseif (isset($i_id) && $i_id == 'facebook' && isset($i_session)) { 22 | $i_session = str_replace('\\', '', $i_session); 23 | $session = json_decode($i_session, true); 24 | if (isset($session['access_token'])) { 25 | $q = json_decode(file_get_contents('https://graph.facebook.com/me?fields=id,name,picture,link,username,email&access_token='.$session['access_token']), true); 26 | if (isset($q['id'])) { 27 | sess('f:id', $q['id']); 28 | sess('f:access_expires', $session['expires']); 29 | sess('f:access_token', $session['access_token']); 30 | sess('u:name', $q['name']); 31 | sess('u:link', $q['link']); 32 | $q['id'] = 'https://graph.facebook.com/'.$q['id']; 33 | sess('u:id', $q['id']); 34 | $sites->append('turtle', "<{$q['id']}> ."); 35 | } 36 | } 37 | header('Location: '.REQUEST_BASE.'/login'); 38 | } elseif (!$_user && !isHTTPS()) { 39 | header('Location: https://'.BASE_DOMAIN.$_options->base_url.'/login?'.newQSA()); 40 | } elseif (isSess('next')) { 41 | $next = sess('next', null); 42 | header('Location: '.$next); 43 | } elseif ($_user) { 44 | header('Location: '.REQUEST_BASE.'/manage'); 45 | } else { 46 | require_once('401.php'); 47 | } 48 | -------------------------------------------------------------------------------- /www/inc/webid.lib.php: -------------------------------------------------------------------------------- 1 | SELECT(sprintf("PREFIX : SELECT ?m ?e WHERE { <%s> :key [ :modulus ?m; :exponent ?e; ] . }", $uri)); 32 | if (isset($q['results']) && isset($q['results']['bindings'])) 33 | $r = $q['results']['bindings']; 34 | return $r; 35 | } 36 | 37 | function webid_verify() { 38 | $q = webid_claim(); 39 | if (isset($q['uri'])) { 40 | foreach (webid_query($q['uri']) as $elt) { 41 | if ($q['e'] == $elt['e']['value'] && $q['m'] == strtolower(preg_replace('/[^0-9a-fA-F]/', '', $elt['m']['value']))) { 42 | return $q['uri']; 43 | } 44 | } 45 | } 46 | return ''; 47 | } 48 | -------------------------------------------------------------------------------- /www/wildcard/output.php: -------------------------------------------------------------------------------- 1 | 'json-ld', 26 | '/json' => 'json', 27 | '/turtle' => 'turtle', 28 | '/n3' => 'turtle', 29 | '/nt' => 'ntriples', 30 | '/rdf+n3' => 'turtle', 31 | '/rdf+nt' => 'ntriples', 32 | '/rdf+xml' => 'rdfxml-abbrev', 33 | '/rdf' => 'rdfxml-abbrev', 34 | '/atom+xml' => 'atom', 35 | '/rss+xml' => 'rss-1.0', 36 | '/rss' => 'rss-1.0', 37 | '/dot' => 'dot', 38 | '/csv' => 'csv', 39 | '/tsv' => 'tsv', 40 | '/tab-separated-values' => 'tsv', 41 | '/html' => 'html' 42 | ); 43 | 44 | $_output = ''; 45 | $_output_type = null; 46 | foreach ($_accept_list as $haystack) { 47 | foreach ($_accept_type_map as $needle=>$output) { 48 | if (strstr($haystack, $needle) !==FALSE) { 49 | $_output = $output; 50 | $_output_type = $haystack; 51 | break; 52 | } 53 | } 54 | if (!empty($_output)) break; 55 | } 56 | if (empty($_output)) 57 | foreach (array_keys($_accept_data) as $haystack) { 58 | foreach ($_accept_type_map as $needle=>$output) { 59 | if (strstr($haystack, $needle) !==FALSE) { 60 | $_output = $output; 61 | $_output_type = $haystack; 62 | break; 63 | } 64 | } 65 | if (!empty($_output)) break; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /www/root/manage.php: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

new cloud

21 |
22 |
choose a name: (at least 4 chars)
23 |
24 | 25 | 26 |   27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |

your clouds

40 |

None found.

'; 49 | echo ''.$site.''; 50 | echo ''; 51 | echo '
'; 52 | echo '
'; 53 | } 54 | } 55 | ?> 56 | 57 | 58 | 59 | 60 | 3 | # email exception hook 4 | 5 | import sys 6 | from traceback import format_exc as _format_exc 7 | 8 | #def format_exc(exc=None): 9 | # """Return exc (or sys.exc_info if None), formatted.""" 10 | # if exc is None: 11 | # exc = sys.exc_info() 12 | # if exc == (None, None, None): 13 | # return "" 14 | # import traceback 15 | # return "".join(traceback.format_exception(*exc)) 16 | 17 | def _excepthook(*argv): 18 | handler(tuple(argv)) 19 | 20 | from smtplib import SMTP as _SMTP 21 | from threading import currentThread as _currentThread 22 | 23 | def handler(exc=None, passthru=True): 24 | if exc is None: 25 | exc = sys.exc_info() 26 | TEXT = _format_exc(exc) 27 | try: 28 | _thread = _currentThread() 29 | TEXT = ("Exception in thread %s:\n" % _thread.name) + TEXT 30 | except: pass 31 | CMD = len(sys.argv) > 1 and sys.argv[1].endswith('.py') and sys.argv[1] or sys.argv[0] 32 | SUBJECT = CMD+': '+str(exc[1]).replace('\r','').replace('\n','') 33 | HEADERS = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (EMAIL_FROM, EMAIL_TO, SUBJECT) 34 | _SMTP('localhost').sendmail(EMAIL_FROM, [EMAIL_TO], HEADERS+TEXT) 35 | if passthru: 36 | sys.__excepthook__(*exc) 37 | return TEXT 38 | 39 | _Thread_run_ = None 40 | def _Thread_run(self): 41 | try: 42 | _Thread_run_(self) 43 | except Exception, e: 44 | handler(None) 45 | 46 | def install(to): 47 | import os 48 | sys.excepthook = _excepthook 49 | global _Thread_run_, EMAIL_FROM, EMAIL_TO 50 | from threading import Thread 51 | _Thread_run_, Thread.run = Thread.run, _Thread_run 52 | try: 53 | EMAIL_FROM = os.getlogin() 54 | except: 55 | _f = os.path.abspath(__file__) 56 | if _f.startswith('/home/'): 57 | EMAIL_FROM = _f.split('/')[2] 58 | elif _f.startswith('/srv/'): 59 | EMAIL_FROM = 'root' 60 | else: 61 | EMAIL_FROM = 'nobody' 62 | del _f 63 | try: 64 | EMAIL_FROM += '@'+os.getenv('HOSTNAME') 65 | except: 66 | import socket 67 | EMAIL_FROM += '@'+socket.gethostname() 68 | EMAIL_TO = to 69 | 70 | -------------------------------------------------------------------------------- /www/inc/header.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | <?=$_SERVER['SERVER_NAME']?>: <?=$TITLE?> 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 29 | 33 |

:

34 | '; 39 | echo ''; 40 | echo '

', sess('u:name'), '

'; 41 | echo '
'; 42 | } 43 | ?> 44 | 4 | # 5 | # $Id$ 6 | 7 | import SWObjects as _SWObjects 8 | 9 | class DefaultGraph(object): 10 | """provides a single, default graph for all operations""" 11 | def __init__(self, base_uri=''): 12 | self._base_uri = base_uri 13 | self._factory = _SWObjects.AtomFactory() 14 | self._database = _SWObjects.RdfDB() 15 | 16 | def append(self, data, base_uri=None, media_type='text/turtle'): 17 | if base_uri is None: base_uri = self._base_uri 18 | _T = _SWObjects.TurtleSDriver(base_uri, self._factory) 19 | _T.setGraph(self._database.ensureGraph(_SWObjects.cvar.DefaultGraph)) 20 | _T.parse(_SWObjects.IStreamContext(data, _SWObjects.StreamContextIstream.STRING)) 21 | return self 22 | 23 | def sparql(self, query, base_uri=None): 24 | if base_uri is None: base_uri = self._base_uri 25 | _S = _SWObjects.SPARQLfedDriver(base_uri, self._factory) 26 | _S.parse(_SWObjects.IStreamContext(query, _SWObjects.StreamContextIstream.STRING)) 27 | _S.root.execute(self._database, _SWObjects.ResultSet(self._factory)) 28 | return self 29 | 30 | def toMediaType(self, media_type=None): 31 | if media_type is None: media_type = 'text/turtle' 32 | return self._database.toString(_SWObjects.MediaType(media_type)) 33 | def __str__(self): return self.toMediaType() 34 | 35 | def write(self, path, media_type=None): 36 | file(path, 'w').write(self.toMediaType(media_type)) 37 | return self 38 | 39 | def test(): 40 | import logging 41 | graph = DefaultGraph('http://test/') 42 | for turtle in (' .', 43 | ' .'): 44 | print '# APPEND: '+turtle.replace('\n','') 45 | print graph.append(turtle) 46 | for sparql in ('INSERT { }',): 47 | print '# SPARQL: '+sparql.replace('\n','') 48 | print graph.sparql(sparql) 49 | 50 | def main(*argv, **kw): 51 | acl = """ 52 | @prefix acl: . 53 | 54 | [] 55 | a acl:Authorization; 56 | acl:accessTo <.> ; 57 | acl:defaultForNew <.> ; 58 | acl:agent ; 59 | acl:agentClass ; 60 | acl:mode acl:Control, acl:Read, acl:Write .""" 61 | dg = DefaultGraph(*argv) 62 | dg.append(acl) 63 | print dg.toMediaType() 64 | 65 | F = _SWObjects.AtomFactory() 66 | base_uri = '' 67 | query = "PREFIX rdf: PREFIX acl: SELECT ?rule WHERE { ?rule rdf:type acl:Authorization ; acl:accessTo ; acl:mode acl:Read ; acl:agent . }" 68 | sparser = _SWObjects.SPARQLfedDriver(base_uri, F) 69 | sparser.parse(_SWObjects.IStreamContext(query, _SWObjects.StreamContextIstream.STRING)) 70 | s = _SWObjects.SPARQLSerializer() 71 | sparser.root.express(s) 72 | print 'SPARQL:', s.str() 73 | 74 | if __name__ == '__main__': 75 | test() 76 | import sys 77 | print '# MAIN' 78 | main(*sys.argv[1:]) 79 | 80 | -------------------------------------------------------------------------------- /www/inc/app.lib.php: -------------------------------------------------------------------------------- 1 | 3; 13 | $r = $r && !in_array($name, array("1234","12345","123456","12345678","654321","admin","administrador","administrateur","administrator","asdf","asdfgh","audio","backlinks","beta","betas","blog","blogs","cache","calendar","calendars","close","cloud","computer","computers","conn","contact","contacts","create","data","database","databases","default","delete","diary","domain","domains","edit","events","facebook","favorite","favorites","forum","forums","free","friend","friends","gallery","gates","google","guest","guests","guestbook","history","info","information","intro","invite","inviter","json","link","linked","linux","live","load","login","love","mail","manage","management","manager","mysql","open","oracle","owner","pass","passwd","password","passwords","photo","photos","post","posts","private","profile","profiles","proxy","public","qwer","read","register","remove","root","schedule","schema","secret","secrets","secure","server","servers","software","source","sources","sparql","standard","student","subscribe","sudo","support","sysop","teacher","temp","test","tests","update","updates","user","users","video","videos","view","views","webid","webids","weblog","webmaster","wiki","wikis","write","wwwadmin")); 14 | return $r; 15 | } 16 | function is_available($name) { 17 | if (!is_valid($name)) 18 | return false; 19 | global $sites; 20 | $domain = "$name.".BASE_DOMAIN; 21 | $r = false; 22 | $q = "SELECT ?o WHERE { <#owner> ?o }"; 23 | $q = $sites->SELECT($q); 24 | if (isset($q['results']['bindings'])) 25 | $r = count($q['results']['bindings']) < 1; 26 | return $r; 27 | } 28 | function is_owner($domain, $uri) { 29 | if ($uri == 'dns:::1') return true; 30 | global $sites; 31 | $r = false; 32 | $q = "SELECT * WHERE { <#owner> <$uri> }"; 33 | $q = $sites->SELECT($q); 34 | if (isset($q['results']['bindings'])) 35 | $r = count($q['results']['bindings']) > 0; 36 | return $r; 37 | } 38 | function created_by($uri) { 39 | global $sites; 40 | $r = array(); 41 | $q = $sites->SELECT("SELECT ?site WHERE { ?site <#owner> <$uri> }"); 42 | if (isset($q['results']['bindings'])) { 43 | foreach ($q['results']['bindings'] as $row) { 44 | $r[] = $row['site']['value']; 45 | } 46 | } 47 | return $r; 48 | } 49 | } 50 | namespace profile { 51 | function knows($uri, $force=false) { 52 | $r = sess('knows'); 53 | if (!$force && !is_null($r)) 54 | return $r; 55 | $user = new \RDF\Graph('uri', $uri); 56 | $d = $user->SELECT("SELECT ?knows WHERE { <$uri> ?knows }"); 57 | $d = $d['results']['bindings']; 58 | $r = array(); 59 | foreach ($d as $row) { 60 | $k = $row['knows']['value']; 61 | $r[$k] = \sites\created_by($k); 62 | } 63 | sess('knows', $r); 64 | sess('knows_TS', REQUEST_TIME); 65 | return $r; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /www/root/help.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | 18 | 19 |

This web data platform supports several generations of standards and recommendations:

20 |
    21 |
  • Web 1 and 2: DAV, AJAX, JSONP, CORS
  • 22 |
  • Read/Write Linked Data, RDF/XML/JSON content negotiation, SPARQL 1.1, and WebID
  • 23 |
24 | 25 |

All endpoints interpret the HTTP request URI as the base URI for RDF operations and the default-graph URI for SPARQL operations.

26 |

Specify the media type of your request data with a Content-Type HTTP header.
27 | Specify your response type preference with an Accept HTTP header.

28 | 29 |
30 |

Request methods:

31 |
    32 |
  • Read: GET, HEAD, OPTIONS
  • 33 |
  • Write: PUT, MKCOL, DELETE
  • 34 |
  • Append: POST
  • 35 |
  • Update: 36 |
      37 |
    • JSON PATCH (application/json)
    • 38 |
    • SPARQL POST (*/sparql-query)
    • 39 |
    40 |
  • 41 |
42 |
43 | 44 |
45 |

Response types:

46 |
    47 |
  • Web (index.html, style.css, script.js)
  • 48 |
  • JSON (Accept */json)
  • 49 |
  • JSON-P (GET ?callback=)
  • 50 |
  • SPARQL JSON (GET/POST ?query=)
  • 51 |
  • RSS (Accept */rss+xml)
  • 52 |
  • Atom (Accept */atom+xml)
  • 53 |
54 |
55 | 56 |
57 |

RDF media types:

58 |
    59 |
  • JSON: application/json
  • 60 |
  • NTriples: */rdf+nt, */nt
  • 61 |
  • RDF/XML: */rdf+xml
  • 62 |
  • RDFa: */html, */xhtml
  • 63 |
  • Turtle: */turtle, */rdf+n3, */n3
  • 64 |
65 | (defaults to Turtle) 66 |
67 | 68 | 69 |
70 |
71 |
*/type
refers to a media type, specified in HTTP header (Accept or Content-Type)
72 |
name.ext
refers to a filename, specified by HTTP request URI
73 |
?k=v
refers to a query string parameter 'k' with value 'v': passed in URL via GET or application/x-www-form-urlencoded via POST
74 |
75 |

Some query string options and response (HTTP Accept) media types are complementary.

76 |
77 | 78 | 79 |

All uses of this service must comply with the MITnet rules of use.

80 | base_url = ''; 78 | $_options->clobber = false; 79 | $_options->coderev = true; 80 | $_options->debug = true; 81 | $_options->editui = true; 82 | $_options->glob = false; 83 | $_options->open = false; 84 | $_options->recursive = false; 85 | $_options->sqlite = false; 86 | if (file_exists(dirname(__FILE__).'/config.inc.php')) { 87 | require_once(dirname(__FILE__).'/config.inc.php'); 88 | } 89 | foreach (array('HTTP_OPTIONS', 'HTTP_X_OPTIONS') as $k0) 90 | if (isset($_SERVER[$k0])) 91 | foreach (explode(',',$_SERVER[$k0]) as $elt) { 92 | $k = strtolower(trim($elt)); 93 | $v = true; 94 | if ($k[0] == 'n' && $k[1] == 'o') { 95 | $k = substr($k, 2); 96 | $v = false; 97 | } 98 | if (in_array($k, array('open'))) continue; 99 | if (isset($_options->$k)) 100 | $_options->$k = $v; 101 | } 102 | 103 | # ensure user props 104 | if (sess('u:id')) { 105 | if (!isSess('u:link')) sess('u:link', $_user); 106 | if (!isSess('u:name')) { 107 | $_user_name = basename($_user); 108 | $c = strpos($_user_name, ':'); 109 | if ($c > 0) 110 | $_user_name = substr($_user_name, $c+1); 111 | sess('u:name', $_user_name); 112 | } 113 | } 114 | 115 | TAG(__FILE__, __LINE__, '$Id$'); 116 | -------------------------------------------------------------------------------- /www/wildcard/index.html.php: -------------------------------------------------------------------------------- 1 | editui)) $_options->editui = true; 13 | if ($_options->editui) { 14 | ?> 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | '; 81 | echo ''; 82 | echo ''; 83 | } 84 | ?> 85 | 86 | editui) { ?> 87 | 88 | 89 | 94 | 95 | 96 | 97 |
NameLast ModifiedSize
'; 53 | if ($_options->editui && !$is_dir) { 54 | echo ' '; 55 | } 56 | echo ''; 57 | echo '', $item_elt, ''; 58 | if ($item_ext == 'sqlite') 59 | echo ' (sqlite)'; 60 | echo ''; 61 | if ($is_dir) { 62 | echo 'Directory'; 63 | } elseif (in_array($item_ext, $_RAW_EXT)) { 64 | echo 'text/', $item_ext=='js'?'javascript':$item_ext; 65 | } elseif ($_options->editui) { 66 | echo 'text/turtle'; 67 | $i = 0; 68 | foreach (array( 69 | //'.json?callback=load'=>'JS', 70 | '.json'=>'JSON', 71 | '?query=SELECT+%2A+WHERE+%7B%3Fs+%3Fp+%3Fo%7D+LIMIT+10'=>'SPARQL', 72 | ) as $ext=>$label) { 73 | echo $i++ ? ', ' : ': '; 74 | printf('%s', $item_elt, $ext, $label); 75 | } 76 | } 77 | echo ''; 78 | if ($_options->editui) 79 | echo ''; 80 | echo ''.strftime('%F %X %Z', filemtime("$_filename/$item")).''.(!$is_dir?filesize("$_filename/$item"):'').'
90 | 91 | 92 | 93 |
98 | editui) { ?> 99 | 106 | debug) { 35 | header('Filename: '.$_filename); 36 | } 37 | 38 | // HTTP Access Control 39 | if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { 40 | header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']); 41 | } else { 42 | header('Access-Control-Allow-Headers: Content-Type, X-Prototype-Version, X-Requested-With'); 43 | } 44 | if (!isHTTPS()) { 45 | header('Access-Control-Allow-Origin: *'); 46 | } else { 47 | $_origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : ''; 48 | $t = explode('/', $_origin); 49 | if (count($t) > 2) { 50 | $_origin = "{$t[0]}//{$t[2]}"; 51 | } else { 52 | $_origin = '*'; 53 | } 54 | header('Access-Control-Allow-Origin: '.$_origin); 55 | } 56 | 57 | // Web Access Control 58 | header('Link: <'.$_options->base_url.'/.meta>; rel=meta'); 59 | $_metabase = $_filebase.$_options->base_url; 60 | $_acl = new \RDF\Graph('', file_exists("$_metabase/.meta.sqlite")?"$_metabase/.meta":"$_metabase/.meta", '', REQUEST_BASE.'/.meta'); 61 | function wac($method,$uri=null) { 62 | // method: Read/Write/Control 63 | global $_acl, $_user, $_base, $_options; 64 | if ($_options->open && !$_acl->size()) 65 | return true; 66 | $uri = is_null($uri) ? $_base : $uri; 67 | // strip trailing slash 68 | if (substr($uri, -1, 1) == '/') 69 | $uri = substr($uri, 0, -1); 70 | $p = $uri; 71 | // walk path 72 | while (true) { 73 | if (!strpos($p, '/')) break; 74 | $verb = $p == $uri ? 'accessTo' : 'defaultForNew'; 75 | // specific authorization 76 | $q = "PREFIX acl: SELECT * WHERE { ?z acl:agent <$_user>; acl:mode acl:$method; acl:$verb <$p> . }"; 77 | $r = $_acl->SELECT($q); 78 | if (isset($r['results']['bindings']) && count($r['results']['bindings']) > 0) 79 | return true; 80 | // public authorization 81 | $q = "PREFIX acl: SELECT * WHERE { ?z acl:agentClass ; acl:mode acl:$method; acl:$verb <$p> . }"; 82 | $r = $_acl->SELECT($q); 83 | if (isset($r['results']['bindings']) && count($r['results']['bindings']) > 0) 84 | return true; 85 | $p = dirname($p); 86 | } 87 | return false; 88 | } 89 | 90 | // HTTP Methods 91 | $_method = ''; 92 | foreach (array('REQUEST_METHOD', 'REDIRECT_REQUEST_METHOD') as $k) { 93 | if (isset($_SERVER[$k])) { 94 | $_method = strtoupper($_SERVER[$k]); 95 | break; 96 | } 97 | } 98 | if ($_method == 'OPTIONS') { 99 | header('HTTP/1.1 200 OK'); 100 | header('Allow: GET, PUT, POST, OPTIONS, HEAD, MKCOL, DELETE, PATCH'); 101 | header('Accept-Patch: application/json'); 102 | exit; 103 | } 104 | 105 | // HTTP Content Negotiation 106 | require_once('input.php'); 107 | require_once('output.php'); 108 | if (in_array($_filename_ext, $_RAW_EXT)) { 109 | $_input = 'raw'; 110 | $_output = 'raw'; 111 | $_output_type = 'text/'.($_filename_ext=='js'?'javascript':$_filename_ext); 112 | } 113 | -------------------------------------------------------------------------------- /www/wildcard/SPARQL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # SPARQL.py 3 | # SPARQL HTTP POST handler 4 | # 5 | # $Id$ 6 | 7 | # exception email sink 8 | import exception as _exception 9 | _exception.install('presbrey@csail.mit.edu') 10 | 11 | # assorted util 12 | def strtok(buf, tok): 13 | next = buf.partition(tok) 14 | if next[1] == tok: 15 | return next[0], next[2] 16 | else: 17 | return None, buf 18 | 19 | _elt_1 = lambda elt: elt[1] 20 | 21 | # Stream.IO Response wrapper 22 | class Response(object): 23 | def __init__(self, file, callback=None): 24 | self._f, self._cb = file, callback 25 | def __iter__(self): 26 | if self._cb: 27 | yield self._cb+'('+self._f.read()+');' 28 | else: 29 | yield self._f.read() 30 | 31 | # FCGI server 32 | from flup.server.fcgi import WSGIServer 33 | import os 34 | from subprocess import Popen, PIPE 35 | from swobjects import DefaultGraph 36 | from time import strftime 37 | from urlparse import parse_qs 38 | 39 | class Server(WSGIServer): 40 | def __init__(self): 41 | WSGIServer.__init__(self, self.application, debug=False) 42 | 43 | def _application(self, environ): 44 | self.devel = 'HTTP_X_DEVEL' in environ 45 | abs_path = '/srv/clouds/'+environ['SERVER_NAME']+environ['SCRIPT_URL'] 46 | method = environ['REQUEST_METHOD'].upper() 47 | 48 | # parse Content-Type 49 | content_type = environ.get('CONTENT_TYPE', '') 50 | # drop charset (Python will do the Right Thing) 51 | content_type = content_type.split(';')[0] 52 | 53 | # parse conneg preference list 54 | accept_lst = [] 55 | for elt in environ.get('HTTP_ACCEPT', '').split(','): 56 | t, q = strtok(elt, ';') 57 | if t is None: 58 | t, q = q, 'q=1.0' 59 | try: 60 | q = float(q[2:]) 61 | except Exception, e: 62 | continue 63 | accept_lst.append((t, q)) 64 | accept_lst = sorted(accept_lst, key=_elt_1, reverse=True) 65 | 66 | # compute base URI 67 | try: 68 | base_uri = environ['SCRIPT_URI'] 69 | except KeyError: 70 | base_uri = (environ.get('HTTPS') and 'https' or 'http')+'://'+environ['HTTP_HOST']+environ['REQUEST_URI'] 71 | 72 | r_status = '200 OK' 73 | r_headers = {} 74 | r_content = '' 75 | 76 | if method == 'POST': 77 | # HTTP read SPARQL query / turtle data 78 | data = environ['wsgi.input'].read() 79 | # FS ensure the directory path 80 | dir_path = os.path.dirname(abs_path) 81 | if not os.path.exists(dir_path): 82 | os.makedirs(dir_path) 83 | graph = DefaultGraph(base_uri) 84 | if os.path.exists(abs_path): 85 | graph.append(file(abs_path).read()) 86 | if content_type == 'application/sparql-query': 87 | # HTTP log the query 88 | print '[%s] [sparql] [client %s] [uri=%s]' % (strftime('%c'), environ['REMOTE_ADDR'], base_uri), data 89 | # SPARQL and write the new data 90 | graph.sparql(data).write(abs_path) 91 | else: 92 | r_status = '415 Unsupported Media Type' 93 | # DEVEL send the DefaultGraph 94 | if self.devel: 95 | r_content = [graph.toMediaType()] 96 | else: 97 | r_status = '501 Method Not Implemented' 98 | r_content = '' 99 | 100 | # see application notes below 101 | return r_status, [(x[0].title(), x[1]) for x in r_headers.items()], r_content 102 | 103 | def application(self, environ, start_response, *argv, **kw): 104 | r, exception = None, None 105 | try: 106 | r = self._application(environ, *argv, **kw) 107 | start_response(r[0], r[1]) 108 | # FCGI will output-buffer this iterable return type 109 | # eg. a string gets done 1 char at a time 110 | # rec. a list of strings 111 | return r[2] 112 | except Exception, e: 113 | exception = _exception.handler() 114 | start_response('500 Python Error', [('Content-Type', 'text/plain')]) 115 | return [exception] 116 | 117 | if __name__ == '__main__': 118 | Server().run() 119 | 120 | -------------------------------------------------------------------------------- /www/inc/util.lib.php: -------------------------------------------------------------------------------- 1 | 0; 12 | } 13 | function isSess($id) { return isset($_SESSION[$id]); } 14 | function sess($id,$val=NULL) { 15 | if (func_num_args()==1) { 16 | return (isSess($id)?$_SESSION[$id]:NULL); 17 | } elseif (is_null($val)) { 18 | $r = isset($_SESSION[$id]) ? $_SESSION[$id] : null; 19 | unset($_SESSION[$id]); 20 | return $r; 21 | } else { 22 | $prev = sess($id); 23 | $_SESSION[$id] = $val; 24 | return $prev; 25 | } 26 | } 27 | 28 | function newQS($key, $val=null) { return newQSA(array($key=>$val)); } 29 | function newQSA($array=array()) { 30 | parse_str($_SERVER['QUERY_STRING'], $arr); 31 | $s = count($arr); 32 | foreach($array as $key=>$val) { 33 | $arr[$key] = $val; 34 | if (is_null($val)) 35 | unset($arr[$key]); 36 | } 37 | return (count($arr)||$s)?'?'.http_build_query($arr):''; 38 | } 39 | 40 | function isHTTPS() { return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'); } 41 | 42 | function timings($query=null) { 43 | global $timingc; 44 | global $timings; 45 | 46 | if(!isset($timings)) { 47 | $timings = array(); 48 | } 49 | 50 | if (!isset($timingc) || empty($timingc)) { 51 | $timingc = 1; 52 | } elseif (!is_null($query)) { 53 | $timingc++; 54 | } 55 | $key = $timingc; 56 | 57 | if (is_null($query)) { 58 | $timings[$key]['time'] = microtime(true)-$timings[$key]['time']; 59 | if (function_exists('mysql_error') && mysql_error()) 60 | $timings[$key]['error'] = mysql_error(); 61 | return true; 62 | } else { 63 | $timings[$key] = array(); 64 | $timings[$key]['time'] = microtime(true); 65 | $timings[$key]['query'] = $query; 66 | return false; 67 | } 68 | } 69 | 70 | function httpStatusExit($status, $message, $require=null, $body=null) { 71 | global $_options, $TAGS; 72 | if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { 73 | $x_json = json_encode(array('status'=>$status,'message'=>$message)); 74 | } 75 | $status = (string)$status; 76 | header("HTTP/1.1 $status $message"); 77 | if (isset($x_json)) 78 | header('X-JSON: '.$x_json); 79 | if ($require) 80 | require_once($require); 81 | else 82 | echo "

$status $message

\n"; 83 | if ($body) 84 | echo "

$body

\n"; 85 | exit; 86 | } 87 | 88 | $TAGS = array(array( 89 | 'file' => __FILE__, 90 | 'line' => __LINE__, 91 | 'id' => null, 92 | 'time'=>microtime(true) 93 | )); 94 | function TAG($file, $line, $id) { 95 | global $TAGS; 96 | $TAGS[] = array( 97 | 'file' => $file, 98 | 'line' => $line, 99 | 'id' => $id, 100 | 'time' => microtime(true) 101 | ); 102 | } 103 | 104 | function http($method, $uri, $content=null) { 105 | $c = stream_context_create(array('http'=>array( 106 | 'method' => $method, 107 | 'header' =>"Connection: close\r\nContent-Type: application/json\r\nAccept: application/json\r\nReferer: https://{$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}\r\n", 108 | 'content' => $content, 109 | 'ignore_errors' => true, 110 | 'max_redirects' => 0, 111 | ))); 112 | $f = fopen($uri, 'r', false, $c); 113 | $h = stream_get_meta_data($f); 114 | $r = stream_get_contents($f); 115 | fclose($f); 116 | $status = 0; 117 | $header = array(); 118 | if (isset($h['wrapper_data'])) { 119 | $status = array_shift($h['wrapper_data']); 120 | $header['Status'] = $status; 121 | $lst = explode(' ', $status); 122 | if (count($lst) > 1) 123 | $status = (int)$lst[1]; 124 | foreach ($h['wrapper_data'] as $elt) { 125 | $i = strpos($elt, ': '); 126 | $k = substr($elt, 0, $i); 127 | if (!isset($header[$k])) 128 | $header[$k] = array(substr($elt, $i+2)); 129 | //elseif (!is_array($header[$k])) 130 | // $header[$k] = array($header[$k], substr($elt, $i+2)); 131 | else 132 | $header[$k][] = substr($elt, $i+2); 133 | } 134 | } 135 | return (object)array('uri'=>$uri, 'status'=>$status, 'header'=>$header, 'body'=>$r); 136 | } 137 | -------------------------------------------------------------------------------- /www/wildcard/GET.php: -------------------------------------------------------------------------------- 1 | base_url.'/favicon.ico'); 12 | exit; 13 | } 14 | 15 | if (!in_array($_method, array('GET', 'HEAD')) && !isset($i_query)) 16 | httpStatusExit(501, 'Not Implemented'); 17 | 18 | if (!file_exists($_filename) && in_array($_filename_ext, array('turtle','n3','json','rdf','nt','json-ld'))) { 19 | $_filename = substr($_filename, 0, -strlen($_filename_ext)-1); 20 | $_base = substr($_base, 0, -strlen($_filename_ext)-1); 21 | if ($_filename_ext == 'turtle' || $_filename_ext == 'n3') { 22 | $_output = 'turtle'; 23 | $_output_type = 'text/turtle'; 24 | } elseif ($_filename_ext == 'json') { 25 | $_output = 'json'; 26 | $_output_type = 'application/json'; 27 | } elseif ($_filename_ext == 'rdf') { 28 | $_output = 'rdfxml-abbrev'; 29 | $_output_type = 'application/rdf+xml'; 30 | } elseif ($_filename_ext == 'nt') { 31 | $_output = 'ntriples'; 32 | $_output_type = 'text/plain'; 33 | } elseif ($_filename_ext == 'json-ld') { 34 | $_output = 'json-ld'; 35 | $_output_type = 'application/json'; 36 | } 37 | } 38 | 39 | // permissions 40 | if (empty($_user)) 41 | httpStatusExit(401, 'Unauthorized', '401.php'); 42 | elseif (!wac('Read')) 43 | httpStatusExit(403, 'Forbidden', '403-404.php'); 44 | 45 | // directory indexing 46 | if (is_dir($_filename) || substr($_filename,-1) == '/') { 47 | if (substr($_filename, -1) != '/') { 48 | header("Location: $_base/"); 49 | exit; 50 | } elseif (!isset($_output) || empty($_output) || $_output == 'html') { 51 | foreach (array('index.html') as $index) { 52 | if (file_exists("$_filename/$index")) { 53 | include_once("$_filename/$index"); 54 | exit; 55 | } 56 | } 57 | include_once('index.html.php'); 58 | exit; 59 | } else { 60 | include_once('index.rdf.php'); 61 | } 62 | } 63 | 64 | // set default output 65 | if (empty($_output)) { 66 | $_output = 'turtle'; 67 | $_output_type = 'text/turtle'; 68 | } 69 | 70 | // output raw 71 | if ($_output == 'raw') { 72 | if ($_output_type) 73 | header("Content-Type: $_output_type"); 74 | if (!file_exists($_filename)) 75 | httpStatusExit(404, 'Not Found', '403-404.php'); 76 | if ($_method == 'GET') 77 | readfile($_filename); 78 | exit; 79 | } 80 | 81 | // tabulator data skin 82 | if ($_output == 'html') { 83 | include_once('contrib/skin.html.php'); 84 | exit; 85 | } 86 | 87 | // output RDF 88 | if (!isset($g)) 89 | $g = new \RDF\Graph('', $_filename, '', $_base); 90 | 91 | // *: glob 92 | if ($_options->glob && (strpos($_filename, '*') !== false || strpos($_filename, '{') !== false)) { 93 | foreach(glob($_filename, GLOB_BRACE|GLOB_NOSORT) as $item) { 94 | if (!substr($item, 0, strlen($_filebase)) == $_filebase) continue; 95 | $item_ext = strrchr($item, '.'); 96 | if ($item_ext == '.sqlite' || ($item_ext && in_array(substr($item_ext, 1), $_RAW_EXT))) continue; 97 | $item_uri = REQUEST_BASE.substr($item, strlen($_filebase)); 98 | $g->append_file('turtle', "file://$item", $item_uri); 99 | } 100 | } elseif (!empty($_filename) && !$g->exists() && !$g->size()) 101 | header('HTTP/1.1 404 Not Found'); 102 | 103 | if (isset($i_wait)) { 104 | $etag = (is_array($i_wait) && isset($i_wait['etag'])) ? $i_wait['etag'] : $g->etag(); 105 | while ($etag == $g->etag()) { 106 | sleep(1); 107 | clearstatcache(); 108 | } 109 | $g->reload(); 110 | } 111 | 112 | $etag = $g->etag(); 113 | if ($etag) 114 | header('ETag: '.$etag); 115 | 116 | header('X-Triples: '.$g->size()); 117 | if (isset($i_query)) 118 | header('Query: '.str_replace(array("\r","\n"), '', $i_query)); 119 | 120 | if (isset($i_callback)) { 121 | header('Content-Type: text/javascript'); 122 | if ($_method == 'GET') { 123 | if ($_output == 'json' || isset($i_query)) { 124 | echo $i_callback, '('; 125 | register_shutdown_function(function() { echo ');'; }); 126 | } else { 127 | echo $i_callback, '("'; 128 | register_shutdown_function(function() { echo '");'; }); 129 | } 130 | } 131 | } elseif (isset($i_query) || isset($i_any)) { 132 | header('Content-Type: application/json'); 133 | } else { 134 | header("Content-Type: $_output_type"); 135 | } 136 | 137 | if (in_array($_method, array('GET', 'POST'))) 138 | if (isset($i_any)) { 139 | echo json_encode($g->any( 140 | isset($i_any['s']) ? $i_any['s'] : null, 141 | isset($i_any['p']) ? $i_any['p'] : null 142 | )); 143 | } elseif (isset($i_query)) { 144 | echo $g->query_to_string($i_query, $_output, $_base); 145 | } else { 146 | echo $g->to_string($_output); 147 | } 148 | -------------------------------------------------------------------------------- /www/root/common/js/common.js: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | 3 | HTTP = Class.create(Ajax.Request, { 4 | request: function(url) { 5 | this.url = url; 6 | this.method = this.options.method; 7 | var params = Object.isString(this.options.parameters) ? 8 | this.options.parameters : 9 | Object.toQueryString(this.options.parameters); 10 | 11 | if (params) { 12 | if (this.method == 'get') 13 | this.url += (this.url.include('?') ? '&' : '?') + params; 14 | else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) 15 | params += '&_='; 16 | } 17 | 18 | this.parameters = params.toQueryParams(); 19 | 20 | try { 21 | var response = new Ajax.Response(this); 22 | if (this.options.onCreate) this.options.onCreate(response); 23 | Ajax.Responders.dispatch('onCreate', this, response); 24 | 25 | this.transport.open(this.method.toUpperCase(), this.url, 26 | this.options.asynchronous); 27 | 28 | if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); 29 | 30 | this.transport.onreadystatechange = this.onStateChange.bind(this); 31 | this.setRequestHeaders(); 32 | 33 | this.body = this.method == 'post' ? (this.options.postBody || params) : null; 34 | this.body = this.body || this.options.body || ''; 35 | this.transport.send(this.body); 36 | 37 | /* Force Firefox to handle ready state 4 for synchronous requests */ 38 | if (!this.options.asynchronous && this.transport.overrideMimeType) 39 | this.onStateChange(); 40 | 41 | } 42 | catch (e) { 43 | this.dispatchException(e); 44 | } 45 | } 46 | }); 47 | 48 | newJS = function(url, callback){ 49 | var script = document.createElement("script") 50 | script.async = true; 51 | script.type = "text/javascript"; 52 | script.src = url; 53 | if (callback) { 54 | if (script.readyState) { // IE 55 | script.onreadystatechange = function() { 56 | if (script.readyState == "loaded" || script.readyState == "complete") { 57 | script.onreadystatechange = null; 58 | callback(); 59 | } 60 | }; 61 | } else { // others 62 | script.onload = function() { 63 | callback(); 64 | }; 65 | } 66 | } 67 | return script; 68 | } 69 | 70 | cloud = {}; 71 | cloud.append = function(path, data) { 72 | data = data || '' 73 | new HTTP(this.request_url+path, { method: 'post', body: data, requestHeaders: {'Content-Type':'text/turtle'}, onSuccess: function() { 74 | window.location.reload(); 75 | }}); 76 | } 77 | cloud.get = function(path) { 78 | new HTTP(this.request_url+path, { method: 'get', evalJS: false, requestHeaders: {'Accept':'text/turtle'}, onSuccess: function(r) { 79 | $('editorpath').value = path; 80 | $('editorpath').enable(); 81 | $('editorarea').value = r.responseText; 82 | $('editorarea').enable(); 83 | $('editor').show(); 84 | }}); 85 | } 86 | cloud.mkdir = function(path) { 87 | new HTTP(this.request_url+path, { method: 'mkcol', onSuccess: function() { 88 | window.location.reload(); 89 | }}); 90 | } 91 | cloud.put = function(path, data) { 92 | new HTTP(this.request_url+path, { method: 'put', body: data, requestHeaders: {'Content-Type':'text/turtle', 'X-Options': 'clobber'}, onSuccess: function() { 93 | //window.location.reload(); 94 | }}); 95 | } 96 | cloud.rm = function(path) { 97 | new HTTP(this.request_url+path, { method: 'delete', onSuccess: function() { 98 | window.location.reload(); 99 | }}); 100 | } 101 | cloud.edit = function(path) { 102 | $('editorpath').value = ''; 103 | $('editorpath').disable(); 104 | $('editorarea').value = ''; 105 | $('editorarea').disable(); 106 | cloud.get(path); 107 | } 108 | cloud.save = function(elt) { 109 | var path = $('editorpath').value; 110 | var data = $('editorarea').value; 111 | cloud.put(path, data); 112 | } 113 | 114 | cloud.init = function(data) { 115 | var k; for (k in data) { this[k] = data[k]; } 116 | this.storage = {}; 117 | try { 118 | if ('localStorage' in window && window['localStorage'] !== null) 119 | this.storage = window.localStorage; 120 | } catch(e){} 121 | } 122 | cloud.refresh = function() { window.location.reload(); } 123 | cloud.remove = function(elt) { 124 | new Ajax.Request(this.request_base+'/json/'+elt, { method: 'delete' }); 125 | } 126 | cloud.updateStatus = function() { 127 | if (Ajax.activeRequestCount > 0) { 128 | $('statusLoading').show(); 129 | $('statusComplete').hide(); 130 | } else { 131 | $('statusComplete').show(); 132 | $('statusLoading').hide(); 133 | } 134 | } 135 | cloud.alert = function(message, cls) { 136 | if (message) { 137 | $('alertbody').update(message); 138 | if (cls) 139 | $('alertbody').addClassName(cls); 140 | $('alert').show(); 141 | } else { 142 | $('alert').hide(); 143 | $('alertbody').classNames().each(function(elt) { 144 | $('alertbody').removeClassName(elt); 145 | }); 146 | } 147 | } 148 | 149 | Ajax.Responders.register({ 150 | onCreate: cloud.updateStatus, 151 | onComplete: function(q, r, data) { 152 | cloud.updateStatus(); 153 | var msg = ''; 154 | var cls = q.success() ? 'info' : 'error'; 155 | try { 156 | msg += data.status.toString()+' '+data.message; 157 | } catch (e) { 158 | msg += r.status.toString()+' '+r.statusText; 159 | } 160 | var method = q.method.toUpperCase(); 161 | var triples = r.getHeader('X-Triples'); 162 | if (triples != null) { 163 | msg = triples.toString()+' triple(s): '+msg; 164 | } else { 165 | if (method == 'GET') { 166 | msg = r.responseText.length.toString()+' byte(s): '+msg; 167 | } else { 168 | msg = q.body.length.toString()+' byte(s): '+msg; 169 | } 170 | } 171 | cloud.alert(method+' '+msg, cls); 172 | window.setTimeout("cloud.alert()", 3000); 173 | }, 174 | }); 175 | 176 | cloud.facebookInit = function() { 177 | FB.init({appId: '119467988130777', status: false, cookie: false, xfbml: true}); 178 | FB._login = FB.login; 179 | FB.login = function(cb, opts) { 180 | if (!opts) opts = {}; 181 | opts['next'] = cloud.request_base + '/login?id=facebook&display=popup'; 182 | return FB._login(cb, opts); 183 | } 184 | }; 185 | window.fbAsyncInit = cloud.facebookInit; 186 | -------------------------------------------------------------------------------- /www/root/common/css/blueprint.css: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | 3 | /* ----------------------------------------------------------------------- 4 | 5 | 6 | Blueprint CSS Framework 1.0 7 | http://blueprintcss.org 8 | 9 | * Copyright (c) 2007-Present. See LICENSE for more info. 10 | * See README for instructions on how to use Blueprint. 11 | * For credits and origins, see AUTHORS. 12 | * This is a compressed file. See the sources in the 'src' directory. 13 | 14 | ----------------------------------------------------------------------- */ 15 | 16 | /* reset.css */ 17 | html {margin:0;padding:0;border:0;} 18 | body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} 19 | article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;} 20 | body {line-height:1.5;background:white;} 21 | table {border-collapse:separate;border-spacing:0;} 22 | caption, th, td {text-align:left;font-weight:normal;float:none !important;} 23 | table, th, td {vertical-align:middle;} 24 | blockquote:before, blockquote:after, q:before, q:after {content:'';} 25 | blockquote, q {quotes:"" "";} 26 | a img {border:none;} 27 | :focus {outline:0;} 28 | 29 | /* typography.css */ 30 | html {font-size:100.01%;} 31 | body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;} 32 | h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;} 33 | h1 {font-size:3em;line-height:1;margin-bottom:0.5em;} 34 | h2 {font-size:2em;margin-bottom:0.75em;} 35 | h3 {font-size:1.5em;line-height:1;margin-bottom:1em;} 36 | h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;} 37 | h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;} 38 | h6 {font-size:1em;font-weight:bold;} 39 | h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;} 40 | p {margin:0 0 1.5em;} 41 | .left {float:left !important;} 42 | p .left {margin:1.5em 1.5em 1.5em 0;padding:0;} 43 | .right {float:right !important;} 44 | p .right {margin:1.5em 0 1.5em 1.5em;padding:0;} 45 | a:focus, a:hover {color:#09f;} 46 | a {color:#06c;text-decoration:underline;} 47 | blockquote {margin:1.5em;color:#666;font-style:italic;} 48 | strong, dfn {font-weight:bold;} 49 | em, dfn {font-style:italic;} 50 | sup, sub {line-height:0;} 51 | abbr, acronym {border-bottom:1px dotted #666;} 52 | address {margin:0 0 1.5em;font-style:italic;} 53 | del {color:#666;} 54 | pre {margin:1.5em 0;white-space:pre;} 55 | pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;} 56 | li ul, li ol {margin:0;} 57 | ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;} 58 | ul {list-style-type:disc;} 59 | ol {list-style-type:decimal;} 60 | dl {margin:0 0 1.5em 0;} 61 | dl dt {font-weight:bold;} 62 | dd {margin-left:1.5em;} 63 | table {margin-bottom:1.4em;width:100%;} 64 | th {font-weight:bold;} 65 | thead th {background:#c3d9ff;} 66 | th, td, caption {padding:4px 10px 4px 5px;} 67 | tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;} 68 | tfoot {font-style:italic;} 69 | caption {background:#eee;} 70 | .small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;} 71 | .large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;} 72 | .hide {display:none;} 73 | .quiet {color:#666;} 74 | .loud {color:#000;} 75 | .highlight {background:#ff0;} 76 | .added {background:#060;color:#fff;} 77 | .removed {background:#900;color:#fff;} 78 | .first {margin-left:0;padding-left:0;} 79 | .last {margin-right:0;padding-right:0;} 80 | .top {margin-top:0;padding-top:0;} 81 | .bottom {margin-bottom:0;padding-bottom:0;} 82 | 83 | /* forms.css */ 84 | label {font-weight:bold;} 85 | fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;} 86 | legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;} 87 | fieldset, #IE8#HACK {padding-top:1.4em;} 88 | legend, #IE8#HACK {margin-top:0;margin-bottom:0;} 89 | input[type=text], input[type=password], input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;} 90 | input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666;} 91 | select {background-color:#fff;border-width:1px;border-style:solid;} 92 | input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;} 93 | input.text, input.title {width:300px;padding:5px;} 94 | input.title {font-size:1.5em;} 95 | textarea {width:390px;height:250px;padding:5px;} 96 | form.inline {line-height:3;} 97 | form.inline p {margin-bottom:0;} 98 | .error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;} 99 | .error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;} 100 | .notice {background:#fff6bf;color:#514721;border-color:#ffd324;} 101 | .success {background:#e6efc2;color:#264409;border-color:#c6d880;} 102 | .info {background:#d5edf8;color:#205791;border-color:#92cae4;} 103 | .error a, .alert a {color:#8a1f11;} 104 | .notice a {color:#514721;} 105 | .success a {color:#264409;} 106 | .info a {color:#205791;} 107 | 108 | /* grid.css */ 109 | .container {width:950px;margin:0 auto;} 110 | .showgrid {background:url(src/grid.png);} 111 | .column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;} 112 | .last {margin-right:0;} 113 | .span-1 {width:30px;} 114 | .span-2 {width:70px;} 115 | .span-3 {width:110px;} 116 | .span-4 {width:150px;} 117 | .span-5 {width:190px;} 118 | .span-6 {width:230px;} 119 | .span-7 {width:270px;} 120 | .span-8 {width:310px;} 121 | .span-9 {width:350px;} 122 | .span-10 {width:390px;} 123 | .span-11 {width:430px;} 124 | .span-12 {width:470px;} 125 | .span-13 {width:510px;} 126 | .span-14 {width:550px;} 127 | .span-15 {width:590px;} 128 | .span-16 {width:630px;} 129 | .span-17 {width:670px;} 130 | .span-18 {width:710px;} 131 | .span-19 {width:750px;} 132 | .span-20 {width:790px;} 133 | .span-21 {width:830px;} 134 | .span-22 {width:870px;} 135 | .span-23 {width:910px;} 136 | .span-24 {width:950px;margin-right:0;} 137 | input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;} 138 | input.span-1, textarea.span-1 {width:18px;} 139 | input.span-2, textarea.span-2 {width:58px;} 140 | input.span-3, textarea.span-3 {width:98px;} 141 | input.span-4, textarea.span-4 {width:138px;} 142 | input.span-5, textarea.span-5 {width:178px;} 143 | input.span-6, textarea.span-6 {width:218px;} 144 | input.span-7, textarea.span-7 {width:258px;} 145 | input.span-8, textarea.span-8 {width:298px;} 146 | input.span-9, textarea.span-9 {width:338px;} 147 | input.span-10, textarea.span-10 {width:378px;} 148 | input.span-11, textarea.span-11 {width:418px;} 149 | input.span-12, textarea.span-12 {width:458px;} 150 | input.span-13, textarea.span-13 {width:498px;} 151 | input.span-14, textarea.span-14 {width:538px;} 152 | input.span-15, textarea.span-15 {width:578px;} 153 | input.span-16, textarea.span-16 {width:618px;} 154 | input.span-17, textarea.span-17 {width:658px;} 155 | input.span-18, textarea.span-18 {width:698px;} 156 | input.span-19, textarea.span-19 {width:738px;} 157 | input.span-20, textarea.span-20 {width:778px;} 158 | input.span-21, textarea.span-21 {width:818px;} 159 | input.span-22, textarea.span-22 {width:858px;} 160 | input.span-23, textarea.span-23 {width:898px;} 161 | input.span-24, textarea.span-24 {width:938px;} 162 | .append-1 {padding-right:40px;} 163 | .append-2 {padding-right:80px;} 164 | .append-3 {padding-right:120px;} 165 | .append-4 {padding-right:160px;} 166 | .append-5 {padding-right:200px;} 167 | .append-6 {padding-right:240px;} 168 | .append-7 {padding-right:280px;} 169 | .append-8 {padding-right:320px;} 170 | .append-9 {padding-right:360px;} 171 | .append-10 {padding-right:400px;} 172 | .append-11 {padding-right:440px;} 173 | .append-12 {padding-right:480px;} 174 | .append-13 {padding-right:520px;} 175 | .append-14 {padding-right:560px;} 176 | .append-15 {padding-right:600px;} 177 | .append-16 {padding-right:640px;} 178 | .append-17 {padding-right:680px;} 179 | .append-18 {padding-right:720px;} 180 | .append-19 {padding-right:760px;} 181 | .append-20 {padding-right:800px;} 182 | .append-21 {padding-right:840px;} 183 | .append-22 {padding-right:880px;} 184 | .append-23 {padding-right:920px;} 185 | .prepend-1 {padding-left:40px;} 186 | .prepend-2 {padding-left:80px;} 187 | .prepend-3 {padding-left:120px;} 188 | .prepend-4 {padding-left:160px;} 189 | .prepend-5 {padding-left:200px;} 190 | .prepend-6 {padding-left:240px;} 191 | .prepend-7 {padding-left:280px;} 192 | .prepend-8 {padding-left:320px;} 193 | .prepend-9 {padding-left:360px;} 194 | .prepend-10 {padding-left:400px;} 195 | .prepend-11 {padding-left:440px;} 196 | .prepend-12 {padding-left:480px;} 197 | .prepend-13 {padding-left:520px;} 198 | .prepend-14 {padding-left:560px;} 199 | .prepend-15 {padding-left:600px;} 200 | .prepend-16 {padding-left:640px;} 201 | .prepend-17 {padding-left:680px;} 202 | .prepend-18 {padding-left:720px;} 203 | .prepend-19 {padding-left:760px;} 204 | .prepend-20 {padding-left:800px;} 205 | .prepend-21 {padding-left:840px;} 206 | .prepend-22 {padding-left:880px;} 207 | .prepend-23 {padding-left:920px;} 208 | .border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;} 209 | .colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;} 210 | .pull-1 {margin-left:-40px;} 211 | .pull-2 {margin-left:-80px;} 212 | .pull-3 {margin-left:-120px;} 213 | .pull-4 {margin-left:-160px;} 214 | .pull-5 {margin-left:-200px;} 215 | .pull-6 {margin-left:-240px;} 216 | .pull-7 {margin-left:-280px;} 217 | .pull-8 {margin-left:-320px;} 218 | .pull-9 {margin-left:-360px;} 219 | .pull-10 {margin-left:-400px;} 220 | .pull-11 {margin-left:-440px;} 221 | .pull-12 {margin-left:-480px;} 222 | .pull-13 {margin-left:-520px;} 223 | .pull-14 {margin-left:-560px;} 224 | .pull-15 {margin-left:-600px;} 225 | .pull-16 {margin-left:-640px;} 226 | .pull-17 {margin-left:-680px;} 227 | .pull-18 {margin-left:-720px;} 228 | .pull-19 {margin-left:-760px;} 229 | .pull-20 {margin-left:-800px;} 230 | .pull-21 {margin-left:-840px;} 231 | .pull-22 {margin-left:-880px;} 232 | .pull-23 {margin-left:-920px;} 233 | .pull-24 {margin-left:-960px;} 234 | .pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;} 235 | .push-1 {margin:0 -40px 1.5em 40px;} 236 | .push-2 {margin:0 -80px 1.5em 80px;} 237 | .push-3 {margin:0 -120px 1.5em 120px;} 238 | .push-4 {margin:0 -160px 1.5em 160px;} 239 | .push-5 {margin:0 -200px 1.5em 200px;} 240 | .push-6 {margin:0 -240px 1.5em 240px;} 241 | .push-7 {margin:0 -280px 1.5em 280px;} 242 | .push-8 {margin:0 -320px 1.5em 320px;} 243 | .push-9 {margin:0 -360px 1.5em 360px;} 244 | .push-10 {margin:0 -400px 1.5em 400px;} 245 | .push-11 {margin:0 -440px 1.5em 440px;} 246 | .push-12 {margin:0 -480px 1.5em 480px;} 247 | .push-13 {margin:0 -520px 1.5em 520px;} 248 | .push-14 {margin:0 -560px 1.5em 560px;} 249 | .push-15 {margin:0 -600px 1.5em 600px;} 250 | .push-16 {margin:0 -640px 1.5em 640px;} 251 | .push-17 {margin:0 -680px 1.5em 680px;} 252 | .push-18 {margin:0 -720px 1.5em 720px;} 253 | .push-19 {margin:0 -760px 1.5em 760px;} 254 | .push-20 {margin:0 -800px 1.5em 800px;} 255 | .push-21 {margin:0 -840px 1.5em 840px;} 256 | .push-22 {margin:0 -880px 1.5em 880px;} 257 | .push-23 {margin:0 -920px 1.5em 920px;} 258 | .push-24 {margin:0 -960px 1.5em 960px;} 259 | .push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;} 260 | div.prepend-top, .prepend-top {margin-top:1.5em;} 261 | div.append-bottom, .append-bottom {margin-bottom:1.5em;} 262 | .box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;} 263 | hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:none;} 264 | hr.space {background:#fff;color:#fff;visibility:hidden;} 265 | .clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;} 266 | .clearfix, .container {display:block;} 267 | .clear {clear:both;} 268 | -------------------------------------------------------------------------------- /www/inc/rdf.lib.php: -------------------------------------------------------------------------------- 1 | _exists = false; 42 | 43 | // auto-detect empty storage from name 44 | if (empty($storage) && !empty($name)) { 45 | $storage = 'memory'; 46 | if (file_exists($name)) { 47 | $this->_exists = true; 48 | if ($ext == 'sqlite') 49 | $storage = 'sqlite'; 50 | } elseif (file_exists("$name.sqlite")) { 51 | $this->_exists = true; 52 | $name = "$name.sqlite"; 53 | $ext = 'sqlite'; 54 | $storage = 'sqlite'; 55 | } elseif ($ext == 'sqlite' || $_options->sqlite) { 56 | $storage = 'sqlite'; 57 | } 58 | } 59 | if ($storage == 'sqlite') { 60 | if ($ext != 'sqlite') { 61 | $ext = 'sqlite'; 62 | $name = "$name.sqlite"; 63 | } 64 | if (file_exists($name)) 65 | $this->_exists = true; 66 | if (empty($options) && !$this->exists()) 67 | $options = "new='yes'"; 68 | } 69 | $this->_name = $name; 70 | $this->_storage = $storage; 71 | $this->_options = $options; 72 | $this->_base = $base; 73 | 74 | // instance state 75 | $this->_world = librdf_new_world(); 76 | if (function_exists('librdf_php_world_set_logger')) 77 | librdf_php_world_set_logger($this->_world); 78 | $this->_base_uri = librdf_new_uri($this->_world, $base); 79 | $this->_stream = null; 80 | 81 | // const objs 82 | $this->_f_relativeURIs = librdf_new_uri($this->_world, 'http://feature.librdf.org/raptor-relativeURIs'); 83 | $this->_f_writeBaseURI = librdf_new_uri($this->_world, 'http://feature.librdf.org/raptor-writeBaseURI'); 84 | $this->_n_0 = librdf_new_node_from_literal($this->_world, 0, null, 0); 85 | 86 | $this->reload(); 87 | //$this->sendHeaders(); 88 | } 89 | function reload() { 90 | if ($this->_model) 91 | librdf_free_model($this->_model); 92 | if ($this->_store) 93 | librdf_free_storage($this->_store); 94 | $this->_store = librdf_new_storage( 95 | $this->_world, $this->_storage, 96 | $this->_storage == 'memory' ? '' : $this->_name, 97 | $this->_options 98 | ); 99 | $this->_model = librdf_new_model($this->_world, $this->_store, null); 100 | $this->_exists = ($this->_name && file_exists($this->_name)) ? true : false; 101 | if ($this->_storage == 'memory') { 102 | if ($this->exists()) 103 | $this->append_file('turtle', "file://{$this->_name}", $this->_base); 104 | } 105 | } 106 | function sendHeaders() { 107 | header('X-Base: '.$this->_base); 108 | header('X-Filename: '.$this->_name); 109 | header('X-Size: '.$this->size()); 110 | header('X-Storage: '.$this->_storage); 111 | header('X-Options: '.$this->_options); 112 | } 113 | function base() { return $this->_base; } 114 | function exists() { return $this->_exists; } 115 | function etag() { 116 | if ($this->exists() && file_exists($this->_name)) { 117 | return filemtime($this->_name).'-'.strtolower(md5(file_get_contents($this->_name, false, null, -1, 1024000))); 118 | } 119 | } 120 | function save() { 121 | if ($this->_storage == 'memory' && !empty($this->_name)) { 122 | file_put_contents($this->_name, $this->__toString()); 123 | } 124 | return librdf_model_sync($this->_model); 125 | } 126 | function truncate() { 127 | librdf_free_model($this->_model); 128 | librdf_free_storage($this->_store); 129 | if ($this->_storage != 'memory') 130 | $this->delete(); 131 | $this->_store = librdf_new_storage( 132 | $this->_world, $this->_storage, 133 | $this->_storage == 'memory' ? '' : $this->_name, 134 | $this->_storage == 'sqlite' ? "new='yes'" : '' 135 | ); 136 | $this->_model = librdf_new_model($this->_world, $this->_store, null); 137 | $this->_exists = $this->_model ? true : false; 138 | } 139 | function delete() { 140 | if ($this->exists()) { 141 | unlink($this->_name); 142 | $this->_exists = false; 143 | } 144 | } 145 | function __destruct() { 146 | /* 147 | if (isset($this->_uriNodes)) 148 | foreach ($this->_uriNodes as $elt) librdf_free_node($elt); 149 | if (isset($this->_blankNodes)) 150 | foreach ($this->_blankNodes as $elt) librdf_free_node($elt); 151 | if (isset($this->_literalNodes)) 152 | foreach ($this->_literalNodes as $type=>$nodes) 153 | foreach ($nodes as $elt) librdf_free_node($elt); 154 | */ 155 | if ($this->_stream) 156 | librdf_free_stream($this->_stream); 157 | // instance state 158 | librdf_free_model($this->_model); 159 | librdf_free_storage($this->_store); 160 | librdf_free_uri($this->_base_uri); 161 | // common 162 | librdf_free_uri($this->_f_relativeURIs); 163 | librdf_free_uri($this->_f_writeBaseURI); 164 | librdf_free_node($this->_n_0); 165 | if ($this->_world) 166 | librdf_free_world($this->_world); 167 | } 168 | function __toString() { 169 | return $this->to_string('turtle'); 170 | } 171 | function to_string($name) { 172 | if (!$this->_model) return; 173 | if ($name == 'json-ld') return $this->to_jsonld_string(); 174 | $s = librdf_new_serializer($this->_world, $name, null, null); 175 | if ($name == 'json') 176 | librdf_serializer_set_feature($s, $this->_f_relativeURIs, $this->_n_0); 177 | librdf_serializer_set_feature($s, $this->_f_writeBaseURI, $this->_n_0); 178 | $r = librdf_serializer_serialize_model_to_string($s, $this->_base_uri, $this->_model); 179 | librdf_free_serializer($s); 180 | assert(strlen($r) || $this->size()<1); 181 | return $r; 182 | } 183 | function size() { 184 | if ($this->_model) 185 | return librdf_model_size($this->_model); 186 | } 187 | function to_stream() { 188 | if ($this->_stream) 189 | librdf_free_stream($this->_stream); 190 | $this->_stream = librdf_model_as_stream($this->_model); 191 | return $this->_stream; 192 | } 193 | function add_stream($stream) { 194 | return librdf_model_add_statements($this->_model, $stream) == 0; 195 | } 196 | function append($content_type, $content) { 197 | if ($content_type == 'json-ld') return $this->append_jsonld($content); 198 | elseif ($content_type == 'json' && raptor_version_decimal_get()<20004) 199 | return $this->append_array(json_decode($content,1)); 200 | $p = librdf_new_parser($this->_world, $content_type, null, null); 201 | $r = librdf_parser_parse_string_into_model($p, $content, $this->_base_uri, $this->_model); 202 | librdf_free_parser($p); 203 | return $r == 0; 204 | } 205 | function append_file($content_type, $file, $base=null) { 206 | $p = librdf_new_parser($this->_world, $content_type, null, null); 207 | $file_uri = librdf_new_uri($this->_world, $file); 208 | $base_uri = librdf_new_uri($this->_world, is_null($base)?$this->_base:$base); 209 | $r = librdf_parser_parse_into_model($p, $file_uri, $base_uri, $this->_model); 210 | librdf_free_parser($p); 211 | librdf_free_uri($base_uri); 212 | librdf_free_uri($file_uri); 213 | return $r == 0; 214 | } 215 | function load($uri) { 216 | $uri = librdf_new_uri($this->_world, $uri); 217 | $r = librdf_model_load($this->_model, $uri, 'guess', null, null); 218 | librdf_free_uri($uri); 219 | return $r; 220 | } 221 | function _node($node) { 222 | $r = array(); 223 | if (librdf_node_is_resource($node)) { 224 | $r['type'] = 'uri'; 225 | $r['value'] = librdf_uri_to_string(librdf_node_get_uri($node)); 226 | } elseif (librdf_node_is_literal($node)) { 227 | $r['type'] = 'literal'; 228 | $r['value'] = librdf_node_get_literal_value($node); 229 | $dt = librdf_node_get_literal_value_datatype_uri($node); 230 | if ($dt) 231 | $r['datatype'] = librdf_uri_to_string($dt); 232 | } elseif (librdf_node_is_blank($node)) { 233 | $r['type'] = 'bnode'; 234 | $r['value'] = librdf_node_get_blank_identifier($node); 235 | } 236 | return $r; 237 | } 238 | function _statement($statement) { 239 | return array( 240 | $this->_node(librdf_statement_get_subject($statement)), 241 | $this->_node(librdf_statement_get_predicate($statement)), 242 | $this->_node(librdf_statement_get_object($statement)) 243 | ); 244 | } 245 | function _uriNode($uri) { 246 | if (!isset($this->_uriNodes[$uri])) 247 | $r = $this->_uriNodes[$uri] = librdf_new_node_from_uri_string($this->_world, $uri); 248 | else 249 | $r = $this->_uriNodes[$uri]; 250 | return $r; 251 | } 252 | function _blankNode($id) { 253 | if (!isset($this->_blankNodes[$id])) 254 | $r = $this->_blankNodes[$id] = librdf_new_node_from_blank_identifier($this->_world, $id); 255 | else 256 | $r = $this->_blankNodes[$id]; 257 | return $r; 258 | } 259 | function _literalNode($value, $type=null) { 260 | if (!isset($this->_literalNodes[$type])) 261 | $this->_literalNodes[$type] = array(); 262 | if (!isset($this->_literalNodes[$type][$value])) 263 | $r = $this->_literalNodes[$type][$value] = librdf_new_node_from_literal($this->_world, $value, NULL, 0); 264 | else 265 | $r = $this->_literalNodes[$type][$value]; 266 | return $r; 267 | } 268 | function any($s=null, $p=null, $o=null) { 269 | $r = array(); 270 | if (!is_null($s)) $s = librdf_new_node_from_uri_string($this->_world, absolutize($this->_base, $s)); 271 | if (!is_null($p)) $p = librdf_new_node_from_uri_string($this->_world, absolutize($this->_base, $p)); 272 | $pattern = librdf_new_statement_from_nodes($this->_world, $s, $p, $o); 273 | $stream = librdf_model_find_statements($this->_model, $pattern); 274 | while (!librdf_stream_end($stream)) { 275 | $q = $this->_statement(librdf_stream_get_object($stream)); 276 | if (!isset($r[$q[0]['value']])) 277 | $r[$q[0]['value']] = array(); 278 | if (!isset($r[$q[0]['value']][$q[1]['value']])) 279 | $r[$q[0]['value']][$q[1]['value']] = array(); 280 | $r[$q[0]['value']][$q[1]['value']][] = $q[2]; 281 | librdf_stream_next($stream); 282 | } 283 | librdf_free_stream($stream); 284 | //librdf_free_statement($pattern); 285 | //$s && librdf_free_node($s); 286 | //$p && librdf_free_node($p); 287 | return $r; 288 | } 289 | function remove_any($s=null, $p=null, $o=null) { 290 | $r = 0; 291 | if (!is_null($s)) $s = $this->_uriNode($s); 292 | if (!is_null($p)) $p = $this->_uriNode($p); 293 | $pattern = librdf_new_statement_from_nodes($this->_world, $s, $p, $o); 294 | $stream = librdf_model_find_statements($this->_model, $pattern); 295 | while (!librdf_stream_end($stream)) { 296 | $elt = librdf_stream_get_object($stream); 297 | $r += librdf_model_remove_statement($this->_model, $elt) ? 0 : 1; 298 | librdf_stream_next($stream); 299 | } 300 | librdf_free_stream($stream); 301 | //librdf_free_statement($pattern); 302 | //$s && librdf_free_node($s); 303 | //$p && librdf_free_node($p); 304 | return $r; 305 | } 306 | function query_to_string($query, $format, $base_uri=null) { 307 | timings($query); 308 | if (is_null($base_uri)) $base_uri = $this->_base_uri; 309 | elseif (is_string($base_uri)) $base_uri = librdf_new_uri($this->_world, $base_uri); 310 | $q = librdf_new_query($this->_world, 'sparql', null, $query, $base_uri); 311 | $r = librdf_model_query_execute($this->_model, $q); 312 | if (in_array($format, array('csv', 'tsv'))) 313 | $format = 'http://www.w3.org/ns/formats/SPARQL_Results_'.strtoupper($format); 314 | else 315 | $format = 'http://www.w3.org/2001/sw/DataAccess/json-sparql/'; 316 | $format_uri = librdf_new_uri($this->_world, $format); 317 | if ($r) 318 | $r = librdf_query_results_to_string($r, $format_uri, $base_uri); 319 | librdf_free_query($q); 320 | librdf_free_uri($format_uri); 321 | timings(); 322 | return $r; 323 | } 324 | function SELECT($query, $base_uri=null) { 325 | return json_decode($this->query_to_string($query, 'json', $base_uri), 1); 326 | } 327 | function SELECT_p_o($uri, $base_uri=null) { 328 | $q = "SELECT * WHERE { <$uri> ?p ?o }"; 329 | $r = array(); 330 | $d = $this->SELECT($q, $base_uri); 331 | if (isset($d['results']) && isset($d['results']['bindings'])) 332 | foreach($d['results']['bindings'] as $elt) { 333 | $p = $elt['p']['value']; 334 | if (!isset($r[$p])) { 335 | $r[$p] = array(); 336 | } 337 | $r[$p][] = $elt['o']; 338 | } 339 | return $r; 340 | } 341 | function CONSTRUCT($query, $base_uri=null) { 342 | if (is_null($base_uri)) $base_uri = $this->_base_uri; 343 | timings($query); 344 | $q = librdf_new_query($this->_world, 'sparql', null, $query, $base_uri); 345 | $r = librdf_model_query_execute($this->_model, $q); 346 | $r_stream = librdf_query_results_as_stream($r); 347 | $r_store = librdf_new_storage($this->_world, 'memory', '', null); 348 | $r_model = librdf_new_model($this->_world, $r_store, null); 349 | librdf_model_add_statements($r_model, $r_stream); 350 | librdf_free_stream($r_stream); 351 | $serializer = librdf_new_serializer($this->_world, 'json', null, null); 352 | $r = librdf_serializer_serialize_model_to_string($serializer, null, $r_model); 353 | librdf_free_serializer($serializer); 354 | $r = json_decode($r, 1); 355 | if (is_null($r)) $r = array(); 356 | librdf_free_model($r_model); 357 | librdf_free_storage($r_store); 358 | librdf_free_query($q); 359 | timings(); 360 | return $r; 361 | } 362 | function append_objects($s0, $p0, $lst) { 363 | if (!is_null($s0)) 364 | $s = (strlen($s0) > 1 && $s0{0} == '_' && $s0{1} == ':') 365 | ? $this->_blankNode($s0) 366 | : $this->_uriNode(absolutize($this->_base, $s0)); 367 | if (!is_null($p0)) $p = $this->_uriNode(absolutize($this->_base, $p0)); 368 | $r = 0; 369 | foreach ($lst as $elt) { 370 | if (isset($elt['type']) && isset($elt['value'])) { 371 | $type = $elt['type']; 372 | $value = $elt['value']; 373 | $datatype = isset($elt['datatype']) ? $elt['datatype'] : ''; 374 | if ($type == 'literal' && $datatype) { 375 | //$json = json_encode(array($s0=>array($p0=>array(array('type'=>'literal','value'=>$value))))); 376 | $datatype_uri = librdf_new_uri($this->_world, $datatype); 377 | $o = librdf_new_node_from_typed_literal($this->_world, $value, NULL, $datatype_uri); 378 | //librdf_free_uri($dt); 379 | } elseif ($type == 'literal') { 380 | $o = $this->_literalNode($value, null); 381 | } elseif ($elt['type'] == 'uri') { 382 | $o = $this->_uriNode(absolutize($this->_base, $value)); 383 | } elseif ($elt['type'] == 'bnode') { 384 | $o = $this->_blankNode($value); 385 | } 386 | $r += librdf_model_add($this->_model, $s, $p, $o) ? 0 : 1; 387 | //$o && librdf_free_node($o); 388 | } 389 | } 390 | //$p && librdf_free_node($p); 391 | //$s && librdf_free_node($s); 392 | return $r; 393 | } 394 | function append_array($data) { 395 | $r = 0; 396 | librdf_model_transaction_start($this->_model); 397 | foreach ($data as $s=>$s_data) { 398 | foreach ($s_data as $p=>$p_data) { 399 | $r += $this->append_objects($s, $p, $p_data); 400 | } 401 | } 402 | librdf_model_transaction_commit($this->_model); 403 | return $r; 404 | } 405 | function patch_array($data) { 406 | $r = 0; 407 | librdf_model_transaction_start($this->_model); 408 | foreach ($data as $s=>$s_data) { 409 | $s = absolutize($this->_base, $s); 410 | foreach ($s_data as $p=>$p_data) { 411 | $r += $this->remove_any($s, $p); 412 | $r += $this->append_objects($s, $p, $p_data); 413 | } 414 | } 415 | librdf_model_transaction_commit($this->_model); 416 | return $r; 417 | } 418 | function patch_json($json) { 419 | if (raptor_version_decimal_get()<20004) 420 | return $this->patch_array(json_decode($json,1)); 421 | $r = 0; 422 | librdf_model_transaction_start($this->_model); 423 | $data = json_decode($json, 1); 424 | foreach ($data as $s=>$s_data) { 425 | $s = absolutize($this->_base, $s); 426 | foreach ($s_data as $p=>$p_data) { 427 | $r += $this->remove_any($s, $p); 428 | } 429 | } 430 | $r += $this->append('json', $json); 431 | librdf_model_transaction_commit($this->_model); 432 | return $r; 433 | } 434 | function append_jsonld($content) { 435 | $data = jsonld_normalize(json_decode($content)); 436 | librdf_model_transaction_start($this->_model); 437 | foreach($data as $s_data) { 438 | $s = $s_data->{'@subject'}->{'@iri'}; 439 | unset($s_data->{'@subject'}); 440 | foreach($s_data as $p=>$p_data) { 441 | if (gettype($p_data) != 'array') 442 | $p_data = array($p_data); 443 | $o_lst = array(); 444 | foreach($p_data as $o) { 445 | if (gettype($o) != 'object') 446 | $o = (object)array('@literal'=>$o); 447 | if (isset($o->{'@iri'})) { 448 | $o = $o->{'@iri'}; 449 | if (strlen($o) > 1 && $o{0} == '_' && $o{1} == ':') 450 | array_push($o_lst, array('type'=>'bnode', 'value'=>$o)); 451 | else 452 | array_push($o_lst, array('type'=>'uri', 'value'=>$o)); 453 | } elseif (isset($o->{'@literal'})) { 454 | if (isset($o->{'@datatype'})) 455 | array_push($o_lst, array('type'=>'literal', 'value'=>$o->{'@literal'}, 'datatype'=>$o->{'@datatype'})); 456 | else 457 | array_push($o_lst, array('type'=>'literal', 'value'=>$o->{'@literal'})); 458 | } 459 | } 460 | $this->append_objects($s, $p, $o_lst); 461 | } 462 | } 463 | librdf_model_transaction_commit($this->_model); 464 | return true; 465 | } 466 | function to_jsonld_string() { 467 | $r = array(); 468 | $stream = librdf_model_as_stream($this->_model); 469 | while (!librdf_stream_end($stream)) { 470 | $elt = $this->_statement(librdf_stream_get_object($stream)); 471 | $d = new \stdClass(); 472 | $d->{'@subject'} = $elt[0]['value']; 473 | if ($elt[2]['type'] == 'literal') 474 | if (isset($elt[2]['datatype'])) 475 | $d->{$elt[1]['value']} = (object)array('@literal'=>$elt[2]['value'],'@datatype'=>$elt[2]['datatype']); 476 | else 477 | $d->{$elt[1]['value']} = $elt[2]['value']; 478 | else 479 | $d->{$elt[1]['value']} = (object)array('@iri'=>$elt[2]['value']); 480 | $r[] = $d; 481 | librdf_stream_next($stream); 482 | } 483 | librdf_free_stream($stream); 484 | return str_replace('\\/', '/', json_encode(jsonld_normalize($r))); 485 | } 486 | } // class Graph 487 | 488 | /* 489 | $_NS = array( 490 | 'rdfs' => '', 491 | 'dc' => '', 492 | 'foaf' => '', 493 | 'en' => '', 494 | 'rdf' => '', 495 | 'xsd' => '', 496 | ); 497 | */ 498 | -------------------------------------------------------------------------------- /www/inc/contrib/jsonld.php: -------------------------------------------------------------------------------- 1 | normalize($input); 25 | } 26 | 27 | /** 28 | * Removes the context from a JSON-LD object, expanding it to full-form. 29 | * 30 | * @param input the JSON-LD object to remove the context from. 31 | * 32 | * @return the context-neutral JSON-LD object. 33 | */ 34 | function jsonld_expand($input) 35 | { 36 | $p = new JsonLdProcessor(); 37 | return $p->expand(new stdClass(), null, $input); 38 | } 39 | 40 | /** 41 | * Expands the given JSON-LD object and then compacts it using the 42 | * given context. 43 | * 44 | * @param ctx the new context to use. 45 | * @param input the input JSON-LD object. 46 | * 47 | * @return the output JSON-LD object. 48 | */ 49 | function jsonld_compact($ctx, $input) 50 | { 51 | $rval = null; 52 | 53 | // TODO: should context simplification be optional? (ie: remove context 54 | // entries that are not used in the output) 55 | 56 | if($input !== null) 57 | { 58 | // fully expand input 59 | $input = jsonld_expand($input); 60 | 61 | // merge context if it is an array 62 | if(is_array($ctx)) 63 | { 64 | $ctx = jsonld_merge_contexts(new stdClass, $ctx); 65 | } 66 | 67 | // setup output context 68 | $ctxOut = new stdClass(); 69 | 70 | // compact 71 | $p = new JsonLdProcessor(); 72 | $rval = $out = $p->compact(_clone($ctx), null, $input, $ctxOut); 73 | 74 | // add context if used 75 | if(count(array_keys((array)$ctxOut)) > 0) 76 | { 77 | $rval = new stdClass(); 78 | $rval->{'@context'} = $ctxOut; 79 | if(is_array($out)) 80 | { 81 | $rval->{_getKeywords($ctxOut)->{'@id'}} = $out; 82 | } 83 | else 84 | { 85 | foreach($out as $k => $v) 86 | { 87 | $rval->{$k} = $v; 88 | } 89 | } 90 | } 91 | } 92 | 93 | return $rval; 94 | } 95 | 96 | /** 97 | * Merges one context with another. 98 | * 99 | * @param ctx1 the context to overwrite/append to. 100 | * @param ctx2 the new context to merge onto ctx1. 101 | * 102 | * @return the merged context. 103 | */ 104 | function jsonld_merge_contexts($ctx1, $ctx2) 105 | { 106 | // merge first context if it is an array 107 | if(is_array($ctx1)) 108 | { 109 | $ctx1 = jsonld_merge_contexts($ctx1, $ctx2); 110 | } 111 | 112 | // copy context to merged output 113 | $merged = _clone($ctx1); 114 | 115 | if(is_array($ctx2)) 116 | { 117 | // merge array of contexts in order 118 | foreach($ctx2 as $ctx) 119 | { 120 | $merged = jsonld_merge_contexts($merged, $ctx); 121 | } 122 | } 123 | else 124 | { 125 | // if the new context contains any IRIs that are in the merged context, 126 | // remove them from the merged context, they will be overwritten 127 | foreach($ctx2 as $key => $value) 128 | { 129 | // ignore special keys starting with '@' 130 | if(strpos($key, '@') !== 0) 131 | { 132 | foreach($merged as $mkey => $mvalue) 133 | { 134 | if($mvalue === $value) 135 | { 136 | // FIXME: update related coerce rules 137 | unset($merged->$mkey); 138 | break; 139 | } 140 | } 141 | } 142 | } 143 | 144 | // merge contexts 145 | foreach($ctx2 as $key => $value) 146 | { 147 | $merged->$key = _clone($value); 148 | } 149 | } 150 | 151 | return $merged; 152 | } 153 | 154 | /** 155 | * Expands a term into an absolute IRI. The term may be a regular term, a 156 | * prefix, a relative IRI, or an absolute IRI. In any case, the associated 157 | * absolute IRI will be returned. 158 | * 159 | * @param ctx the context to use. 160 | * @param term the term to expand. 161 | * 162 | * @return the expanded term as an absolute IRI. 163 | */ 164 | function jsonld_expand_term($ctx, $term) 165 | { 166 | return _expandTerm($ctx, $term); 167 | } 168 | 169 | /** 170 | * Compacts an IRI into a term or prefix if it can be. IRIs will not be 171 | * compacted to relative IRIs if they match the given context's default 172 | * vocabulary. 173 | * 174 | * @param ctx the context to use. 175 | * @param iri the IRI to compact. 176 | * 177 | * @return the compacted IRI as a term or prefix or the original IRI. 178 | */ 179 | function jsonld_compact_iri($ctx, $iri) 180 | { 181 | return _compactIri($ctx, $iri, null); 182 | } 183 | 184 | /** 185 | * Frames JSON-LD input. 186 | * 187 | * @param input the JSON-LD input. 188 | * @param frame the frame to use. 189 | * @param options framing options to use. 190 | * 191 | * @return the framed output. 192 | */ 193 | function jsonld_frame($input, $frame, $options=null) 194 | { 195 | $rval; 196 | 197 | // normalize input 198 | $input = jsonld_normalize($input); 199 | 200 | // save frame context 201 | $ctx = null; 202 | if(is_object($frame) and property_exists($frame, '@context')) 203 | { 204 | $ctx = _clone($frame->{'@context'}); 205 | 206 | // remove context from frame 207 | $frame = jsonld_expand($frame); 208 | } 209 | else if(is_array($frame)) 210 | { 211 | // save first context in the array 212 | if(count($frame) > 0 and property_exists($frame[0], '@context')) 213 | { 214 | $ctx = _clone($frame[0]->{'@context'}); 215 | } 216 | 217 | // expand all elements in the array 218 | $tmp = array(); 219 | foreach($frame as $f) 220 | { 221 | $tmp[] = jsonld_expand($f); 222 | } 223 | $frame = $tmp; 224 | } 225 | 226 | // create framing options 227 | // TODO: merge in options from function parameter 228 | $options = new stdClass(); 229 | $options->defaults = new stdClass(); 230 | $options->defaults->embedOn = true; 231 | $options->defaults->explicitOn = false; 232 | $options->defaults->omitDefaultOn = false; 233 | 234 | // build map of all subjects 235 | $subjects = new stdClass(); 236 | foreach($input as $i) 237 | { 238 | $subjects->{$i->{'@id'}} = $i; 239 | } 240 | 241 | // frame input 242 | $rval = _frame( 243 | $subjects, $input, $frame, new stdClass(), false, null, null, $options); 244 | 245 | // apply context 246 | if($ctx !== null and $rval !== null) 247 | { 248 | // preserve top-level array by compacting individual entries 249 | if(is_array($rval)) 250 | { 251 | $tmp = $rval; 252 | $rval = array(); 253 | foreach($tmp as $value) 254 | { 255 | $rval[] = jsonld_compact($ctx, $value); 256 | } 257 | } 258 | else 259 | { 260 | $rval = jsonld_compact($ctx, $rval); 261 | } 262 | } 263 | 264 | return $rval; 265 | } 266 | 267 | /** 268 | * Resolves external @context URLs. Every @context URL in the given JSON-LD 269 | * object is resolved using the given URL-resolver function. Once all of 270 | * the @contexts have been resolved, the method will return. If an error 271 | * is encountered, an exception will be thrown. 272 | * 273 | * @param input the JSON-LD input object (or array). 274 | * @param resolver the resolver method that takes a URL and returns a JSON-LD 275 | * serialized @context or throws an exception. 276 | * 277 | * @return the fully-resolved JSON-LD output (object or array). 278 | */ 279 | function jsonld_resolve($input, $resolver) 280 | { 281 | // find all @context URLs 282 | $urls = new ArrayObject(); 283 | _findUrls($input, $urls, false); 284 | 285 | // resolve all URLs 286 | foreach($urls as $url => $value) 287 | { 288 | $result = call_user_func($resolver, $url); 289 | if(!is_string($result)) 290 | { 291 | // already deserialized 292 | $urls[$url] = $result->{'@context'}; 293 | } 294 | else 295 | { 296 | // deserialize JSON 297 | $tmp = json_decode($result); 298 | if($tmp === null) 299 | { 300 | throw new Exception( 301 | 'Could not resolve @context URL ("$url"), ' . 302 | 'malformed JSON detected.'); 303 | } 304 | $urls[$url] = $tmp->{'@context'}; 305 | } 306 | } 307 | 308 | // replace @context URLs in input 309 | _findUrls($input, $urls, true); 310 | 311 | return $input; 312 | } 313 | 314 | /** 315 | * Finds all of the @context URLs in the given input and replaces them 316 | * if requested by their associated values in the given URL map. 317 | * 318 | * @param input the JSON-LD input object. 319 | * @param urls the URLs ArrayObject. 320 | * @param replace true to replace, false not to. 321 | */ 322 | function _findUrls($input, $urls, $replace) 323 | { 324 | if(is_array($input)) 325 | { 326 | foreach($input as $v) 327 | { 328 | _findUrls($v); 329 | } 330 | } 331 | else if(is_object($input)) 332 | { 333 | foreach($input as $key => $value) 334 | { 335 | if($key === '@context') 336 | { 337 | // @context is an array that might contain URLs 338 | if(is_array($value)) 339 | { 340 | foreach($value as $idx => $v) 341 | { 342 | if(is_string($v)) 343 | { 344 | // replace w/resolved @context if appropriate 345 | if($replace) 346 | { 347 | $input->{$key}[$idx] = $urls[$v]; 348 | } 349 | // unresolved @context found 350 | else 351 | { 352 | $urls[$v] = new stdClass(); 353 | } 354 | } 355 | } 356 | } 357 | else if(is_string($value)) 358 | { 359 | // replace w/resolved @context if appropriate 360 | if($replace) 361 | { 362 | $input->$key = $urls[$value]; 363 | } 364 | // unresolved @context found 365 | else 366 | { 367 | $urls[$value] = new stdClass(); 368 | } 369 | } 370 | } 371 | } 372 | } 373 | } 374 | 375 | /** 376 | * Gets the keywords from a context. 377 | * 378 | * @param ctx the context. 379 | * 380 | * @return the keywords. 381 | */ 382 | function _getKeywords($ctx) 383 | { 384 | // TODO: reduce calls to this function by caching keywords in processor 385 | // state 386 | 387 | $rval = (object)array( 388 | '@id' => '@id', 389 | '@language' => '@language', 390 | '@value' => '@value', 391 | '@type' => '@type' 392 | ); 393 | 394 | if($ctx) 395 | { 396 | // gather keyword aliases from context 397 | $keywords = new stdClass(); 398 | foreach($ctx as $key => $value) 399 | { 400 | if(is_string($value) and property_exists($rval, $value)) 401 | { 402 | $keywords->{$value} = $key; 403 | } 404 | } 405 | 406 | // overwrite keywords 407 | foreach($keywords as $key => $value) 408 | { 409 | $rval->$key = $value; 410 | } 411 | } 412 | 413 | return $rval; 414 | } 415 | 416 | /** 417 | * Sets a subject's property to the given object value. If a value already 418 | * exists, it will be appended to an array. 419 | * 420 | * @param s the subject. 421 | * @param p the property. 422 | * @param o the object. 423 | */ 424 | function _setProperty($s, $p, $o) 425 | { 426 | if(property_exists($s, $p)) 427 | { 428 | if(is_array($s->$p)) 429 | { 430 | array_push($s->$p, $o); 431 | } 432 | else 433 | { 434 | $s->$p = array($s->$p, $o); 435 | } 436 | } 437 | else 438 | { 439 | $s->$p = $o; 440 | } 441 | } 442 | 443 | /** 444 | * Clones an object, array, or string/number. If cloning an object, the keys 445 | * will be sorted. 446 | * 447 | * @param value the value to clone. 448 | * 449 | * @return the cloned value. 450 | */ 451 | function _clone($value) 452 | { 453 | $rval; 454 | 455 | if(is_object($value)) 456 | { 457 | $rval = new stdClass(); 458 | $keys = array_keys((array)$value); 459 | sort($keys); 460 | foreach($keys as $key) 461 | { 462 | $rval->$key = _clone($value->$key); 463 | } 464 | } 465 | else if(is_array($value)) 466 | { 467 | $rval = array(); 468 | foreach($value as $v) 469 | { 470 | $rval[] = _clone($v); 471 | } 472 | } 473 | else 474 | { 475 | $rval = $value; 476 | } 477 | 478 | return $rval; 479 | } 480 | 481 | /** 482 | * Gets the iri associated with a term. 483 | * 484 | * @param ctx the context. 485 | * @param term the term. 486 | * 487 | * @return the iri or NULL. 488 | */ 489 | function _getTermIri($ctx, $term) 490 | { 491 | $rval = null; 492 | if(property_exists($ctx, $term)) 493 | { 494 | if(is_string($ctx->$term)) 495 | { 496 | $rval = $ctx->$term; 497 | } 498 | else if(is_object($ctx->$term) and property_exists($ctx->$term, '@id')) 499 | { 500 | $rval = $ctx->$term->{'@id'}; 501 | } 502 | } 503 | return $rval; 504 | } 505 | 506 | /** 507 | * Compacts an IRI into a term or prefix if it can be. IRIs will not be 508 | * compacted to relative IRIs if they match the given context's default 509 | * vocabulary. 510 | * 511 | * @param ctx the context to use. 512 | * @param iri the IRI to compact. 513 | * @param usedCtx a context to update if a value was used from "ctx". 514 | * 515 | * @return the compacted IRI as a term or prefix or the original IRI. 516 | */ 517 | function _compactIri($ctx, $iri, $usedCtx) 518 | { 519 | $rval = null; 520 | 521 | // check the context for a term that could shorten the IRI 522 | // (give preference to terms over prefixes) 523 | foreach($ctx as $key => $value) 524 | { 525 | // skip special context keys (start with '@') 526 | if(strlen($key) > 0 and $key[0] !== '@') 527 | { 528 | // compact to a term 529 | if($iri === _getTermIri($ctx, $key)) 530 | { 531 | $rval = $key; 532 | if($usedCtx !== null) 533 | { 534 | $usedCtx->$key = _clone($ctx->$key); 535 | } 536 | break; 537 | } 538 | } 539 | } 540 | 541 | // term not found, if term is keyword, use alias 542 | if($rval === null) 543 | { 544 | $keywords = _getKeywords($ctx); 545 | if(property_exists($keywords, $iri)) 546 | { 547 | $rval = $keywords->{$iri}; 548 | if($rval !== $iri and $usedCtx !== null) 549 | { 550 | $usedCtx->$rval = $iri; 551 | } 552 | } 553 | } 554 | 555 | // term not found, check the context for a prefix 556 | if($rval === null) 557 | { 558 | foreach($ctx as $key => $value) 559 | { 560 | // skip special context keys (start with '@') 561 | if(strlen($key) > 0 and $key[0] !== '@') 562 | { 563 | // see if IRI begins with the next IRI from the context 564 | $ctxIri = _getTermIri($ctx, $key); 565 | if($ctxIri !== null) 566 | { 567 | $idx = strpos($iri, $ctxIri); 568 | 569 | // compact to a prefix 570 | if($idx === 0 and strlen($iri) > strlen($ctxIri)) 571 | { 572 | $rval = $key . ':' . substr($iri, strlen($ctxIri)); 573 | if($usedCtx !== null) 574 | { 575 | $usedCtx->$key = _clone($ctx->$key); 576 | } 577 | break; 578 | } 579 | } 580 | } 581 | } 582 | } 583 | 584 | // could not compact IRI 585 | if($rval === null) 586 | { 587 | $rval = $iri; 588 | } 589 | 590 | return $rval; 591 | } 592 | 593 | /** 594 | * Expands a term into an absolute IRI. The term may be a regular term, a 595 | * prefix, a relative IRI, or an absolute IRI. In any case, the associated 596 | * absolute IRI will be returned. 597 | * 598 | * @param ctx the context to use. 599 | * @param term the term to expand. 600 | * @param usedCtx a context to update if a value was used from "ctx". 601 | * 602 | * @return the expanded term as an absolute IRI. 603 | */ 604 | function _expandTerm($ctx, $term, $usedCtx) 605 | { 606 | $rval = $term; 607 | 608 | // get JSON-LD keywords 609 | $keywords = _getKeywords($ctx); 610 | 611 | // 1. If the property has a colon, it is a prefix or an absolute IRI: 612 | $idx = strpos($term, ':'); 613 | if($idx !== false) 614 | { 615 | // get the potential prefix 616 | $prefix = substr($term, 0, $idx); 617 | 618 | // expand term if prefix is in context, otherwise leave it be 619 | if(property_exists($ctx, $prefix)) 620 | { 621 | // prefix found, expand property to absolute IRI 622 | $iri = _getTermIri($ctx, $prefix); 623 | $rval = $iri . substr($term, $idx + 1); 624 | if($usedCtx !== null) 625 | { 626 | $usedCtx->$prefix = _clone($ctx->$prefix); 627 | } 628 | } 629 | } 630 | // 2. If the property is in the context, then it's a term. 631 | else if(property_exists($ctx, $term)) 632 | { 633 | $rval = _getTermIri($ctx, $term); 634 | if($usedCtx !== null) 635 | { 636 | $usedCtx->$term = _clone($ctx->$term); 637 | } 638 | } 639 | // 3. The property is a keyword. 640 | else 641 | { 642 | foreach($keywords as $key => $value) 643 | { 644 | if($term === $value) 645 | { 646 | $rval = $key; 647 | break; 648 | } 649 | } 650 | } 651 | 652 | return $rval; 653 | } 654 | 655 | /** 656 | * Gets whether or not a value is a reference to a subject (or a subject with 657 | * no properties). 658 | * 659 | * @param value the value to check. 660 | * 661 | * @return true if the value is a reference to a subject, false if not. 662 | */ 663 | function _isReference($value) 664 | { 665 | // Note: A value is a reference to a subject if all of these hold true: 666 | // 1. It is an Object. 667 | // 2. It is has an @id key. 668 | // 3. It has only 1 key. 669 | return ($value !== null and 670 | is_object($value) and 671 | property_exists($value, '@id') and 672 | count(get_object_vars($value)) === 1); 673 | }; 674 | 675 | /** 676 | * Gets whether or not a value is a subject with properties. 677 | * 678 | * @param value the value to check. 679 | * 680 | * @return true if the value is a subject with properties, false if not. 681 | */ 682 | function _isSubject($value) 683 | { 684 | $rval = false; 685 | 686 | // Note: A value is a subject if all of these hold true: 687 | // 1. It is an Object. 688 | // 2. It is not a literal (@value). 689 | // 3. It has more than 1 key OR any existing key is not '@id'. 690 | if($value !== null and is_object($value) and 691 | !property_exists($value, '@value')) 692 | { 693 | $keyCount = count(get_object_vars($value)); 694 | $rval = ($keyCount > 1 or !property_exists($value, '@id')); 695 | } 696 | 697 | return $rval; 698 | } 699 | 700 | function _isBlankNodeIri($v) 701 | { 702 | return strpos($v, '_:') === 0; 703 | } 704 | 705 | function _isNamedBlankNode($v) 706 | { 707 | // look for "_:" at the beginning of the subject 708 | return ( 709 | is_object($v) and property_exists($v, '@id') and 710 | _isBlankNodeIri($v->{'@id'})); 711 | } 712 | 713 | function _isBlankNode($v) 714 | { 715 | // look for a subject with no ID or a blank node ID 716 | return ( 717 | _isSubject($v) and 718 | (!property_exists($v, '@id') or _isNamedBlankNode($v))); 719 | } 720 | 721 | /** 722 | * Compares two values. 723 | * 724 | * @param v1 the first value. 725 | * @param v2 the second value. 726 | * 727 | * @return -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2. 728 | */ 729 | function _compare($v1, $v2) 730 | { 731 | $rval = 0; 732 | 733 | if(is_array($v1) and is_array($v2)) 734 | { 735 | $length = count($v1); 736 | for($i = 0; $i < $length and $rval === 0; ++$i) 737 | { 738 | $rval = _compare($v1[$i], $v2[$i]); 739 | } 740 | } 741 | else 742 | { 743 | $rval = ($v1 < $v2 ? -1 : ($v1 > $v2 ? 1 : 0)); 744 | } 745 | 746 | return $rval; 747 | } 748 | 749 | /** 750 | * Compares two keys in an object. If the key exists in one object 751 | * and not the other, the object with the key is less. If the key exists in 752 | * both objects, then the one with the lesser value is less. 753 | * 754 | * @param o1 the first object. 755 | * @param o2 the second object. 756 | * @param key the key. 757 | * 758 | * @return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2. 759 | */ 760 | function _compareObjectKeys($o1, $o2, $key) 761 | { 762 | $rval = 0; 763 | if(property_exists($o1, $key)) 764 | { 765 | if(property_exists($o2, $key)) 766 | { 767 | $rval = _compare($o1->$key, $o2->$key); 768 | } 769 | else 770 | { 771 | $rval = -1; 772 | } 773 | } 774 | else if(property_exists($o2, $key)) 775 | { 776 | $rval = 1; 777 | } 778 | return $rval; 779 | } 780 | 781 | /** 782 | * Compares two object values. 783 | * 784 | * @param o1 the first object. 785 | * @param o2 the second object. 786 | * 787 | * @return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2. 788 | */ 789 | function _compareObjects($o1, $o2) 790 | { 791 | $rval = 0; 792 | 793 | if(is_string($o1)) 794 | { 795 | if(!is_string($o2)) 796 | { 797 | $rval = -1; 798 | } 799 | else 800 | { 801 | $rval = _compare($o1, $o2); 802 | } 803 | } 804 | else if(is_string($o2)) 805 | { 806 | $rval = 1; 807 | } 808 | else 809 | { 810 | $rval = _compareObjectKeys($o1, $o2, '@value'); 811 | if($rval === 0) 812 | { 813 | if(property_exists($o1, '@value')) 814 | { 815 | $rval = _compareObjectKeys($o1, $o2, '@type'); 816 | if($rval === 0) 817 | { 818 | $rval = _compareObjectKeys($o1, $o2, '@language'); 819 | } 820 | } 821 | // both are '@id' objects 822 | else 823 | { 824 | $rval = _compare($o1->{'@id'}, $o2->{'@id'}); 825 | } 826 | } 827 | } 828 | 829 | return $rval; 830 | } 831 | 832 | /** 833 | * Filter function for bnodes. 834 | * 835 | * @param e the array element. 836 | * 837 | * @return true to return the element in the filter results, false not to. 838 | */ 839 | function _filterBlankNodes($e) 840 | { 841 | return !_isNamedBlankNode($e); 842 | } 843 | 844 | /** 845 | * Compares the object values between two bnodes. 846 | * 847 | * @param a the first bnode. 848 | * @param b the second bnode. 849 | * 850 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 851 | */ 852 | function _compareBlankNodeObjects($a, $b) 853 | { 854 | $rval = 0; 855 | 856 | /* 857 | 3. For each property, compare sorted object values. 858 | 3.1. The bnode with fewer objects is first. 859 | 3.2. For each object value, compare only literals (@values) and non-bnodes. 860 | 3.2.1. The bnode with fewer non-bnodes is first. 861 | 3.2.2. The bnode with a string object is first. 862 | 3.2.3. The bnode with the alphabetically-first string is first. 863 | 3.2.4. The bnode with a @value is first. 864 | 3.2.5. The bnode with the alphabetically-first @value is first. 865 | 3.2.6. The bnode with the alphabetically-first @type is first. 866 | 3.2.7. The bnode with a @language is first. 867 | 3.2.8. The bnode with the alphabetically-first @language is first. 868 | 3.2.9. The bnode with the alphabetically-first @id is first. 869 | */ 870 | 871 | foreach($a as $p => $value) 872 | { 873 | // skip IDs (IRIs) 874 | if($p !== '@id') 875 | { 876 | // step #3.1 877 | $lenA = is_array($a->$p) ? count($a->$p) : 1; 878 | $lenB = is_array($b->$p) ? count($b->$p) : 1; 879 | $rval = _compare($lenA, $lenB); 880 | 881 | // step #3.2.1 882 | if($rval === 0) 883 | { 884 | // normalize objects to an array 885 | $objsA = $a->$p; 886 | $objsB = $b->$p; 887 | if(!is_array($objsA)) 888 | { 889 | $objsA = array($objsA); 890 | $objsB = array($objsB); 891 | } 892 | 893 | // compare non-bnodes (remove bnodes from comparison) 894 | $objsA = array_filter($objsA, '_filterBlankNodes'); 895 | $objsB = array_filter($objsB, '_filterBlankNodes'); 896 | $objsALen = count($objsA); 897 | $rval = _compare($objsALen, count($objsB)); 898 | } 899 | 900 | // steps #3.2.2-3.2.9 901 | if($rval === 0) 902 | { 903 | usort($objsA, '_compareObjects'); 904 | usort($objsB, '_compareObjects'); 905 | for($i = 0; $i < $objsALen and $rval === 0; ++$i) 906 | { 907 | $rval = _compareObjects($objsA[$i], $objsB[$i]); 908 | } 909 | } 910 | 911 | if($rval !== 0) 912 | { 913 | break; 914 | } 915 | } 916 | } 917 | 918 | return $rval; 919 | } 920 | 921 | /** 922 | * A blank node name generator that uses a prefix and counter. 923 | */ 924 | class NameGenerator 925 | { 926 | public function __construct($prefix) 927 | { 928 | $this->count = -1; 929 | $this->base = '_:' . $prefix; 930 | } 931 | 932 | public function next() 933 | { 934 | $this->count += 1; 935 | return $this->current(); 936 | } 937 | 938 | public function current() 939 | { 940 | return $this->base . $this->count; 941 | } 942 | 943 | public function inNamespace($iri) 944 | { 945 | return strpos($iri, $this->base) === 0; 946 | } 947 | } 948 | 949 | /** 950 | * Populates a map of all named subjects from the given input and an array 951 | * of all unnamed bnodes (includes embedded ones). 952 | * 953 | * @param input the input (must be expanded, no context). 954 | * @param subjects the subjects map to populate. 955 | * @param bnodes the bnodes array to populate. 956 | */ 957 | function _collectSubjects($input, $subjects, $bnodes) 958 | { 959 | if($input === null) 960 | { 961 | // nothing to collect 962 | } 963 | else if(is_array($input)) 964 | { 965 | foreach($input as $value) 966 | { 967 | _collectSubjects($value, $subjects, $bnodes); 968 | } 969 | } 970 | else if(is_object($input)) 971 | { 972 | if(property_exists($input, '@id')) 973 | { 974 | // graph literal/disjoint graph 975 | if(is_array($input->{'@id'})) 976 | { 977 | _collectSubjects($input->{'@id'}, $subjects, $bnodes); 978 | } 979 | // named subject 980 | else if(_isSubject($input)) 981 | { 982 | $subjects->{$input->{'@id'}} = $input; 983 | } 984 | } 985 | // unnamed blank node 986 | else if(_isBlankNode($input)) 987 | { 988 | $bnodes[] = $input; 989 | } 990 | 991 | // recurse through subject properties 992 | foreach($input as $value) 993 | { 994 | _collectSubjects($value, $subjects, $bnodes); 995 | } 996 | } 997 | } 998 | 999 | /** 1000 | * Filters duplicate objects. 1001 | */ 1002 | class DuplicateFilter 1003 | { 1004 | public function __construct($obj) 1005 | { 1006 | $this->obj = $obj; 1007 | } 1008 | 1009 | public function filter($e) 1010 | { 1011 | return (_compareObjects($e, $this->obj) === 0); 1012 | } 1013 | } 1014 | 1015 | /** 1016 | * Flattens the given value into a map of unique subjects. It is assumed that 1017 | * all blank nodes have been uniquely named before this call. Array values for 1018 | * properties will be sorted. 1019 | * 1020 | * @param parent the value's parent, NULL for none. 1021 | * @param parentProperty the property relating the value to the parent. 1022 | * @param value the value to flatten. 1023 | * @param subjects the map of subjects to write to. 1024 | */ 1025 | function _flatten($parent, $parentProperty, $value, $subjects) 1026 | { 1027 | $flattened = null; 1028 | 1029 | if($value === null) 1030 | { 1031 | // drop null values 1032 | } 1033 | else if(is_array($value)) 1034 | { 1035 | // list of objects or a disjoint graph 1036 | foreach($value as $v) 1037 | { 1038 | _flatten($parent, $parentProperty, $v, $subjects); 1039 | } 1040 | } 1041 | else if(is_object($value)) 1042 | { 1043 | // already-expanded value or special-case reference-only @type 1044 | if(property_exists($value, '@value') or $parentProperty === '@type') 1045 | { 1046 | $flattened = _clone($value); 1047 | } 1048 | // graph literal/disjoint graph 1049 | else if(is_array($value->{'@id'})) 1050 | { 1051 | // cannot flatten embedded graph literals 1052 | if($parent !== null) 1053 | { 1054 | throw new Exception('Embedded graph literals cannot be flattened.'); 1055 | } 1056 | 1057 | // top-level graph literal 1058 | foreach($value->{'@id'} as $k => $v) 1059 | { 1060 | _flatten($parent, $parentProperty, $v, $subjects); 1061 | } 1062 | } 1063 | // regular subject 1064 | else 1065 | { 1066 | // create or fetch existing subject 1067 | if(property_exists($subjects, $value->{'@id'})) 1068 | { 1069 | // FIXME: '@id' might be a graph literal (as {}) 1070 | $subject = $subjects->{$value->{'@id'}}; 1071 | } 1072 | else 1073 | { 1074 | // FIXME: '@id' might be a graph literal (as {}) 1075 | $subject = new stdClass(); 1076 | $subject->{'@id'} = $value->{'@id'}; 1077 | $subjects->{$value->{'@id'}} = $subject; 1078 | } 1079 | $flattened = new stdClass(); 1080 | $flattened->{'@id'} = $subject->{'@id'}; 1081 | 1082 | // flatten embeds 1083 | foreach($value as $key => $v) 1084 | { 1085 | // drop null values, skip @id (it is already set above) 1086 | if($v !== null and $key !== '@id') 1087 | { 1088 | if(property_exists($subject, $key)) 1089 | { 1090 | if(!is_array($subject->$key)) 1091 | { 1092 | $subject->$key = new ArrayObject(array($subject->$key)); 1093 | } 1094 | else 1095 | { 1096 | $subject->$key = new ArrayObject($subject->$key); 1097 | } 1098 | } 1099 | else 1100 | { 1101 | $subject->$key = new ArrayObject(); 1102 | } 1103 | 1104 | _flatten($subject->$key, $key, $value->$key, $subjects); 1105 | $subject->$key = (array)$subject->$key; 1106 | if(count($subject->$key) === 1) 1107 | { 1108 | // convert subject[key] to object if it has only 1 1109 | $arr = $subject->$key; 1110 | $subject->$key = $arr[0]; 1111 | } 1112 | } 1113 | } 1114 | } 1115 | } 1116 | // string value 1117 | else 1118 | { 1119 | $flattened = $value; 1120 | } 1121 | 1122 | // add flattened value to parent 1123 | if($flattened !== null and $parent !== null) 1124 | { 1125 | if($parent instanceof ArrayObject) 1126 | { 1127 | // do not add duplicate IRIs for the same property 1128 | $duplicate = count(array_filter( 1129 | (array)$parent, array( 1130 | new DuplicateFilter($flattened), 'filter'))) > 0; 1131 | if(!$duplicate) 1132 | { 1133 | $parent[] = $flattened; 1134 | } 1135 | } 1136 | else 1137 | { 1138 | $parent->$parentProperty = $flattened; 1139 | } 1140 | } 1141 | } 1142 | 1143 | /** 1144 | * A MappingBuilder is used to build a mapping of existing blank node names 1145 | * to a form for serialization. The serialization is used to compare blank 1146 | * nodes against one another to determine a sort order. 1147 | */ 1148 | class MappingBuilder 1149 | { 1150 | /** 1151 | * Constructs a new MappingBuilder. 1152 | */ 1153 | public function __construct() 1154 | { 1155 | $this->count = 1; 1156 | $this->processed = new stdClass(); 1157 | $this->mapping = new stdClass(); 1158 | $this->adj = new stdClass(); 1159 | $this->keyStack = array(); 1160 | $entry = new stdClass(); 1161 | $entry->keys = array('s1'); 1162 | $entry->idx = 0; 1163 | $this->keyStack[] = $entry; 1164 | $this->done = new stdClass(); 1165 | $this->s = ''; 1166 | } 1167 | 1168 | /** 1169 | * Copies this MappingBuilder. 1170 | * 1171 | * @return the MappingBuilder copy. 1172 | */ 1173 | public function copy() 1174 | { 1175 | $rval = new MappingBuilder(); 1176 | $rval->count = $this->count; 1177 | $rval->processed = _clone($this->processed); 1178 | $rval->mapping = _clone($this->mapping); 1179 | $rval->adj = _clone($this->adj); 1180 | $rval->keyStack = _clone($this->keyStack); 1181 | $rval->done = _clone($this->done); 1182 | $rval->s = $this->s; 1183 | return $rval; 1184 | } 1185 | 1186 | /** 1187 | * Maps the next name to the given bnode IRI if the bnode IRI isn't already 1188 | * in the mapping. If the given bnode IRI is canonical, then it will be 1189 | * given a shortened form of the same name. 1190 | * 1191 | * @param iri the blank node IRI to map the next name to. 1192 | * 1193 | * @return the mapped name. 1194 | */ 1195 | public function mapNode($iri) 1196 | { 1197 | if(!property_exists($this->mapping, $iri)) 1198 | { 1199 | if(strpos($iri, '_:c14n') === 0) 1200 | { 1201 | $this->mapping->$iri = 'c' . substr($iri, 6); 1202 | } 1203 | else 1204 | { 1205 | $this->mapping->$iri = 's' . $this->count++; 1206 | } 1207 | } 1208 | return $this->mapping->$iri; 1209 | } 1210 | } 1211 | 1212 | /** 1213 | * Rotates the elements in an array one position. 1214 | * 1215 | * @param a the array. 1216 | */ 1217 | function _rotate(&$a) 1218 | { 1219 | $e = array_shift($a); 1220 | array_push($a, $e); 1221 | return $e; 1222 | } 1223 | 1224 | /** 1225 | * Compares two serializations for the same blank node. If the two 1226 | * serializations aren't complete enough to determine if they are equal (or if 1227 | * they are actually equal), 0 is returned. 1228 | * 1229 | * @param s1 the first serialization. 1230 | * @param s2 the second serialization. 1231 | * 1232 | * @return -1 if s1 < s2, 0 if s1 == s2 (or indeterminate), 1 if s1 > v2. 1233 | */ 1234 | function _compareSerializations($s1, $s2) 1235 | { 1236 | $rval = 0; 1237 | 1238 | $s1Len = strlen($s1); 1239 | $s2Len = strlen($s2); 1240 | if($s1Len == $s2Len) 1241 | { 1242 | $rval = strcmp($s1, $s2); 1243 | } 1244 | else 1245 | { 1246 | $rval = strncmp($s1, $s2, ($s1Len > $s2Len) ? $s2Len : $s1Len); 1247 | } 1248 | 1249 | return $rval; 1250 | } 1251 | 1252 | /** 1253 | * Compares two nodes based on their iris. 1254 | * 1255 | * @param a the first node. 1256 | * @param b the second node. 1257 | * 1258 | * @return -1 if iriA < iriB, 0 if iriA == iriB, 1 if iriA > iriB. 1259 | */ 1260 | function _compareIris($a, $b) 1261 | { 1262 | return _compare($a->{'@id'}, $b->{'@id'}); 1263 | } 1264 | 1265 | /** 1266 | * Filters blank node edges. 1267 | * 1268 | * @param e the edge to check. 1269 | * 1270 | * @return true if the edge is a blank node edge. 1271 | */ 1272 | function _filterBlankNodeEdges($e) 1273 | { 1274 | return _isBlankNodeIri($e->s); 1275 | } 1276 | 1277 | /** 1278 | * Sorts mapping keys based on their associated mapping values. 1279 | */ 1280 | class MappingKeySorter 1281 | { 1282 | public function __construct($mapping) 1283 | { 1284 | $this->mapping = $mapping; 1285 | } 1286 | 1287 | public function compare($a, $b) 1288 | { 1289 | return _compare($this->mapping->$a, $this->mapping->$b); 1290 | } 1291 | } 1292 | 1293 | /** 1294 | * A JSON-LD processor. 1295 | */ 1296 | class JsonLdProcessor 1297 | { 1298 | /** 1299 | * Constructs a JSON-LD processor. 1300 | */ 1301 | public function __construct() 1302 | { 1303 | } 1304 | 1305 | /** 1306 | * Recursively compacts a value. This method will compact IRIs to prefixes 1307 | * or terms and do reverse type coercion to compact a value. 1308 | * 1309 | * @param ctx the context to use. 1310 | * @param property the property that points to the value, NULL for none. 1311 | * @param value the value to compact. 1312 | * @param usedCtx a context to update if a value was used from "ctx". 1313 | * 1314 | * @return the compacted value. 1315 | */ 1316 | public function compact($ctx, $property, $value, $usedCtx) 1317 | { 1318 | $rval; 1319 | 1320 | // get JSON-LD keywords 1321 | $keywords = _getKeywords($ctx); 1322 | 1323 | if($value === null) 1324 | { 1325 | // return null, but check coerce type to add to usedCtx 1326 | $rval = null; 1327 | $this->getCoerceType($ctx, $property, $usedCtx); 1328 | } 1329 | else if(is_array($value)) 1330 | { 1331 | // recursively add compacted values to array 1332 | $rval = array(); 1333 | foreach($value as $v) 1334 | { 1335 | $rval[] = $this->compact($ctx, $property, $v, $usedCtx); 1336 | } 1337 | } 1338 | // graph literal/disjoint graph 1339 | else if( 1340 | is_object($value) and 1341 | property_exists($value, '@id') and 1342 | is_array($value->{'@id'})) 1343 | { 1344 | $rval = new stdClass(); 1345 | $rval->{$keywords->{'@id'}} = $this->compact( 1346 | $ctx, $property, $value->{'@id'}, $usedCtx); 1347 | } 1348 | // recurse if value is a subject 1349 | else if(_isSubject($value)) 1350 | { 1351 | // recursively handle sub-properties that aren't a sub-context 1352 | $rval = new stdClass(); 1353 | foreach($value as $key => $v) 1354 | { 1355 | if($v !== '@context') 1356 | { 1357 | // set object to compacted property, only overwrite existing 1358 | // properties if the property actually compacted 1359 | $p = _compactIri($ctx, $key, $usedCtx); 1360 | if($p !== $key or !property_exists($rval, $p)) 1361 | { 1362 | $rval->$p = $this->compact($ctx, $key, $v, $usedCtx); 1363 | } 1364 | } 1365 | } 1366 | } 1367 | else 1368 | { 1369 | // get coerce type 1370 | $coerce = $this->getCoerceType($ctx, $property, $usedCtx); 1371 | 1372 | // get type from value, to ensure coercion is valid 1373 | $type = null; 1374 | if(is_object($value)) 1375 | { 1376 | // type coercion can only occur if language is not specified 1377 | if(!property_exists($value, '@language')) 1378 | { 1379 | // type must match coerce type if specified 1380 | if(property_exists($value, '@type')) 1381 | { 1382 | $type = $value->{'@type'}; 1383 | } 1384 | // type is ID (IRI) 1385 | else if(property_exists($value, '@id')) 1386 | { 1387 | $type = '@id'; 1388 | } 1389 | // can be coerced to any type 1390 | else 1391 | { 1392 | $type = $coerce; 1393 | } 1394 | } 1395 | } 1396 | // type can be coerced to anything 1397 | else if(is_string($value)) 1398 | { 1399 | $type = $coerce; 1400 | } 1401 | 1402 | // types that can be auto-coerced from a JSON-builtin 1403 | if($coerce === null and 1404 | ($type === JSONLD_XSD_BOOLEAN or $type === JSONLD_XSD_INTEGER or 1405 | $type === JSONLD_XSD_DOUBLE)) 1406 | { 1407 | $coerce = $type; 1408 | } 1409 | 1410 | // do reverse type-coercion 1411 | if($coerce !== null) 1412 | { 1413 | // type is only null if a language was specified, which is an error 1414 | // if type coercion is specified 1415 | if($type === null) 1416 | { 1417 | throw new Exception( 1418 | 'Cannot coerce type when a language is specified. ' . 1419 | 'The language information would be lost.'); 1420 | } 1421 | // if the value type does not match the coerce type, it is an error 1422 | else if($type !== $coerce) 1423 | { 1424 | throw new Exception( 1425 | 'Cannot coerce type because the type does not match.'); 1426 | } 1427 | // do reverse type-coercion 1428 | else 1429 | { 1430 | if(is_object($value)) 1431 | { 1432 | if(property_exists($value, '@id')) 1433 | { 1434 | $rval = $value->{'@id'}; 1435 | } 1436 | else if(property_exists($value, '@value')) 1437 | { 1438 | $rval = $value->{'@value'}; 1439 | } 1440 | } 1441 | else 1442 | { 1443 | $rval = $value; 1444 | } 1445 | 1446 | // do basic JSON types conversion 1447 | if($coerce === JSONLD_XSD_BOOLEAN) 1448 | { 1449 | $rval = ($rval === 'true' or $rval != 0); 1450 | } 1451 | else if($coerce === JSONLD_XSD_DOUBLE) 1452 | { 1453 | $rval = floatval($rval); 1454 | } 1455 | else if($coerce === JSONLD_XSD_INTEGER) 1456 | { 1457 | $rval = intval($rval); 1458 | } 1459 | } 1460 | } 1461 | // no type-coercion, just change keywords/copy value 1462 | else if(is_object($value)) 1463 | { 1464 | $rval = new stdClass(); 1465 | foreach($value as $key => $v) 1466 | { 1467 | $rval->{$keywords->$key} = $v; 1468 | } 1469 | } 1470 | else 1471 | { 1472 | $rval = _clone($value); 1473 | } 1474 | 1475 | // compact IRI 1476 | if($type === '@id') 1477 | { 1478 | if(is_object($rval)) 1479 | { 1480 | $rval->{$keywords->{'@id'}} = _compactIri( 1481 | $ctx, $rval->{$keywords->{'@id'}}, $usedCtx); 1482 | } 1483 | else 1484 | { 1485 | $rval = _compactIri($ctx, $rval, $usedCtx); 1486 | } 1487 | } 1488 | } 1489 | 1490 | return $rval; 1491 | } 1492 | 1493 | /** 1494 | * Recursively expands a value using the given context. Any context in 1495 | * the value will be removed. 1496 | * 1497 | * @param ctx the context. 1498 | * @param property the property that points to the value, NULL for none. 1499 | * @param value the value to expand. 1500 | * 1501 | * @return the expanded value. 1502 | */ 1503 | public function expand($ctx, $property, $value) 1504 | { 1505 | $rval; 1506 | 1507 | // TODO: add data format error detection? 1508 | 1509 | // value is null, nothing to expand 1510 | if($value === null) 1511 | { 1512 | $rval = null; 1513 | } 1514 | // if no property is specified and the value is a string (this means the 1515 | // value is a property itself), expand to an IRI 1516 | else if($property === null and is_string($value)) 1517 | { 1518 | $rval = _expandTerm($ctx, $value, null); 1519 | } 1520 | else if(is_array($value)) 1521 | { 1522 | // recursively add expanded values to array 1523 | $rval = array(); 1524 | foreach($value as $v) 1525 | { 1526 | $rval[] = $this->expand($ctx, $property, $v); 1527 | } 1528 | } 1529 | else if(is_object($value)) 1530 | { 1531 | // if value has a context, use it 1532 | if(property_exists($value, '@context')) 1533 | { 1534 | $ctx = jsonld_merge_contexts($ctx, $value->{'@context'}); 1535 | } 1536 | 1537 | // recursively handle sub-properties that aren't a sub-context 1538 | $rval = new stdClass(); 1539 | foreach($value as $key => $v) 1540 | { 1541 | // preserve frame keywords 1542 | if($key === '@embed' or $key === '@explicit' or 1543 | $key === '@default' or $key === '@omitDefault') 1544 | { 1545 | _setProperty($rval, $key, _clone($v)); 1546 | } 1547 | else if($key !== '@context') 1548 | { 1549 | // set object to expanded property 1550 | _setProperty( 1551 | $rval, _expandTerm($ctx, $key, null), 1552 | $this->expand($ctx, $key, $v)); 1553 | } 1554 | } 1555 | } 1556 | else 1557 | { 1558 | // do type coercion 1559 | $coerce = $this->getCoerceType($ctx, $property, null); 1560 | 1561 | // get JSON-LD keywords 1562 | $keywords = _getKeywords($ctx); 1563 | 1564 | // automatic coercion for basic JSON types 1565 | if($coerce === null and !is_string($value) and 1566 | (is_numeric($value) or is_bool($value))) 1567 | { 1568 | if(is_bool($value)) 1569 | { 1570 | $coerce = JSONLD_XSD_BOOLEAN; 1571 | } 1572 | else if(is_int($value)) 1573 | { 1574 | $coerce = JSONLD_XSD_INTEGER; 1575 | } 1576 | else 1577 | { 1578 | $coerce = JSONLD_XSD_DOUBLE; 1579 | } 1580 | } 1581 | 1582 | // special-case expand @id and @type (skips '@id' expansion) 1583 | if($property === '@id' or $property === $keywords->{'@id'} or 1584 | $property === '@type' or $property === $keywords->{'@type'}) 1585 | { 1586 | $rval = _expandTerm($ctx, $value, null); 1587 | } 1588 | // coerce to appropriate type 1589 | else if($coerce !== null) 1590 | { 1591 | $rval = new stdClass(); 1592 | 1593 | // expand ID (IRI) 1594 | if($coerce === '@id') 1595 | { 1596 | $rval->{'@id'} = _expandTerm($ctx, $value, null); 1597 | } 1598 | // other type 1599 | else 1600 | { 1601 | $rval->{'@type'} = $coerce; 1602 | if($coerce === JSONLD_XSD_DOUBLE) 1603 | { 1604 | // do special JSON-LD double format 1605 | $value = preg_replace( 1606 | '/(e(?:\+|-))([0-9])$/', '${1}0${2}', 1607 | sprintf('%1.6e', $value)); 1608 | } 1609 | else if($coerce === JSONLD_XSD_BOOLEAN) 1610 | { 1611 | $value = $value ? 'true' : 'false'; 1612 | } 1613 | $rval->{'@value'} = '' . $value; 1614 | } 1615 | } 1616 | // nothing to coerce 1617 | else 1618 | { 1619 | $rval = '' . $value; 1620 | } 1621 | } 1622 | 1623 | return $rval; 1624 | } 1625 | 1626 | /** 1627 | * Normalizes a JSON-LD object. 1628 | * 1629 | * @param input the JSON-LD object to normalize. 1630 | * 1631 | * @return the normalized JSON-LD object. 1632 | */ 1633 | public function normalize($input) 1634 | { 1635 | $rval = array(); 1636 | 1637 | // TODO: validate context 1638 | 1639 | if($input !== null) 1640 | { 1641 | // create name generator state 1642 | $this->ng = new stdClass(); 1643 | $this->ng->tmp = null; 1644 | $this->ng->c14n = null; 1645 | 1646 | // expand input 1647 | $expanded = $this->expand(new stdClass(), null, $input); 1648 | 1649 | // assign names to unnamed bnodes 1650 | $this->nameBlankNodes($expanded); 1651 | 1652 | // flatten 1653 | $subjects = new stdClass(); 1654 | _flatten(null, null, $expanded, $subjects); 1655 | 1656 | // append subjects with sorted properties to array 1657 | foreach($subjects as $key => $s) 1658 | { 1659 | $sorted = new stdClass(); 1660 | $keys = array_keys((array)$s); 1661 | sort($keys); 1662 | foreach($keys as $i => $k) 1663 | { 1664 | $sorted->$k = $s->$k; 1665 | } 1666 | $rval[] = $sorted; 1667 | } 1668 | 1669 | // canonicalize blank nodes 1670 | $this->canonicalizeBlankNodes($rval); 1671 | 1672 | // sort output 1673 | usort($rval, '_compareIris'); 1674 | } 1675 | 1676 | return $rval; 1677 | } 1678 | 1679 | /** 1680 | * Gets the coerce type for the given property. 1681 | * 1682 | * @param ctx the context to use. 1683 | * @param property the property to get the coerced type for. 1684 | * @param usedCtx a context to update if a value was used from "ctx". 1685 | * 1686 | * @return the coerce type, null for none. 1687 | */ 1688 | public function getCoerceType($ctx, $property, $usedCtx) 1689 | { 1690 | $rval = null; 1691 | 1692 | // get expanded property 1693 | $p = _expandTerm($ctx, $property, null); 1694 | 1695 | // built-in type coercion JSON-LD-isms 1696 | if($p === '@id' or $p === '@type') 1697 | { 1698 | $rval = '@id'; 1699 | } 1700 | else 1701 | { 1702 | // look up compacted property in coercion map 1703 | $p = _compactIri($ctx, $p, null); 1704 | if(property_exists($ctx, $p) and is_object($ctx->$p) and 1705 | property_exists($ctx->$p, '@type')) 1706 | { 1707 | // property found, return expanded type 1708 | $type = $ctx->$p->{'@type'}; 1709 | $rval = _expandTerm($ctx, $type, $usedCtx); 1710 | if($usedCtx !== null) 1711 | { 1712 | $usedCtx->$p = _clone($ctx->$p); 1713 | } 1714 | } 1715 | } 1716 | 1717 | return $rval; 1718 | } 1719 | 1720 | /** 1721 | * Assigns unique names to blank nodes that are unnamed in the given input. 1722 | * 1723 | * @param input the input to assign names to. 1724 | */ 1725 | public function nameBlankNodes($input) 1726 | { 1727 | // create temporary blank node name generator 1728 | $ng = $this->ng->tmp = new NameGenerator('tmp'); 1729 | 1730 | // collect subjects and unnamed bnodes 1731 | $subjects = new stdClass(); 1732 | $bnodes = new ArrayObject(); 1733 | _collectSubjects($input, $subjects, $bnodes); 1734 | 1735 | // uniquely name all unnamed bnodes 1736 | foreach($bnodes as $i => $bnode) 1737 | { 1738 | if(!property_exists($bnode, '@id')) 1739 | { 1740 | // generate names until one is unique 1741 | while(property_exists($subjects, $ng->next())); 1742 | $bnode->{'@id'} = $ng->current(); 1743 | $subjects->{$ng->current()} = $bnode; 1744 | } 1745 | } 1746 | } 1747 | 1748 | /** 1749 | * Renames a blank node, changing its references, etc. The method assumes 1750 | * that the given name is unique. 1751 | * 1752 | * @param b the blank node to rename. 1753 | * @param id the new name to use. 1754 | */ 1755 | public function renameBlankNode($b, $id) 1756 | { 1757 | $old = $b->{'@id'}; 1758 | 1759 | // update bnode IRI 1760 | $b->{'@id'} = $id; 1761 | 1762 | // update subjects map 1763 | $subjects = $this->subjects; 1764 | $subjects->$id = $subjects->$old; 1765 | unset($subjects->$old); 1766 | 1767 | // update reference and property lists 1768 | $this->edges->refs->$id = $this->edges->refs->$old; 1769 | $this->edges->props->$id = $this->edges->props->$old; 1770 | unset($this->edges->refs->$old); 1771 | unset($this->edges->props->$old); 1772 | 1773 | // update references to this bnode 1774 | $refs = $this->edges->refs->$id->all; 1775 | foreach($refs as $i => $r) 1776 | { 1777 | $iri = $r->s; 1778 | if($iri === $old) 1779 | { 1780 | $iri = $id; 1781 | } 1782 | $ref = $subjects->$iri; 1783 | $props = $this->edges->props->$iri->all; 1784 | foreach($props as $prop) 1785 | { 1786 | if($prop->s === $old) 1787 | { 1788 | $prop->s = $id; 1789 | 1790 | // normalize property to array for single code-path 1791 | $p = $prop->p; 1792 | $tmp = is_object($ref->$p) ? array($ref->$p) : 1793 | (is_array($ref->$p) ? $ref->$p : array()); 1794 | $length = count($tmp); 1795 | for($n = 0; $n < $length; ++$n) 1796 | { 1797 | if(is_object($tmp[$n]) and 1798 | property_exists($tmp[$n], '@id') and 1799 | $tmp[$n]->{'@id'} === $old) 1800 | { 1801 | $tmp[$n]->{'@id'} = $id; 1802 | } 1803 | } 1804 | } 1805 | } 1806 | } 1807 | 1808 | // update references from this bnode 1809 | $props = $this->edges->props->$id->all; 1810 | foreach($props as $prop) 1811 | { 1812 | $iri = $prop->s; 1813 | $refs = $this->edges->refs->$iri->all; 1814 | foreach($refs as $r) 1815 | { 1816 | if($r->s === $old) 1817 | { 1818 | $r->s = $id; 1819 | } 1820 | } 1821 | } 1822 | } 1823 | 1824 | /** 1825 | * Canonically names blank nodes in the given input. 1826 | * 1827 | * @param input the flat input graph to assign names to. 1828 | */ 1829 | public function canonicalizeBlankNodes($input) 1830 | { 1831 | // create serialization state 1832 | $this->renamed = new stdClass(); 1833 | $this->mappings = new stdClass(); 1834 | $this->serializations = new stdClass(); 1835 | 1836 | // collect subjects and bnodes from flat input graph 1837 | $edges = $this->edges = new stdClass(); 1838 | $edges->refs = new stdClass(); 1839 | $edges->props = new stdClass(); 1840 | $subjects = $this->subjects = new stdClass(); 1841 | $bnodes = array(); 1842 | foreach($input as $v) 1843 | { 1844 | $iri = $v->{'@id'}; 1845 | $subjects->$iri = $v; 1846 | $edges->refs->$iri = new stdClass(); 1847 | $edges->refs->$iri->all = array(); 1848 | $edges->refs->$iri->bnodes = array(); 1849 | $edges->props->$iri = new stdClass(); 1850 | $edges->props->$iri->all = array(); 1851 | $edges->props->$iri->bnodes = array(); 1852 | if(_isBlankNodeIri($iri)) 1853 | { 1854 | $bnodes[] = $v; 1855 | } 1856 | } 1857 | 1858 | // collect edges in the graph 1859 | $this->collectEdges(); 1860 | 1861 | // create canonical blank node name generator 1862 | $c14n = $this->ng->c14n = new NameGenerator('c14n'); 1863 | $ngTmp = $this->ng->tmp; 1864 | 1865 | // rename all bnodes that happen to be in the c14n namespace 1866 | // and initialize serializations 1867 | foreach($bnodes as $i => $bnode) 1868 | { 1869 | $iri = $bnode->{'@id'}; 1870 | if($c14n->inNamespace($iri)) 1871 | { 1872 | // generate names until one is unique 1873 | while(property_exists($subjects, $ngTmp->next())); 1874 | $this->renameBlankNode($bnode, $ngTmp->current()); 1875 | $iri = $bnode->{'@id'}; 1876 | } 1877 | $this->serializations->$iri = new stdClass(); 1878 | $this->serializations->$iri->props = null; 1879 | $this->serializations->$iri->refs = null; 1880 | } 1881 | 1882 | // keep sorting and naming blank nodes until they are all named 1883 | $resort = true; 1884 | while(count($bnodes) > 0) 1885 | { 1886 | if($resort) 1887 | { 1888 | $resort = false; 1889 | usort($bnodes, array($this, 'deepCompareBlankNodes')); 1890 | } 1891 | 1892 | // name all bnodes according to the first bnode's relation mappings 1893 | // (if it has mappings then a resort will be necessary) 1894 | $bnode = array_shift($bnodes); 1895 | $iri = $bnode->{'@id'}; 1896 | $resort = ($this->serializations->$iri->{'props'} !== null); 1897 | $dirs = array('props', 'refs'); 1898 | foreach($dirs as $dir) 1899 | { 1900 | // if no serialization has been computed, name only the first node 1901 | if($this->serializations->$iri->$dir === null) 1902 | { 1903 | $mapping = new stdClass(); 1904 | $mapping->$iri = 's1'; 1905 | } 1906 | else 1907 | { 1908 | $mapping = $this->serializations->$iri->$dir->m; 1909 | } 1910 | 1911 | // sort keys by value to name them in order 1912 | $keys = array_keys((array)$mapping); 1913 | usort($keys, array(new MappingKeySorter($mapping), 'compare')); 1914 | 1915 | // name bnodes in mapping 1916 | $renamed = array(); 1917 | foreach($keys as $i => $iriK) 1918 | { 1919 | if(!$c14n->inNamespace($iri) and 1920 | property_exists($subjects, $iriK)) 1921 | { 1922 | $this->renameBlankNode($subjects->$iriK, $c14n->next()); 1923 | $renamed[] = $iriK; 1924 | } 1925 | } 1926 | 1927 | // only keep non-canonically named bnodes 1928 | $tmp = $bnodes; 1929 | $bnodes = array(); 1930 | foreach($tmp as $i => $b) 1931 | { 1932 | $iriB = $b->{'@id'}; 1933 | if(!$c14n->inNamespace($iriB)) 1934 | { 1935 | // mark serializations related to the named bnodes as dirty 1936 | foreach($renamed as $r) 1937 | { 1938 | if($this->markSerializationDirty($iriB, $r, $dir)) 1939 | { 1940 | // resort if a serialization was marked dirty 1941 | $resort = true; 1942 | } 1943 | } 1944 | $bnodes[] = $b; 1945 | } 1946 | } 1947 | } 1948 | } 1949 | 1950 | // sort property lists that now have canonically-named bnodes 1951 | foreach($edges->props as $key => $value) 1952 | { 1953 | if(count($value->bnodes) > 0) 1954 | { 1955 | $bnode = $subjects->$key; 1956 | foreach($bnode as $p => $v) 1957 | { 1958 | if(strpos($p, '@') !== 0 and is_array($v)) 1959 | { 1960 | usort($v, '_compareObjects'); 1961 | $bnode->$p = $v; 1962 | } 1963 | } 1964 | } 1965 | } 1966 | } 1967 | 1968 | /** 1969 | * Marks a relation serialization as dirty if necessary. 1970 | * 1971 | * @param iri the IRI of the bnode to check. 1972 | * @param changed the old IRI of the bnode that changed. 1973 | * @param dir the direction to check ('props' or 'refs'). 1974 | * 1975 | * @return true if the serialization was marked dirty, false if not. 1976 | */ 1977 | public function markSerializationDirty($iri, $changed, $dir) 1978 | { 1979 | $rval = false; 1980 | $s = $this->serializations->$iri; 1981 | if($s->$dir !== null and property_exists($s->$dir->m, $changed)) 1982 | { 1983 | $s->$dir = null; 1984 | $rval = true; 1985 | } 1986 | return $rval; 1987 | } 1988 | 1989 | /** 1990 | * Serializes the properties of the given bnode for its relation 1991 | * serialization. 1992 | * 1993 | * @param b the blank node. 1994 | * 1995 | * @return the serialized properties. 1996 | */ 1997 | public function serializeProperties($b) 1998 | { 1999 | $rval = ''; 2000 | 2001 | $first = true; 2002 | foreach($b as $p => $o) 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 | $objs = is_array($o) ? $o : array($o); 2020 | foreach($objs as $obj) 2021 | { 2022 | if(is_object($obj)) 2023 | { 2024 | // ID (IRI) 2025 | if(property_exists($obj, '@id')) 2026 | { 2027 | if(_isBlankNodeIri($obj->{'@id'})) 2028 | { 2029 | $rval .= '_:'; 2030 | } 2031 | else 2032 | { 2033 | $rval .= '<' . $obj->{'@id'} . '>'; 2034 | } 2035 | } 2036 | // literal 2037 | else 2038 | { 2039 | $rval .= '"' . $obj->{'@value'} . '"'; 2040 | 2041 | // type literal 2042 | if(property_exists($obj, '@type')) 2043 | { 2044 | $rval .= '^^<' . $obj->{'@type'} . '>'; 2045 | } 2046 | // language literal 2047 | else if(property_exists($obj, '@language')) 2048 | { 2049 | $rval .= '@' . $obj->{'@language'}; 2050 | } 2051 | } 2052 | } 2053 | // plain literal 2054 | else 2055 | { 2056 | $rval .= '"' . $obj . '"'; 2057 | } 2058 | } 2059 | } 2060 | } 2061 | 2062 | return $rval; 2063 | } 2064 | 2065 | /** 2066 | * Recursively increments the relation serialization for a mapping. 2067 | * 2068 | * @param mb the mapping builder to update. 2069 | */ 2070 | public function serializeMapping($mb) 2071 | { 2072 | if(count($mb->keyStack) > 0) 2073 | { 2074 | // continue from top of key stack 2075 | $next = array_pop($mb->keyStack); 2076 | $len = count($next->keys); 2077 | for(; $next->idx < $len; ++$next->idx) 2078 | { 2079 | $k = $next->keys[$next->idx]; 2080 | if(!property_exists($mb->adj, $k)) 2081 | { 2082 | $mb->keyStack[] = $next; 2083 | break; 2084 | } 2085 | 2086 | if(property_exists($mb->done, $k)) 2087 | { 2088 | // mark cycle 2089 | $mb->s .= '_' . $k; 2090 | } 2091 | else 2092 | { 2093 | // mark key as serialized 2094 | $mb->done->$k = true; 2095 | 2096 | // serialize top-level key and its details 2097 | $s = $k; 2098 | $adj = $mb->adj->$k; 2099 | $iri = $adj->i; 2100 | if(property_exists($this->subjects, $iri)) 2101 | { 2102 | $b = $this->subjects->$iri; 2103 | 2104 | // serialize properties 2105 | $s .= '[' . $this->serializeProperties($b) . ']'; 2106 | 2107 | // serialize references 2108 | $s .= '['; 2109 | $first = true; 2110 | $refs = $this->edges->refs->$iri->all; 2111 | foreach($refs as $r) 2112 | { 2113 | if($first) 2114 | { 2115 | $first = false; 2116 | } 2117 | else 2118 | { 2119 | $s .= '|'; 2120 | } 2121 | $s .= '<' . $r->p . '>'; 2122 | $s .= _isBlankNodeIri($r->s) ? 2123 | '_:' : ('<' . $refs->s . '>'); 2124 | } 2125 | $s .= ']'; 2126 | } 2127 | 2128 | // serialize adjacent node keys 2129 | $s .= implode($adj->k); 2130 | $mb->s .= $s; 2131 | $entry = new stdClass(); 2132 | $entry->keys = $adj->k; 2133 | $entry->idx = 0; 2134 | $mb->keyStack[] = $entry; 2135 | $this->serializeMapping($mb); 2136 | } 2137 | } 2138 | } 2139 | } 2140 | 2141 | /** 2142 | * Recursively serializes adjacent bnode combinations. 2143 | * 2144 | * @param s the serialization to update. 2145 | * @param iri the IRI of the bnode being serialized. 2146 | * @param siri the serialization name for the bnode IRI. 2147 | * @param mb the MappingBuilder to use. 2148 | * @param dir the edge direction to use ('props' or 'refs'). 2149 | * @param mapped all of the already-mapped adjacent bnodes. 2150 | * @param notMapped all of the not-yet mapped adjacent bnodes. 2151 | */ 2152 | public function serializeCombos( 2153 | $s, $iri, $siri, $mb, $dir, $mapped, $notMapped) 2154 | { 2155 | // handle recursion 2156 | if(count($notMapped) > 0) 2157 | { 2158 | // copy mapped nodes 2159 | $mapped = _clone($mapped); 2160 | 2161 | // map first bnode in list 2162 | $mapped->{$mb->mapNode($notMapped[0]->s)} = $notMapped[0]->s; 2163 | 2164 | // recurse into remaining possible combinations 2165 | $original = $mb->copy(); 2166 | $notMapped = array_slice($notMapped, 1); 2167 | $rotations = max(1, count($notMapped)); 2168 | for($r = 0; $r < $rotations; ++$r) 2169 | { 2170 | $m = ($r === 0) ? $mb : $original->copy(); 2171 | $this->serializeCombos( 2172 | $s, $iri, $siri, $m, $dir, $mapped, $notMapped); 2173 | 2174 | // rotate not-mapped for next combination 2175 | _rotate($notMapped); 2176 | } 2177 | } 2178 | // no more adjacent bnodes to map, update serialization 2179 | else 2180 | { 2181 | $keys = array_keys((array)$mapped); 2182 | sort($keys); 2183 | $entry = new stdClass(); 2184 | $entry->i = $iri; 2185 | $entry->k = $keys; 2186 | $entry->m = $mapped; 2187 | $mb->adj->$siri = $entry; 2188 | $this->serializeMapping($mb); 2189 | 2190 | // optimize away mappings that are already too large 2191 | if($s->$dir === null or 2192 | _compareSerializations($mb->s, $s->$dir->s) <= 0) 2193 | { 2194 | // recurse into adjacent values 2195 | foreach($keys as $i => $k) 2196 | { 2197 | $this->serializeBlankNode($s, $mapped->$k, $mb, $dir); 2198 | } 2199 | 2200 | // update least serialization if new one has been found 2201 | $this->serializeMapping($mb); 2202 | if($s->$dir === null or 2203 | (_compareSerializations($mb->s, $s->$dir->s) <= 0 and 2204 | strlen($mb->s) >= strlen($s->$dir->s))) 2205 | { 2206 | $s->$dir = new stdClass(); 2207 | $s->$dir->s = $mb->s; 2208 | $s->$dir->m = $mb->mapping; 2209 | } 2210 | } 2211 | } 2212 | } 2213 | 2214 | /** 2215 | * Computes the relation serialization for the given blank node IRI. 2216 | * 2217 | * @param s the serialization to update. 2218 | * @param iri the current bnode IRI to be mapped. 2219 | * @param mb the MappingBuilder to use. 2220 | * @param dir the edge direction to use ('props' or 'refs'). 2221 | */ 2222 | public function serializeBlankNode($s, $iri, $mb, $dir) 2223 | { 2224 | // only do mapping if iri not already processed 2225 | if(!property_exists($mb->processed, $iri)) 2226 | { 2227 | // iri now processed 2228 | $mb->processed->$iri = true; 2229 | $siri = $mb->mapNode($iri); 2230 | 2231 | // copy original mapping builder 2232 | $original = $mb->copy(); 2233 | 2234 | // split adjacent bnodes on mapped and not-mapped 2235 | $adjs = $this->edges->$dir->$iri->bnodes; 2236 | $mapped = new stdClass(); 2237 | $notMapped = array(); 2238 | foreach($adjs as $adj) 2239 | { 2240 | if(property_exists($mb->mapping, $adj->s)) 2241 | { 2242 | $mapped->{$mb->mapping->{$adj->s}} = $adj->s; 2243 | } 2244 | else 2245 | { 2246 | $notMapped[] = $adj; 2247 | } 2248 | } 2249 | 2250 | // TODO: ensure this optimization does not alter canonical order 2251 | 2252 | // if the current bnode already has a serialization, reuse it 2253 | /*$hint = property_exists($this->serializations, $iri) ? 2254 | $this->serializations->$iri->$dir : null; 2255 | if($hint !== null) 2256 | { 2257 | $hm = $hint->m; 2258 | usort(notMapped, 2259 | { 2260 | return _compare(hm[a.s], hm[b.s]); 2261 | }); 2262 | for($i in notMapped) 2263 | { 2264 | mapped[mb.mapNode(notMapped[i].s)] = notMapped[i].s; 2265 | } 2266 | notMapped = array(); 2267 | }*/ 2268 | 2269 | // loop over possible combinations 2270 | $combos = max(1, count($notMapped)); 2271 | for($i = 0; $i < $combos; ++$i) 2272 | { 2273 | $m = ($i === 0) ? $mb : $original->copy(); 2274 | $this->serializeCombos( 2275 | $s, $iri, $siri, $mb, $dir, $mapped, $notMapped); 2276 | } 2277 | } 2278 | } 2279 | 2280 | /** 2281 | * Compares two blank nodes for equivalence. 2282 | * 2283 | * @param a the first blank node. 2284 | * @param b the second blank node. 2285 | * 2286 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2287 | */ 2288 | public function deepCompareBlankNodes($a, $b) 2289 | { 2290 | $rval = 0; 2291 | 2292 | // compare IRIs 2293 | $iriA = $a->{'@id'}; 2294 | $iriB = $b->{'@id'}; 2295 | if($iriA === $iriB) 2296 | { 2297 | $rval = 0; 2298 | } 2299 | else 2300 | { 2301 | // do shallow compare first 2302 | $rval = $this->shallowCompareBlankNodes($a, $b); 2303 | 2304 | // deep comparison is necessary 2305 | if($rval === 0) 2306 | { 2307 | // compare property edges and then reference edges 2308 | $dirs = array('props', 'refs'); 2309 | for($i = 0; $rval === 0 and $i < 2; ++$i) 2310 | { 2311 | // recompute 'a' and 'b' serializations as necessary 2312 | $dir = $dirs[$i]; 2313 | $sA = $this->serializations->$iriA; 2314 | $sB = $this->serializations->$iriB; 2315 | if($sA->$dir === null) 2316 | { 2317 | $mb = new MappingBuilder(); 2318 | if($dir === 'refs') 2319 | { 2320 | // keep same mapping and count from 'props' serialization 2321 | $mb->mapping = _clone($sA->props->m); 2322 | $mb->count = count(array_keys((array)$mb->mapping)) + 1; 2323 | } 2324 | $this->serializeBlankNode($sA, $iriA, $mb, $dir); 2325 | } 2326 | if($sB->$dir === null) 2327 | { 2328 | $mb = new MappingBuilder(); 2329 | if($dir === 'refs') 2330 | { 2331 | // keep same mapping and count from 'props' serialization 2332 | $mb->mapping = _clone($sB->props->m); 2333 | $mb->count = count(array_keys((array)$mb->mapping)) + 1; 2334 | } 2335 | $this->serializeBlankNode($sB, $iriB, $mb, $dir); 2336 | } 2337 | 2338 | // compare serializations 2339 | $rval = _compare($sA->$dir->s, $sB->$dir->s); 2340 | } 2341 | } 2342 | } 2343 | 2344 | return $rval; 2345 | } 2346 | 2347 | /** 2348 | * Performs a shallow sort comparison on the given bnodes. 2349 | * 2350 | * @param a the first bnode. 2351 | * @param b the second bnode. 2352 | * 2353 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2354 | */ 2355 | public function shallowCompareBlankNodes($a, $b) 2356 | { 2357 | $rval = 0; 2358 | 2359 | /* ShallowSort Algorithm (when comparing two bnodes): 2360 | 1. Compare the number of properties. 2361 | 1.1. The bnode with fewer properties is first. 2362 | 2. Compare alphabetically sorted-properties. 2363 | 2.1. The bnode with the alphabetically-first property is first. 2364 | 3. For each property, compare object values. 2365 | 4. Compare the number of references. 2366 | 4.1. The bnode with fewer references is first. 2367 | 5. Compare sorted references. 2368 | 5.1. The bnode with the reference iri (vs. bnode) is first. 2369 | 5.2. The bnode with the alphabetically-first reference iri is first. 2370 | 5.3. The bnode with the alphabetically-first reference property is 2371 | first. 2372 | */ 2373 | $pA = array_keys((array)$a); 2374 | $pB = array_keys((array)$b); 2375 | 2376 | // step #1 2377 | $rval = _compare(count($pA), count($pB)); 2378 | 2379 | // step #2 2380 | if($rval === 0) 2381 | { 2382 | sort($pA); 2383 | sort($pB); 2384 | $rval = _compare($pA, $pB); 2385 | } 2386 | 2387 | // step #3 2388 | if($rval === 0) 2389 | { 2390 | $rval = _compareBlankNodeObjects($a, $b); 2391 | } 2392 | 2393 | // step #4 2394 | if($rval === 0) 2395 | { 2396 | $edgesA = $this->edges->refs->{$a->{'@id'}}->all; 2397 | $edgesB = $this->edges->refs->{$b->{'@id'}}->all; 2398 | $edgesALen = count($edgesA); 2399 | $rval = _compare($edgesALen, count($edgesB)); 2400 | } 2401 | 2402 | // step #5 2403 | if($rval === 0) 2404 | { 2405 | for($i = 0; $i < $edgesALen and $rval === 0; ++$i) 2406 | { 2407 | $rval = $this->compareEdges($edgesA[$i], $edgesB[$i]); 2408 | } 2409 | } 2410 | 2411 | return $rval; 2412 | } 2413 | 2414 | /** 2415 | * Compares two edges. Edges with an IRI (vs. a bnode ID) come first, then 2416 | * alphabetically-first IRIs, then alphabetically-first properties. If a 2417 | * blank node has been canonically named, then blank nodes will be compared 2418 | * after properties (with a preference for canonically named over 2419 | * non-canonically named), otherwise they won't be. 2420 | * 2421 | * @param a the first edge. 2422 | * @param b the second edge. 2423 | * 2424 | * @return -1 if a < b, 0 if a == b, 1 if a > b. 2425 | */ 2426 | public function compareEdges($a, $b) 2427 | { 2428 | $rval = 0; 2429 | 2430 | $bnodeA = _isBlankNodeIri($a->s); 2431 | $bnodeB = _isBlankNodeIri($b->s); 2432 | $c14n = $this->ng->c14n; 2433 | 2434 | // if not both bnodes, one that is a bnode is greater 2435 | if($bnodeA != $bnodeB) 2436 | { 2437 | $rval = $bnodeA ? 1 : -1; 2438 | } 2439 | else 2440 | { 2441 | if(!$bnodeA) 2442 | { 2443 | $rval = _compare($a->s, $b->s); 2444 | } 2445 | if($rval === 0) 2446 | { 2447 | $rval = _compare($a->p, $b->p); 2448 | } 2449 | 2450 | // do bnode IRI comparison if canonical naming has begun 2451 | if($rval === 0 and $c14n !== null) 2452 | { 2453 | $c14nA = $c14n->inNamespace($a->s); 2454 | $c14nB = $c14n->inNamespace($b->s); 2455 | if($c14nA != $c14nB) 2456 | { 2457 | $rval = $c14nA ? 1 : -1; 2458 | } 2459 | else if($c14nA) 2460 | { 2461 | $rval = _compare($a->s, $b->s); 2462 | } 2463 | } 2464 | } 2465 | 2466 | return $rval; 2467 | } 2468 | 2469 | /** 2470 | * Populates the given reference map with all of the subject edges in the 2471 | * graph. The references will be categorized by the direction of the edges, 2472 | * where 'props' is for properties and 'refs' is for references to a subject 2473 | * as an object. The edge direction categories for each IRI will be sorted 2474 | * into groups 'all' and 'bnodes'. 2475 | */ 2476 | public function collectEdges() 2477 | { 2478 | $refs = $this->edges->refs; 2479 | $props = $this->edges->props; 2480 | 2481 | // collect all references and properties 2482 | foreach($this->subjects as $iri => $subject) 2483 | { 2484 | foreach($subject as $key => $object) 2485 | { 2486 | if($key !== '@id') 2487 | { 2488 | // normalize to array for single codepath 2489 | $tmp = !is_array($object) ? array($object) : $object; 2490 | foreach($tmp as $o) 2491 | { 2492 | if(is_object($o) and property_exists($o, '@id') and 2493 | property_exists($this->subjects, $o->{'@id'})) 2494 | { 2495 | $objIri = $o->{'@id'}; 2496 | 2497 | // map object to this subject 2498 | $e = new stdClass(); 2499 | $e->s = $iri; 2500 | $e->p = $key; 2501 | $refs->$objIri->all[] = $e; 2502 | 2503 | // map this subject to object 2504 | $e = new stdClass(); 2505 | $e->s = $objIri; 2506 | $e->p = $key; 2507 | $props->$iri->all[] = $e; 2508 | } 2509 | } 2510 | } 2511 | } 2512 | } 2513 | 2514 | // create sorted categories 2515 | foreach($refs as $iri => $ref) 2516 | { 2517 | usort($ref->all, array($this, 'compareEdges')); 2518 | $ref->bnodes = array_filter($ref->all, '_filterBlankNodeEdges'); 2519 | } 2520 | foreach($props as $iri => $prop) 2521 | { 2522 | usort($prop->all, array($this, 'compareEdges')); 2523 | $prop->bnodes = array_filter($prop->all, '_filterBlankNodeEdges'); 2524 | } 2525 | } 2526 | } 2527 | 2528 | /** 2529 | * Returns true if the given input is a subject and has one of the given types 2530 | * in the given frame. 2531 | * 2532 | * @param input the input. 2533 | * @param frame the frame with types to look for. 2534 | * 2535 | * @return true if the input has one of the given types. 2536 | */ 2537 | function _isType($input, $frame) 2538 | { 2539 | $rval = false; 2540 | 2541 | // check if type(s) are specified in frame and input 2542 | $type = '@type'; 2543 | if(property_exists($frame, $type) and 2544 | is_object($input) and 2545 | property_exists($input, $type)) 2546 | { 2547 | $tmp = is_array($input->$type) ? $input->$type : array($input->$type); 2548 | $types = is_array($frame->$type) ? $frame->$type : array($frame->$type); 2549 | $length = count($types); 2550 | for($t = 0; $t < $length and !$rval; ++$t) 2551 | { 2552 | $type = $types[$t]; 2553 | foreach($tmp as $e) 2554 | { 2555 | if($e === $type) 2556 | { 2557 | $rval = true; 2558 | break; 2559 | } 2560 | } 2561 | } 2562 | } 2563 | 2564 | return $rval; 2565 | } 2566 | 2567 | /** 2568 | * Filters non-keywords. 2569 | * 2570 | * @param e the element to check. 2571 | * 2572 | * @return true if the element is a non-keyword. 2573 | */ 2574 | function _filterNonKeyWords($e) 2575 | { 2576 | return strpos($e, '@') !== 0; 2577 | } 2578 | 2579 | /** 2580 | * Returns true if the given input matches the given frame via duck-typing. 2581 | * 2582 | * @param input the input. 2583 | * @param frame the frame to check against. 2584 | * 2585 | * @return true if the input matches the frame. 2586 | */ 2587 | function _isDuckType($input, $frame) 2588 | { 2589 | $rval = false; 2590 | 2591 | // frame must not have a specific type 2592 | if(!property_exists($frame, '@type')) 2593 | { 2594 | // get frame properties that must exist on input 2595 | $props = array_filter(array_keys((array)$frame), '_filterNonKeywords'); 2596 | if(count($props) === 0) 2597 | { 2598 | // input always matches if there are no properties 2599 | $rval = true; 2600 | } 2601 | // input must be a subject with all the given properties 2602 | else if(is_object($input) and property_exists($input, '@id')) 2603 | { 2604 | $rval = true; 2605 | foreach($props as $prop) 2606 | { 2607 | if(!property_exists($input, $prop)) 2608 | { 2609 | $rval = false; 2610 | break; 2611 | } 2612 | } 2613 | } 2614 | } 2615 | 2616 | return $rval; 2617 | } 2618 | 2619 | /** 2620 | * Recursively removes dependent dangling embeds. 2621 | * 2622 | * @param iri the iri of the parent to remove embeds for. 2623 | * @param embeds the embeds map. 2624 | */ 2625 | function removeDependentEmbeds($iri, $embeds) 2626 | { 2627 | $iris = get_object_vars($embeds); 2628 | foreach($iris as $i => $embed) 2629 | { 2630 | if($embed->parent !== null and 2631 | $embed->parent->{'@id'} === $iri) 2632 | { 2633 | unset($embeds->$i); 2634 | removeDependentEmbeds($i, $embeds); 2635 | } 2636 | } 2637 | } 2638 | 2639 | /** 2640 | * Subframes a value. 2641 | * 2642 | * @param subjects a map of subjects in the graph. 2643 | * @param value the value to subframe. 2644 | * @param frame the frame to use. 2645 | * @param embeds a map of previously embedded subjects, used to prevent cycles. 2646 | * @param autoembed true if auto-embed is on, false if not. 2647 | * @param parent the parent object. 2648 | * @param parentKey the parent object. 2649 | * @param options the framing options. 2650 | * 2651 | * @return the framed input. 2652 | */ 2653 | function _subframe( 2654 | $subjects, $value, $frame, $embeds, $autoembed, 2655 | $parent, $parentKey, $options) 2656 | { 2657 | // get existing embed entry 2658 | $iri = $value->{'@id'}; 2659 | $embed = property_exists($embeds, $iri) ? $embeds->{$iri} : null; 2660 | 2661 | // determine if value should be embedded or referenced, 2662 | // embed is ON if: 2663 | // 1. The frame OR default option specifies @embed as ON, AND 2664 | // 2. There is no existing embed OR it is an autoembed, AND 2665 | // autoembed mode is off. 2666 | $embedOn = ( 2667 | ((property_exists($frame, '@embed') and $frame->{'@embed'}) or 2668 | (!property_exists($frame, '@embed') and $options->defaults->embedOn)) and 2669 | ($embed === null or ($embed->autoembed and !$autoembed))); 2670 | 2671 | if(!$embedOn) 2672 | { 2673 | // not embedding, so only use subject IRI as reference 2674 | $tmp = new stdClass(); 2675 | $tmp->{'@id'} = $value->{'@id'}; 2676 | $value = $tmp; 2677 | } 2678 | else 2679 | { 2680 | // create new embed entry 2681 | if($embed === null) 2682 | { 2683 | $embed = new stdClass(); 2684 | $embeds->{$iri} = $embed; 2685 | } 2686 | // replace the existing embed with a reference 2687 | else if($embed->parent !== null) 2688 | { 2689 | if(is_array($embed->parent->{$embed->key})) 2690 | { 2691 | // find and replace embed in array 2692 | $arrLen = count($embed->parent->{$embed->key}); 2693 | for($i = 0; $i < $arrLen; ++$i) 2694 | { 2695 | $obj = $embed->parent->{$embed->key}[$i]; 2696 | if(is_object($obj) and property_exists($obj, '@id') and 2697 | $obj->{'@id'} === $iri) 2698 | { 2699 | $tmp = new stdClass(); 2700 | $tmp->{'@id'} = $value->{'@id'}; 2701 | $embed->parent->{$embed->key}[$i] = $tmp; 2702 | break; 2703 | } 2704 | } 2705 | } 2706 | else 2707 | { 2708 | $tmp = new stdClass(); 2709 | $tmp->{'@id'} = $value->{'@id'}; 2710 | $embed->parent->{$embed->key} = $tmp; 2711 | } 2712 | 2713 | // recursively remove any dependent dangling embeds 2714 | removeDependentEmbeds($iri, $embeds); 2715 | } 2716 | 2717 | // update embed entry 2718 | $embed->autoembed = $autoembed; 2719 | $embed->parent = $parent; 2720 | $embed->key = $parentKey; 2721 | 2722 | // check explicit flag 2723 | $explicitOn = property_exists($frame, '@explicit') ? 2724 | $frame->{'@explicit'} : $options->defaults->explicitOn; 2725 | if($explicitOn) 2726 | { 2727 | // remove keys from the value that aren't in the frame 2728 | foreach($value as $key => $v) 2729 | { 2730 | // do not remove @id or any frame key 2731 | if($key !== '@id' and !property_exists($frame, $key)) 2732 | { 2733 | unset($value->$key); 2734 | } 2735 | } 2736 | } 2737 | 2738 | // iterate over keys in value 2739 | $vars = get_object_vars($value); 2740 | foreach($vars as $key => $v) 2741 | { 2742 | // skip keywords 2743 | if(strpos($key, '@') !== 0) 2744 | { 2745 | // get the subframe if available 2746 | if(property_exists($frame, $key)) 2747 | { 2748 | $f = $frame->{$key}; 2749 | $_autoembed = false; 2750 | } 2751 | // use a catch-all subframe to preserve data from graph 2752 | else 2753 | { 2754 | $f = is_array($v) ? array() : new stdClass(); 2755 | $_autoembed = true; 2756 | } 2757 | 2758 | // build input and do recursion 2759 | $input = is_array($v) ? $v : array($v); 2760 | $length = count($input); 2761 | for($n = 0; $n < $length; ++$n) 2762 | { 2763 | // replace reference to subject w/embedded subject 2764 | if(is_object($input[$n]) and 2765 | property_exists($input[$n], '@id') and 2766 | property_exists($subjects, $input[$n]->{'@id'})) 2767 | { 2768 | $input[$n] = $subjects->{$input[$n]->{'@id'}}; 2769 | } 2770 | } 2771 | $value->$key = _frame( 2772 | $subjects, $input, $f, $embeds, $_autoembed, 2773 | $value, $key, $options); 2774 | } 2775 | } 2776 | 2777 | // iterate over frame keys to add any missing values 2778 | foreach($frame as $key => $f) 2779 | { 2780 | // skip keywords and non-null keys in value 2781 | if(strpos($key, '@') !== 0 and 2782 | (!property_exists($value, $key) || $value->{$key} === null)) 2783 | { 2784 | // add empty array to value 2785 | if(is_array($f)) 2786 | { 2787 | // add empty array/null property to value 2788 | $value->$key = array(); 2789 | } 2790 | // add default value to value 2791 | else 2792 | { 2793 | // use first subframe if frame is an array 2794 | if(is_array($f)) 2795 | { 2796 | $f = (count($f) > 0) ? $f[0] : new stdClass(); 2797 | } 2798 | 2799 | // determine if omit default is on 2800 | $omitOn = property_exists($f, '@omitDefault') ? 2801 | $f->{'@omitDefault'} : 2802 | $options->defaults->omitDefaultOn; 2803 | if(!$omitOn) 2804 | { 2805 | if(property_exists($f, '@default')) 2806 | { 2807 | // use specified default value 2808 | $value->{$key} = $f->{'@default'}; 2809 | } 2810 | else 2811 | { 2812 | // build-in default value is: null 2813 | $value->{$key} = null; 2814 | } 2815 | } 2816 | } 2817 | } 2818 | } 2819 | } 2820 | 2821 | return $value; 2822 | } 2823 | 2824 | /** 2825 | * Recursively frames the given input according to the given frame. 2826 | * 2827 | * @param subjects a map of subjects in the graph. 2828 | * @param input the input to frame. 2829 | * @param frame the frame to use. 2830 | * @param embeds a map of previously embedded subjects, used to prevent cycles. 2831 | * @param autoembed true if auto-embed is on, false if not. 2832 | * @param parent the parent object (for subframing), null for none. 2833 | * @param parentKey the parent key (for subframing), null for none. 2834 | * @param options the framing options. 2835 | * 2836 | * @return the framed input. 2837 | */ 2838 | function _frame( 2839 | $subjects, $input, $frame, $embeds, $autoembed, 2840 | $parent, $parentKey, $options) 2841 | { 2842 | $rval = null; 2843 | 2844 | // prepare output, set limit, get array of frames 2845 | $limit = -1; 2846 | if(is_array($frame)) 2847 | { 2848 | $rval = array(); 2849 | $frames = $frame; 2850 | if(count($frames) == 0) 2851 | { 2852 | $frames[] = new stdClass(); 2853 | } 2854 | } 2855 | else 2856 | { 2857 | $frames = array($frame); 2858 | $limit = 1; 2859 | } 2860 | 2861 | // iterate over frames adding input matches to list 2862 | $frameLen = count($frames); 2863 | $values = array(); 2864 | for($i = 0; $i < $frameLen and $limit !== 0; ++$i) 2865 | { 2866 | // get next frame 2867 | $frame = $frames[$i]; 2868 | if(!is_object($frame)) 2869 | { 2870 | throw new Exception( 2871 | 'Invalid JSON-LD frame. Frame type is not a map or array.'); 2872 | } 2873 | 2874 | // create array of values for each frame 2875 | $inLen = count($input); 2876 | $v = array(); 2877 | for($n = 0; $n < $inLen and $limit !== 0; ++$n) 2878 | { 2879 | // dereference input if it refers to a subject 2880 | $next = $input[$n]; 2881 | if(is_object($next) and property_exists($next, '@id') and 2882 | property_exists($subjects, $next->{'@id'})) 2883 | { 2884 | $next = $subjects->{$next->{'@id'}}; 2885 | } 2886 | 2887 | // add input to list if it matches frame specific type or duck-type 2888 | if(_isType($next, $frame) or _isDuckType($next, $frame)) 2889 | { 2890 | $v[] = $next; 2891 | --$limit; 2892 | } 2893 | } 2894 | $values[$i] = $v; 2895 | } 2896 | 2897 | // for each matching value, add it to the output 2898 | $vaLen = count($values); 2899 | for($i1 = 0; $i1 < $vaLen; ++$i1) 2900 | { 2901 | foreach($values[$i1] as $value) 2902 | { 2903 | $frame = $frames[$i1]; 2904 | 2905 | // if value is a subject, do subframing 2906 | if(_isSubject($value)) 2907 | { 2908 | $value = _subframe( 2909 | $subjects, $value, $frame, $embeds, $autoembed, 2910 | $parent, $parentKey, $options); 2911 | } 2912 | 2913 | // add value to output 2914 | if($rval === null) 2915 | { 2916 | $rval = $value; 2917 | } 2918 | else 2919 | { 2920 | // determine if value is a reference to an embed 2921 | $isRef = (_isReference($value) and 2922 | property_exists($embeds, $value->{'@id'})); 2923 | 2924 | // push any value that isn't a parentless reference 2925 | if(!($parent === null and $isRef)) 2926 | { 2927 | $rval[] = $value; 2928 | } 2929 | } 2930 | } 2931 | } 2932 | 2933 | return $rval; 2934 | } 2935 | 2936 | ?> 2937 | --------------------------------------------------------------------------------
15 | Welcome! This Read/Write Linked Data service is free (and open-source) for educational and personal use. 16 |