rdflib 2.3.0 → 2.3.1-0007ffed

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/n3parser.js CHANGED
@@ -212,10 +212,20 @@ var signed_integer = new RegExp("^[-+]?[0-9]+", 'g');
212
212
  var number_syntax = new RegExp("^([-+]?[0-9]+)(\\.[0-9]+)?([eE][-+]?[0-9]+)?", 'g');
213
213
  var datetime_syntax = new RegExp('^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9](T[0-9][0-9]:[0-9][0-9](:[0-9][0-9](\\.[0-9]*)?)?)?Z?');
214
214
 
215
+ // Reused in tight loops to detect whitespace or comment after a dot
216
+ var wsOrHash = new RegExp("[\\s#]");
217
+
215
218
  var digitstring = new RegExp("^[0-9]+", 'g');
216
219
  var interesting = new RegExp("[\\\\\\r\\n\\\"]", 'g');
217
220
  var langcode = new RegExp("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*", 'g');
218
221
 
222
+ // Returns true when a dot at position i should terminate a name,
223
+ // i.e., when the next character is whitespace, a comment start, or EOF
224
+ function dotTerminatesName(str, i) {
225
+ var next = str.charAt(i + 1);
226
+ return next === '' || wsOrHash.test(next);
227
+ }
228
+
219
229
  function createSinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why) {
220
230
  return new SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why);
221
231
  }
@@ -554,7 +564,7 @@ export class SinkParser {
554
564
  <- prop -<
555
565
  _operator_*/
556
566
 
557
- var j = this.skipSpace(str, i);
567
+ var j = this.skipSpace(str, i);
558
568
  if ((j < 0)) {
559
569
  return j;
560
570
  }
@@ -703,22 +713,17 @@ export class SinkParser {
703
713
  if ((j >= 0)) {
704
714
  var subj = objs[0];
705
715
  if ((pyjslib_len(objs) > 1)) {
706
-
707
716
  var __obj = new pyjslib_Iterator(objs);
708
717
  try {
709
718
  while (true) {
710
719
  var obj = __obj.next();
711
-
712
-
713
720
  this.makeStatement(new pyjslib_Tuple([this._context, this._store.sym(DAML_sameAs_URI), subj, obj]));
714
-
715
721
  }
716
722
  } catch (e) {
717
723
  if (e != StopIteration) {
718
724
  throw e;
719
725
  }
720
726
  }
721
-
722
727
  }
723
728
  var j = this.skipSpace(str, j);
724
729
  if ((j < 0)) {
@@ -743,6 +748,16 @@ export class SinkParser {
743
748
  if ((j < 0)) {
744
749
  throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF when ']' expected after [ <propertyList>");
745
750
  }
751
+ if ((str.slice( j, ( j + 1 ) ) == ".")) {
752
+ // If a dot is found after a blank node, treat it as a statement terminator.
753
+ // Do NOT consume the '.' here: statement terminators are handled centrally by
754
+ // checkDot() (called by directiveOrStatement after statement()). Consuming the dot
755
+ // locally would bypass that unified logic and could cause inconsistencies.
756
+ // We do consume ']' below because it is a structural closer of the blank node,
757
+ // not a statement terminator.
758
+ res.push(subj);
759
+ return j; // leave '.' for checkDot()
760
+ }
746
761
  if ((str.slice( j, ( j + 1 ) ) != "]")) {
747
762
  throw BadSyntax(this._thisDoc, this.lines, str, j, "']' expected");
748
763
  }
@@ -1208,7 +1223,17 @@ export class SinkParser {
1208
1223
  return -1;
1209
1224
  }
1210
1225
  var i = j;
1211
- while ((i < pyjslib_len(str)) && (_notNameChars.indexOf(str.charAt(i)) < 0)) {
1226
+ while ((i < pyjslib_len(str))) {
1227
+ var c = str.charAt(i);
1228
+ if (c === '.') {
1229
+ if (dotTerminatesName(str, i)) {
1230
+ break; // treat as statement terminator, not part of name
1231
+ }
1232
+ // else: accept '.' as part of name
1233
+ } else if (_notNameChars.indexOf(c) >= 0) {
1234
+ // Other invalid characters terminate the name
1235
+ break;
1236
+ }
1212
1237
  var i = ( i + 1 ) ;
1213
1238
  }
1214
1239
  res.push(str.slice( j, i));
@@ -1235,13 +1260,15 @@ export class SinkParser {
1235
1260
  var i = ( i + 1 ) ;
1236
1261
  while ((i < pyjslib_len(str))) {
1237
1262
  var c = str.charAt(i);
1238
- if ((_notNameChars.indexOf(c) < 0)) {
1239
- var ln = ( ln + c ) ;
1240
- var i = ( i + 1 ) ;
1241
- }
1242
- else {
1263
+ if (c === '.') {
1264
+ if (dotTerminatesName(str, i)) {
1265
+ break; // dot ends the name here
1266
+ }
1267
+ } else if (_notNameChars.indexOf(c) >= 0) {
1243
1268
  break;
1244
1269
  }
1270
+ var ln = ( ln + c ) ;
1271
+ var i = ( i + 1 ) ;
1245
1272
  }
1246
1273
  }
1247
1274
  else {
@@ -1253,13 +1280,15 @@ export class SinkParser {
1253
1280
  var ln = "";
1254
1281
  while ((i < pyjslib_len(str))) {
1255
1282
  var c = str.charAt(i);
1256
- if ((_notNameChars.indexOf(c) < 0)) {
1257
- var ln = ( ln + c ) ;
1258
- var i = ( i + 1 ) ;
1259
- }
1260
- else {
1283
+ if (c === '.') {
1284
+ if (dotTerminatesName(str, i)) {
1285
+ break; // dot ends the name here
1286
+ }
1287
+ } else if (_notNameChars.indexOf(c) >= 0) {
1261
1288
  break;
1262
1289
  }
1290
+ var ln = ( ln + c ) ;
1291
+ var i = ( i + 1 ) ;
1263
1292
  }
1264
1293
  res.push(new pyjslib_Tuple([pfx, ln]));
1265
1294
  return i;
package/src/serialize.ts CHANGED
@@ -72,7 +72,8 @@ export default function serialize (
72
72
  return executeCallback(null, documentString)
73
73
  case TurtleContentType:
74
74
  case TurtleLegacyContentType:
75
- sz.setFlags('si') // Suppress = for sameAs and => for implies
75
+ // Suppress = for sameAs and => for implies; preserve any user-specified flags (e.g., 'o')
76
+ sz.setFlags('si' + (opts.flags ? (' ' + opts.flags) : ''))
76
77
  documentString = sz.statementsToN3(newSts)
77
78
  return executeCallback(null, documentString)
78
79
  case NTriplesContentType:
@@ -80,7 +81,8 @@ export default function serialize (
80
81
  documentString = sz.statementsToNTriples(newSts)
81
82
  return executeCallback(null, documentString)
82
83
  case JSONLDContentType:
83
- sz.setFlags('si dr') // turtle + dr (means no default, no relative prefix)
84
+ // turtle + dr (means no default, no relative prefix); preserve user flags
85
+ sz.setFlags('si dr' + (opts.flags ? (' ' + opts.flags) : ''))
84
86
  documentString = sz.statementsToJsonld(newSts) // convert via turtle
85
87
  return executeCallback(null, documentString)
86
88
  case NQuadsContentType:
package/src/serializer.js CHANGED
@@ -5,14 +5,12 @@
5
5
  ** This is or was https://github.com/linkeddata/rdflib.js/blob/main/src/serializer.js
6
6
  ** Licence: MIT
7
7
  */
8
- import NamedNode from './named-node'
9
- import BlankNode from './blank-node'
8
+ import * as ttl2jsonld from '@frogcat/ttl2jsonld'
9
+ import solidNs from 'solid-namespace'
10
+ import CanonicalDataFactory from './factories/canonical-data-factory'
10
11
  import * as Uri from './uri'
11
12
  import * as Util from './utils-js'
12
- import CanonicalDataFactory from './factories/canonical-data-factory'
13
13
  import { createXSD } from './xsd'
14
- import solidNs from 'solid-namespace'
15
- import * as ttl2jsonld from '@frogcat/ttl2jsonld'
16
14
 
17
15
 
18
16
  export default function createSerializer(store) {
@@ -52,6 +50,12 @@ export class Serializer {
52
50
  return this
53
51
  }
54
52
 
53
+ /**
54
+ * Set serializer behavior flags. Letters can be combined with spaces.
55
+ * Examples: 'si', 'deinprstux', 'si dr', 'o'.
56
+ * Notable flags:
57
+ * - 'o': do not abbreviate to a prefixed name when the local part contains a dot
58
+ */
55
59
  setFlags(flags) {
56
60
  this.flags = flags || '';
57
61
  return this
@@ -251,10 +255,33 @@ export class Serializer {
251
255
  return this.statementsToN3(f.statements)
252
256
  }
253
257
 
254
- _notQNameChars = '\t\r\n !"#$%&\'()*.,+/;<=>?@[\\]^`{|}~'
258
+ _notQNameChars = '\t\r\n !"#$%&\'()*,+/;<=>?@[\\]^`{|}~' // issue#228
255
259
  _notNameChars =
256
260
  (this._notQNameChars + ':')
257
261
 
262
+ // Validate if a string is a valid PN_LOCAL per Turtle 1.1 spec
263
+ // Allows dots inside the local name but not as trailing character
264
+ // Also allows empty local names (for URIs ending in / or #)
265
+ isValidPNLocal(local) {
266
+ // Empty local name is valid (e.g., ex: for http://example.com/)
267
+ if (local.length === 0) return true
268
+
269
+ // Cannot end with a dot
270
+ if (local[local.length - 1] === '.') return false
271
+
272
+ // Check each character (allow dots mid-string)
273
+ for (var i = 0; i < local.length; i++) {
274
+ var ch = local[i]
275
+ // Dot is allowed unless it's the last character (checked above)
276
+ if (ch === '.') continue
277
+ // Other characters must not be in the blacklist
278
+ if (this._notNameChars.indexOf(ch) >= 0) {
279
+ return false
280
+ }
281
+ }
282
+ return true
283
+ }
284
+
258
285
  explicitURI(uri) {
259
286
  if (this.flags.indexOf('r') < 0 && this.base) {
260
287
  uri = Uri.refTo(this.base, uri)
@@ -628,13 +655,17 @@ export class Serializer {
628
655
  if (j >= 0 && this.flags.indexOf('p') < 0 &&
629
656
  // Can split at namespace but only if http[s]: URI or file: or ws[s] (why not others?)
630
657
  (uri.indexOf('http') === 0 || uri.indexOf('ws') === 0 || uri.indexOf('file') === 0)) {
631
- var canSplit = true
632
- for (var k = j + 1; k < uri.length; k++) {
633
- if (this._notNameChars.indexOf(uri[k]) >= 0) {
634
- canSplit = false
635
- break
636
- }
637
- }
658
+ var localid = uri.slice(j + 1)
659
+ var namesp = uri.slice(0, j + 1)
660
+ // Don't split if namespace is just the protocol (e.g., https://)
661
+ // A valid namespace should have content after the protocol
662
+ var minNamespaceLength = uri.indexOf('://') + 4 // e.g., "http://x" minimum
663
+ // Also don't split if namespace is the base directory (would serialize as relative URI)
664
+ var baseDir = this.base ? this.base.slice(0, Math.max(this.base.lastIndexOf('/'), this.base.lastIndexOf('#')) + 1) : null
665
+ var namespaceIsBaseDir = baseDir && namesp === baseDir
666
+ // If flag 'o' is present, forbid dots in local part when abbreviating
667
+ var forbidDotLocal = this.flags.indexOf('o') >= 0 && localid.indexOf('.') >= 0
668
+ var canSplit = !namespaceIsBaseDir && !forbidDotLocal && namesp.length > minNamespaceLength && this.isValidPNLocal(localid)
638
669
  /*
639
670
  if (uri.slice(0, j + 1) === this.base + '#') { // base-relative
640
671
  if (canSplit) {
@@ -645,8 +676,6 @@ export class Serializer {
645
676
  }
646
677
  */
647
678
  if (canSplit) {
648
- var localid = uri.slice(j + 1)
649
- var namesp = uri.slice(0, j + 1)
650
679
  if (this.defaultNamespace && this.defaultNamespace === namesp &&
651
680
  this.flags.indexOf('d') < 0) { // d -> suppress default
652
681
  if (this.flags.indexOf('k') >= 0 &&
@@ -883,10 +912,10 @@ export class Serializer {
883
912
  break
884
913
  case 'Literal':
885
914
  results = results.concat(['<' + t +
886
- (st.object.datatype.equals(this.xsd.string)
887
- ? ''
888
- : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') +
889
- (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') +
915
+ (st.object.language ? ' xml:lang="' + st.object.language + '"' :
916
+ (st.object.datatype.equals(this.xsd.string)
917
+ ? ''
918
+ : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"')) +
890
919
  '>' + escapeForXML(st.object.value) +
891
920
  '</' + t + '>'])
892
921
  break
@@ -949,9 +978,10 @@ export class Serializer {
949
978
  break
950
979
  case 'Literal':
951
980
  results = results.concat(['<' + qname(st.predicate) +
952
- (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') +
953
- (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') +
954
- '>' + escapeForXML(st.object.value) +
981
+ (st.object.language ?
982
+ ' xml:lang="' + st.object.language + '"' :
983
+ (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"'))
984
+ + '>' + escapeForXML(st.object.value) +
955
985
  '</' + qname(st.predicate) + '>'])
956
986
  break
957
987
  case 'Collection':