serialize.js (31860B)
1 /* Serialization of RDF Graphs 2 ** 3 ** Tim Berners-Lee 2006 4 ** This is or was http://dig.csail.mit.edu/2005/ajar/ajaw/js/rdf/serialize.js 5 ** 6 ** Bug: can't serialize http://data.semanticweb.org/person/abraham-bernstein/rdf 7 ** in XML (from mhausenblas) 8 */ 9 // @@@ Check the whole toStr thing tosee whetehr it still makes sense -- tbl 10 // 11 $rdf.Serializer = function () { 12 13 var __Serializer = function (store) { 14 this.flags = ""; 15 this.base = null; 16 this.prefixes = []; 17 this.keywords = ['a']; // The only one we generate at the moment 18 this.prefixchars = "abcdefghijklmnopqustuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 19 this.incoming = null; // Array not calculated yet 20 this.formulas = []; // remebering original formulae from hashes 21 this.store = store; 22 23 /* pass */ 24 } 25 26 var Serializer = function (store) { 27 return new __Serializer(store) 28 }; 29 30 __Serializer.prototype.setBase = function (base) { 31 this.base = base 32 }; 33 34 __Serializer.prototype.setFlags = function (flags) { 35 this.flags = flags ? flags : '' 36 }; 37 38 39 __Serializer.prototype.toStr = function (x) { 40 var s = x.toNT(); 41 if(x.termType == 'formula') { 42 this.formulas[s] = x; // remember as reverse does not work 43 } 44 return s; 45 }; 46 47 __Serializer.prototype.fromStr = function (s) { 48 if(s[0] == '{') { 49 var x = this.formulas[s]; 50 if(!x) alert('No formula object for ' + s) 51 return x; 52 } 53 return this.store.fromNT(s); 54 }; 55 56 57 58 59 60 /* Accumulate Namespaces 61 ** 62 ** These are only hints. If two overlap, only one gets used 63 ** There is therefore no guarantee in general. 64 */ 65 66 __Serializer.prototype.suggestPrefix = function (prefix, uri) { 67 this.prefixes[uri] = prefix; 68 } 69 70 // Takes a namespace -> prefix map 71 __Serializer.prototype.suggestNamespaces = function (namespaces) { 72 for(var px in namespaces) { 73 this.prefixes[namespaces[px]] = px; 74 } 75 } 76 77 // Make up an unused prefix for a random namespace 78 __Serializer.prototype.makeUpPrefix = function (uri) { 79 var p = uri; 80 var namespaces = []; 81 var pok; 82 var sz = this; 83 84 function canUse(pp) { 85 if(namespaces[pp]) return false; // already used 86 sz.prefixes[uri] = pp; 87 pok = pp; 88 return true 89 } 90 for(var ns in sz.prefixes) { 91 namespaces[sz.prefixes[ns]] = ns; // reverse index 92 } 93 // trim off illegal characters from the end 94 var i; 95 for(i = p.length - 1; i>=0; i--) { 96 if(sz._notNameChars.indexOf(p.charAt(i)) == -1) break; 97 } 98 p = p.substring(0, i+1); 99 if(p) { 100 // find shortest possible NCName to use as namespace name 101 for(i = p.length - 1; i>=0; i--) { 102 if(sz._notNameChars.indexOf(p.charAt(i)) != -1) break; 103 } 104 i++; 105 p = p.substr(i); 106 107 if(p.length < 6 && canUse(p)) return pok; // exact is best 108 if(canUse(p.slice(0, 3))) return pok; 109 if(canUse(p.slice(0, 2))) return pok; 110 if(canUse(p.slice(0, 4))) return pok; 111 if(canUse(p.slice(0, 1))) return pok; 112 if(canUse(p.slice(0, 5))) return pok; 113 p = p.slice(0, 3); 114 } else { 115 // no suitable characters (weird), fall back to 'ns' 116 p = 'ns'; 117 if(canUse(p)) return pok; 118 } 119 for(var i = 0;; i++) if(canUse(p + i)) return pok; 120 } 121 122 123 124 // Todo: 125 // - Sort the statements by subject, pred, object 126 // - do stuff about the docu first and then (or first) about its primary topic. 127 __Serializer.prototype.rootSubjects = function (sts) { 128 var incoming = {}; 129 var subjects = {}; 130 var sz = this; 131 var allBnodes = {}; 132 133 /* This scan is to find out which nodes will have to be the roots of trees 134 ** in the serialized form. This will be any symbols, and any bnodes 135 ** which hve more or less than one incoming arc, and any bnodes which have 136 ** one incoming arc but it is an uninterrupted loop of such nodes back to itself. 137 ** This should be kept linear time with repect to the number of statements. 138 ** Note it does not use any indexing of the store. 139 */ 140 141 142 tabulator.log.debug('serialize.js Find bnodes with only one incoming arc\n') 143 for(var i = 0; i < sts.length; i++) { 144 var st = sts[i]; 145 [st.subject, st.predicate, st.object].map(function (y) { 146 if(y.termType == 'bnode') { 147 allBnodes[y.toNT()] = true 148 } 149 }); 150 var x = sts[i].object; 151 if(!incoming[x]) incoming[x] = []; 152 incoming[x].push(st.subject) // List of things which will cause this to be printed 153 var ss = subjects[sz.toStr(st.subject)]; // Statements with this as subject 154 if(!ss) ss = []; 155 ss.push(st); 156 subjects[this.toStr(st.subject)] = ss; // Make hash. @@ too slow for formula? 157 //$rdf.log.debug(' sz potential subject: '+sts[i].subject) 158 } 159 160 var roots = []; 161 for(var xNT in subjects) { 162 var x = sz.fromStr(xNT); 163 if((x.termType != 'bnode') || !incoming[x] || (incoming[x].length != 1)) { 164 roots.push(x); 165 //$rdf.log.debug(' sz actual subject -: ' + x) 166 continue; 167 } 168 } 169 this.incoming = incoming; // Keep for serializing @@ Bug for nested formulas 170 //////////// New bit for CONNECTED bnode loops:frootshash 171 // This scans to see whether the serialization is gpoing to lead to a bnode loop 172 // and at the same time accumulates a list of all bnodes mentioned. 173 // This is in fact a cut down N3 serialization 174 /* 175 tabulator.log.debug('serialize.js Looking for connected bnode loops\n') 176 for (var i=0; i<sts.length; i++) { // @@TBL 177 // dump('\t'+sts[i]+'\n'); 178 } 179 var doneBnodesNT = {}; 180 function dummyPropertyTree(subject, subjects, rootsHash) { 181 // dump('dummyPropertyTree('+subject+'...)\n'); 182 var sts = subjects[sz.toStr(subject)]; // relevant statements 183 for (var i=0; i<sts.length; i++) { 184 dummyObjectTree(sts[i].object, subjects, rootsHash); 185 } 186 } 187 188 // Convert a set of statements into a nested tree of lists and strings 189 // @param force, "we know this is a root, do it anyway. It isn't a loop." 190 function dummyObjectTree(obj, subjects, rootsHash, force) { 191 // dump('dummyObjectTree('+obj+'...)\n'); 192 if (obj.termType == 'bnode' && (subjects[sz.toStr(obj)] && 193 (force || (rootsHash[obj.toNT()] == undefined )))) {// and there are statements 194 if (doneBnodesNT[obj.toNT()]) { // Ah-ha! a loop 195 throw "Serializer: Should be no loops "+obj; 196 } 197 doneBnodesNT[obj.toNT()] = true; 198 return dummyPropertyTree(obj, subjects, rootsHash); 199 } 200 return dummyTermToN3(obj, subjects, rootsHash); 201 } 202 203 // Scan for bnodes nested inside lists too 204 function dummyTermToN3(expr, subjects, rootsHash) { 205 if (expr.termType == 'bnode') doneBnodesNT[expr.toNT()] = true; 206 tabulator.log.debug('serialize: seen '+expr); 207 if (expr.termType == 'collection') { 208 for (i=0; i<expr.elements.length; i++) { 209 if (expr.elements[i].termType == 'bnode') 210 dummyObjectTree(expr.elements[i], subjects, rootsHash); 211 } 212 return; 213 } 214 } 215 216 // The tree for a subject 217 function dummySubjectTree(subject, subjects, rootsHash) { 218 // dump('dummySubjectTree('+subject+'...)\n'); 219 if (subject.termType == 'bnode' && !incoming[subject]) 220 return dummyObjectTree(subject, subjects, rootsHash, true); // Anonymous bnode subject 221 dummyTermToN3(subject, subjects, rootsHash); 222 dummyPropertyTree(subject, subjects, rootsHash); 223 } 224 */ 225 // Now do the scan using existing roots 226 tabulator.log.debug('serialize.js Dummy serialize to check for missing nodes') 227 var rootsHash = {}; 228 for(var i = 0; i < roots.length; i++) rootsHash[roots[i].toNT()] = true; 229 /* 230 for (var i=0; i<roots.length; i++) { 231 var root = roots[i]; 232 dummySubjectTree(root, subjects, rootsHash); 233 } 234 // dump('Looking for mising bnodes...\n') 235 236 // Now in new roots for anythig not acccounted for 237 // Now we check for any bndoes which have not been covered. 238 // Such bnodes must be in isolated rings of pure bnodes. 239 // They each have incoming link of 1. 240 241 tabulator.log.debug('serialize.js Looking for connected bnode loops\n') 242 for (;;) { 243 var bnt; 244 var found = null; 245 for (bnt in allBnodes) { // @@ Note: not repeatable. No canonicalisation 246 if (doneBnodesNT[bnt]) continue; 247 found = bnt; // Ah-ha! not covered 248 break; 249 } 250 if (found == null) break; // All done - no bnodes left out/ 251 // dump('Found isolated bnode:'+found+'\n'); 252 doneBnodesNT[bnt] = true; 253 var root = this.store.fromNT(found); 254 roots.push(root); // Add a new root 255 rootsHash[found] = true; 256 tabulator.log.debug('isolated bnode:'+found+', subjects[found]:'+subjects[found]+'\n'); 257 if (subjects[found] == undefined) { 258 for (var i=0; i<sts.length; i++) { 259 // dump('\t'+sts[i]+'\n'); 260 } 261 throw "Isolated node should be a subject" +found; 262 } 263 dummySubjectTree(root, subjects, rootsHash); // trace out the ring 264 } 265 // dump('Done bnode adjustments.\n') 266 */ 267 return { 268 'roots': roots, 269 'subjects': subjects, 270 'rootsHash': rootsHash, 271 'incoming': incoming 272 }; 273 } 274 275 //////////////////////////////////////////////////////// 276 __Serializer.prototype.toN3 = function (f) { 277 return this.statementsToN3(f.statements); 278 } 279 280 __Serializer.prototype._notQNameChars = "\t\r\n !\"#$%&'()*,+/;<=>?@[\\]^`{|}~"; 281 __Serializer.prototype._notNameChars = (__Serializer.prototype._notQNameChars + ":"); 282 __Serializer.prototype._NCNameRegExp = (function() { 283 // escape characters that are unsafe inside RegExp character set 284 var reSafeChars = __Serializer.prototype._notNameChars.replace(/[-\]\\]/g, '\\$&'); 285 return new RegExp('[^0-9\\-.' + reSafeChars + '][^' + reSafeChars + ']*$'); 286 })(); 287 288 289 __Serializer.prototype.statementsToN3 = function (sts) { 290 var indent = 4; 291 var width = 80; 292 var sz = this; 293 294 var namespaceCounts = []; // which have been used 295 var predMap = { 296 'http://www.w3.org/2002/07/owl#sameAs': '=', 297 'http://www.w3.org/2000/10/swap/log#implies': '=>', 298 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': 'a' 299 } 300 301 302 303 304 ////////////////////////// Arrange the bits of text 305 var spaces = function (n) { 306 var s = ''; 307 for(var i = 0; i < n; i++) s += ' '; 308 return s 309 } 310 311 var treeToLine = function (tree) { 312 var str = ''; 313 for(var i = 0; i < tree.length; i++) { 314 var branch = tree[i]; 315 var s2 = (typeof branch == 'string') ? branch : treeToLine(branch); 316 if(i != 0 && s2 != ',' && s2 != ';' && s2 != '.') str += ' '; 317 str += s2; 318 } 319 return str; 320 } 321 322 // Convert a nested tree of lists and strings to a string 323 var treeToString = function (tree, level) { 324 var str = ''; 325 var lastLength = 100000; 326 if(!level) level = 0; 327 for(var i = 0; i < tree.length; i++) { 328 var branch = tree[i]; 329 if(typeof branch != 'string') { 330 var substr = treeToString(branch, level + 1); 331 if(substr.length < 10 * (width - indent * level) 332 && substr.indexOf('"""') < 0) { 333 // Don't mess up multiline strings 334 var line = treeToLine(branch); 335 if(line.length < (width - indent * level)) { 336 branch = ' ' + line; // @@ Hack: treat as string below 337 substr = '' 338 } 339 } 340 if(substr) lastLength = 10000; 341 str += substr; 342 } 343 if(typeof branch == 'string') { 344 if(branch.length == '1' && str.slice(-1) == '\n') { 345 if(",.;".indexOf(branch) >= 0) { 346 str = str.slice(0, -1) + branch + '\n'; // slip punct'n on end 347 lastLength += 1; 348 continue; 349 } else if("])}".indexOf(branch) >= 0) { 350 str = str.slice(0, -1) + ' ' + branch + '\n'; 351 lastLength += 2; 352 continue; 353 } 354 } 355 if(lastLength < (indent * level + 4)) { // continue 356 str = str.slice(0, -1) + ' ' + branch + '\n'; 357 lastLength += branch.length + 1; 358 } else { 359 var line = spaces(indent * level) + branch; 360 str += line + '\n'; 361 lastLength = line.length; 362 } 363 364 } else { // not string 365 } 366 } 367 return str; 368 }; 369 370 ////////////////////////////////////////////// Structure for N3 371 372 // Convert a set of statements into a nested tree of lists and strings 373 function statementListToTree(statements) { 374 // print('Statement tree for '+statements.length); 375 var res = []; 376 var stats = sz.rootSubjects(statements); 377 var roots = stats.roots; 378 var results = [] 379 for(var i = 0; i < roots.length; i++) { 380 var root = roots[i]; 381 results.push(subjectTree(root, stats)) 382 } 383 return results; 384 } 385 386 // The tree for a subject 387 function subjectTree(subject, stats) { 388 if(subject.termType == 'bnode' && !stats.incoming[subject]) 389 return objectTree(subject, stats, true).concat(["."]); // Anonymous bnode subject 390 return [termToN3(subject, stats)].concat([propertyTree(subject, stats)]).concat(["."]); 391 } 392 393 394 // The property tree for a single subject or anonymous node 395 function propertyTree(subject, stats) { 396 // print('Proprty tree for '+subject); 397 var results = [] 398 var lastPred = null; 399 var sts = stats.subjects[sz.toStr(subject)]; // relevant statements 400 if(typeof sts == 'undefined') { 401 throw('Cant find statements for ' + subject); 402 } 403 sts.sort(); 404 var objects = []; 405 for(var i = 0; i < sts.length; i++) { 406 var st = sts[i]; 407 if(st.predicate.uri == lastPred) { 408 objects.push(','); 409 } else { 410 if(lastPred) { 411 results = results.concat([objects]).concat([';']); 412 objects = []; 413 } 414 results.push(predMap[st.predicate.uri] ? 415 predMap[st.predicate.uri] : 416 termToN3(st.predicate, stats)); 417 } 418 lastPred = st.predicate.uri; 419 objects.push(objectTree(st.object, stats)); 420 } 421 results = results.concat([objects]); 422 return results; 423 } 424 425 function objectTree(obj, stats, force) { 426 if(obj.termType == 'bnode' 427 && stats.subjects[sz.toStr(obj)] 428 // and there are statements 429 && (force || stats.rootsHash[obj.toNT()] == undefined)) // and not a root 430 return ['['].concat(propertyTree(obj, stats)).concat([']']); 431 return termToN3(obj, stats); 432 } 433 434 function termToN3(expr, stats) { 435 switch(expr.termType) { 436 case 'bnode': 437 case 'variable': 438 return expr.toNT(); 439 case 'literal': 440 var str = stringToN3(expr.value); 441 if(expr.lang) str += '@' + expr.lang; 442 if(expr.datatype) str += '^^' + termToN3(expr.datatype, stats); 443 return str; 444 case 'symbol': 445 return symbolToN3(expr.uri); 446 case 'formula': 447 var res = ['{']; 448 res = res.concat(statementListToTree(expr.statements)); 449 return res.concat(['}']); 450 case 'collection': 451 var res = ['(']; 452 for(i = 0; i < expr.elements.length; i++) { 453 res.push([objectTree(expr.elements[i], stats)]); 454 } 455 res.push(')'); 456 return res; 457 458 default: 459 throw "Internal: termToN3 cannot handle " + expr + " of termType+" + expr.termType 460 return '' + expr; 461 } 462 } 463 464 ////////////////////////////////////////////// Atomic Terms 465 // Deal with term level things and nesting with no bnode structure 466 function symbolToN3(uri) { // c.f. symbolString() in notation3.py 467 var j = uri.indexOf('#'); 468 if(j < 0 && sz.flags.indexOf('/') < 0) { 469 j = uri.lastIndexOf('/'); 470 } 471 if(j >= 0 && sz.flags.indexOf('p') < 0) { // Can split at namespace 472 var canSplit = true; 473 for(var k = j + 1; k < uri.length; k++) { 474 if(__Serializer.prototype._notNameChars.indexOf(uri[k]) >= 0) { 475 canSplit = false; 476 break; 477 } 478 } 479 if(canSplit) { 480 var localid = uri.slice(j + 1); 481 var namesp = uri.slice(0, j + 1); 482 if(sz.defaultNamespace 483 && sz.defaultNamespace == namesp 484 && sz.flags.indexOf('d') < 0) { // d -> suppress default 485 if(sz.flags.indexOf('k') >= 0 486 && sz.keyords.indexOf(localid) < 0) 487 return localid; 488 return ':' + localid; 489 } 490 var prefix = sz.prefixes[namesp]; 491 if(prefix) { 492 namespaceCounts[namesp] = true; 493 return prefix + ':' + localid; 494 } 495 if(uri.slice(0, j) == sz.base) 496 return '<#' + localid + '>'; 497 // Fall though if can't do qname 498 } 499 } 500 if(sz.flags.indexOf('r') < 0 && sz.base) 501 uri = $rdf.Util.uri.refTo(sz.base, uri); 502 else if(sz.flags.indexOf('u') >= 0) 503 uri = backslashUify(uri); 504 else uri = hexify(uri); 505 return '<' + uri + '>'; 506 } 507 508 function prefixDirectives() { 509 var str = ''; 510 if(sz.defaultNamespace) 511 str += '@prefix : <' + sz.defaultNamespace + '>.\n'; 512 for(var ns in namespaceCounts) { 513 str += '@prefix ' + sz.prefixes[ns] + ': <' + ns + '>.\n'; 514 } 515 return str + '\n'; 516 } 517 518 // stringToN3: String escaping for N3 519 // 520 var forbidden1 = new RegExp(/[\\"\b\f\r\v\t\n\u0080-\uffff]/gm); 521 var forbidden3 = new RegExp(/[\\"\b\f\r\v\u0080-\uffff]/gm); 522 523 function stringToN3(str, flags) { 524 if(!flags) flags = "e"; 525 var res = '', i = 0, j = 0; 526 var delim; 527 var forbidden; 528 if(str.length > 20 // Long enough to make sense 529 && str.slice(-1) != '"' // corner case' 530 && flags.indexOf('n') < 0 // Force single line 531 && (str.indexOf('\n') > 0 || str.indexOf('"') > 0)) { 532 delim = '"""'; 533 forbidden = forbidden3; 534 } else { 535 delim = '"'; 536 forbidden = forbidden1; 537 } 538 for(i = 0; i < str.length;) { 539 forbidden.lastIndex = 0; 540 var m = forbidden.exec(str.slice(i)); 541 if(m == null) break; 542 j = i + forbidden.lastIndex - 1; 543 res += str.slice(i, j); 544 var ch = str[j]; 545 if(ch == '"' && delim == '"""' && str.slice(j, j + 3) != '"""') { 546 res += ch; 547 } else { 548 var k = '\b\f\r\t\v\n\\"'.indexOf(ch); // No escaping of bell (7)? 549 if(k >= 0) { 550 res += "\\" + 'bfrtvn\\"' [k]; 551 } else { 552 if(flags.indexOf('e') >= 0) { 553 res += '\\u' + ('000' + ch.charCodeAt(0).toString(16).toLowerCase()).slice(-4) 554 } else { // no 'e' flag 555 res += ch; 556 } 557 } 558 } 559 i = j + 1; 560 } 561 return delim + res + str.slice(i) + delim 562 } 563 564 // Body of toN3: 565 var tree = statementListToTree(sts); 566 return prefixDirectives() + treeToString(tree, -1); 567 568 } 569 570 // String ecaping utilities 571 function hexify(str) { // also used in parser 572 // var res = ''; 573 // for (var i=0; i<str.length; i++) { 574 // k = str.charCodeAt(i); 575 // if (k>126 || k<33) 576 // res += '%' + ('0'+n.toString(16)).slice(-2); // convert to upper? 577 // else 578 // res += str[i]; 579 // } 580 // return res; 581 return encodeURI(str); 582 } 583 584 585 function backslashUify(str) { 586 var res = '', k; 587 for(var i = 0; i < str.length; i++) { 588 k = str.charCodeAt(i); 589 if(k > 65535) 590 res += '\\U' + ('00000000' + n.toString(16)).slice(-8); // convert to upper? 591 else if(k > 126) 592 res += '\\u' + ('0000' + n.toString(16)).slice(-4); 593 else 594 res += str[i]; 595 } 596 return res; 597 } 598 599 600 601 602 603 604 //////////////////////////////////////////////// XML serialization 605 __Serializer.prototype.statementsToXML = function (sts) { 606 var indent = 4; 607 var width = 80; 608 var sz = this; 609 610 var namespaceCounts = []; // which have been used 611 namespaceCounts['http://www.w3.org/1999/02/22-rdf-syntax-ns#'] = true; 612 613 ////////////////////////// Arrange the bits of XML text 614 var spaces = function (n) { 615 var s = ''; 616 for(var i = 0; i < n; i++) s += ' '; 617 return s 618 } 619 620 var XMLtreeToLine = function (tree) { 621 var str = ''; 622 for(var i = 0; i < tree.length; i++) { 623 var branch = tree[i]; 624 var s2 = (typeof branch == 'string') ? branch : XMLtreeToLine(branch); 625 str += s2; 626 } 627 return str; 628 } 629 630 // Convert a nested tree of lists and strings to a string 631 var XMLtreeToString = function (tree, level) { 632 var str = ''; 633 var lastLength = 100000; 634 if(!level) level = 0; 635 for(var i = 0; i < tree.length; i++) { 636 var branch = tree[i]; 637 if(typeof branch != 'string') { 638 var substr = XMLtreeToString(branch, level + 1); 639 if(substr.length < 10 * (width - indent * level) 640 && substr.indexOf('"""') < 0) { 641 // Don't mess up multiline strings 642 var line = XMLtreeToLine(branch); 643 if(line.length < (width - indent * level)) { 644 branch = ' ' + line; // @@ Hack: treat as string below 645 substr = '' 646 } 647 } 648 if(substr) lastLength = 10000; 649 str += substr; 650 } 651 if(typeof branch == 'string') { 652 if(lastLength < (indent * level + 4)) { // continue 653 str = str.slice(0, -1) + ' ' + branch + '\n'; 654 lastLength += branch.length + 1; 655 } else { 656 var line = spaces(indent * level) + branch; 657 str += line + '\n'; 658 lastLength = line.length; 659 } 660 661 } else { // not string 662 } 663 } 664 return str; 665 }; 666 667 function statementListToXMLTree(statements) { 668 sz.suggestPrefix('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); 669 var stats = sz.rootSubjects(statements); 670 var roots = stats.roots; 671 var results = [], root; 672 for(var i = 0; i < roots.length; i++) { 673 root = roots[i]; 674 results.push(subjectXMLTree(root, stats)) 675 } 676 return results; 677 } 678 679 function escapeForXML(str) { 680 if(typeof str == 'undefined') return '@@@undefined@@@@'; 681 return str.replace(/&/g, '&') 682 .replace(/</g, '<') 683 .replace(/>/g, '>') 684 .replace(/"/g, '"'); 685 } 686 687 function relURI(term) { 688 return escapeForXML((sz.base) ? $rdf.Util.uri.refTo(this.base, term.uri) : term.uri); 689 } 690 691 // The tree for a subject 692 function subjectXMLTree(subject, stats) { 693 const liPrefix = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#_'; 694 var results = []; 695 var type = null, t, st; 696 var sts = stats.subjects[sz.toStr(subject)]; // relevant statements 697 // Sort only on the predicate, leave the order at object 698 // level undisturbed. This leaves multilingual content in 699 // the order of entry (for partner literals), which helps 700 // readability. 701 // 702 // For the predicate sort, we attempt to split the uri 703 // as a hint to the sequence, as sequenced items seems 704 // to be of the form http://example.com#_1, http://example.com#_2, 705 // et cetera. Probably not the most optimal of fixes, but 706 // it does work. 707 sts.sort(function (a, b) { 708 var aa = a.predicate.uri.split('#_'); 709 var bb = a.predicate.uri.split('#_'); 710 if(aa[0] > bb[0]) { 711 return 1; 712 } else if(aa[0] < bb[0]) { 713 return -1; 714 } else if("undefined" !== typeof aa[1] && "undefined" !== typeof bb[1]) { 715 if(parseInt(aa[1], 10) > parseInt(bb[1], 10)) { 716 return 1; 717 } else if(parseInt(aa[1], 10) < parseInt(bb[1], 10)) { 718 return -1; 719 } 720 } 721 return 0; 722 }); 723 724 for(var i = 0; i < sts.length; i++) { 725 st = sts[i]; 726 if(st.predicate.uri == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' && !type && st.object.termType == "symbol") { 727 // look for a type 728 type = st.object; 729 } else { 730 // see whether predicate can be replaced with "li" 731 if(st.predicate.uri.substr(0, liPrefix.length) == liPrefix) { 732 var number = st.predicate.uri.substr(liPrefix.length); 733 // make sure these are actually numeric list items 734 var intNumber = parseInt(number); 735 if(number == intNumber.toString()) { 736 // was numeric; don't need to worry about ordering since we've already 737 // sorted the statements 738 st.predicate = $rdf.Symbol('http://www.w3.org/1999/02/22-rdf-syntax-ns#li'); 739 } 740 } 741 t = qname(st.predicate); 742 switch(st.object.termType) { 743 case 'bnode': 744 if(sz.incoming[st.object].length == 1) { 745 results = results.concat(['<' + t + '>', 746 subjectXMLTree(st.object, stats), 747 '</' + t + '>']); 748 } else { 749 results = results.concat(['<' + t + ' rdf:nodeID="' 750 + st.object.toNT().slice(2) + '"/>']); 751 } 752 break; 753 case 'symbol': 754 results = results.concat(['<' + t + ' rdf:resource="' 755 + relURI(st.object) + '"/>']); 756 break; 757 case 'literal': 758 results = results.concat(['<' + t 759 + (st.object.dt ? ' rdf:datatype="' + escapeForXML(st.object.dt.uri) + '"' : '') 760 + (st.object.lang ? ' xml:lang="' + st.object.lang + '"' : '') 761 + '>' + escapeForXML(st.object.value) 762 + '</' + t + '>']); 763 break; 764 case 'collection': 765 results = results.concat(['<' + t + ' rdf:parseType="Collection">', 766 collectionXMLTree(st.object, stats), 767 '</' + t + '>']); 768 break; 769 default: 770 throw "Can't serialize object of type " + st.object.termType + " into XML"; 771 } // switch 772 } 773 } 774 775 var tag = type ? qname(type) : 'rdf:Description'; 776 777 var attrs = ''; 778 if(subject.termType == 'bnode') { 779 if(!sz.incoming[subject] || sz.incoming[subject].length != 1) { // not an anonymous bnode 780 attrs = ' rdf:nodeID="' + subject.toNT().slice(2) + '"'; 781 } 782 } else { 783 attrs = ' rdf:about="' + relURI(subject) + '"'; 784 } 785 786 return ['<' + tag + attrs + '>'].concat([results]).concat(["</" + tag + ">"]); 787 } 788 789 function collectionXMLTree(subject, stats) { 790 var res = [] 791 for(var i = 0; i < subject.elements.length; i++) { 792 res.push(subjectXMLTree(subject.elements[i], stats)); 793 } 794 return res; 795 } 796 797 // The property tree for a single subject or anonymos node 798 function propertyXMLTree(subject, stats) { 799 var results = [] 800 var sts = stats.subjects[sz.toStr(subject)]; // relevant statements 801 if(sts == undefined) return results; // No relevant statements 802 sts.sort(); 803 for(var i = 0; i < sts.length; i++) { 804 var st = sts[i]; 805 switch(st.object.termType) { 806 case 'bnode': 807 if(stats.rootsHash[st.object.toNT()]) { // This bnode has been done as a root -- no content here @@ what bout first time 808 results = results.concat(['<' + qname(st.predicate) + ' rdf:nodeID="' + st.object.toNT().slice(2) + '">', 809 '</' + qname(st.predicate) + '>']); 810 } else { 811 results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Resource">', 812 propertyXMLTree(st.object, stats), 813 '</' + qname(st.predicate) + '>']); 814 } 815 break; 816 case 'symbol': 817 results = results.concat(['<' + qname(st.predicate) + ' rdf:resource="' 818 + relURI(st.object) + '"/>']); 819 break; 820 case 'literal': 821 results = results.concat(['<' + qname(st.predicate) 822 + (st.object.datatype ? ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"' : '') 823 + (st.object.lang ? ' xml:lang="' + st.object.lang + '"' : '') 824 + '>' + escapeForXML(st.object.value) 825 + '</' + qname(st.predicate) + '>']); 826 break; 827 case 'collection': 828 results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Collection">', 829 collectionXMLTree(st.object, stats), 830 '</' + qname(st.predicate) + '>']); 831 break; 832 default: 833 throw "Can't serialize object of type " + st.object.termType + " into XML"; 834 835 } // switch 836 } 837 return results; 838 } 839 840 function qname(term) { 841 var uri = term.uri; 842 843 var j = uri.search(sz._NCNameRegExp); 844 if(j < 0) throw("Cannot make qname out of <" + uri + ">") 845 846 var localid = uri.substr(j); 847 var namesp = uri.substr(0, j); 848 if(sz.defaultNamespace 849 && sz.defaultNamespace == namesp 850 && sz.flags.indexOf('d') < 0) { // d -> suppress default 851 return localid; 852 } 853 var prefix = sz.prefixes[namesp]; 854 if(!prefix) prefix = sz.makeUpPrefix(namesp); 855 namespaceCounts[namesp] = true; 856 return prefix + ':' + localid; 857 // throw ('No prefix for namespace "'+namesp +'" for XML qname for '+uri+', namespaces: '+sz.prefixes+' sz='+sz); 858 } 859 860 // Body of toXML: 861 var tree = statementListToXMLTree(sts); 862 var str = '<rdf:RDF'; 863 if(sz.defaultNamespace) 864 str += ' xmlns="' + escapeForXML(sz.defaultNamespace) + '"'; 865 for(var ns in namespaceCounts) { 866 str += '\n xmlns:' + sz.prefixes[ns] + '="' + escapeForXML(ns) + '"'; 867 } 868 str += '>'; 869 870 var tree2 = [str, tree, '</rdf:RDF>']; //@@ namespace declrations 871 return XMLtreeToString(tree2, -1); 872 873 874 } // End @@ body 875 return Serializer; 876 877 }();