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/esm/n3parser.js CHANGED
@@ -183,9 +183,19 @@ var ws = new RegExp("^[ \\t]*", 'g');
183
183
  var signed_integer = new RegExp("^[-+]?[0-9]+", 'g');
184
184
  var number_syntax = new RegExp("^([-+]?[0-9]+)(\\.[0-9]+)?([eE][-+]?[0-9]+)?", 'g');
185
185
  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?');
186
+
187
+ // Reused in tight loops to detect whitespace or comment after a dot
188
+ var wsOrHash = new RegExp("[\\s#]");
186
189
  var digitstring = new RegExp("^[0-9]+", 'g');
187
190
  var interesting = new RegExp("[\\\\\\r\\n\\\"]", 'g');
188
191
  var langcode = new RegExp("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*", 'g');
192
+
193
+ // Returns true when a dot at position i should terminate a name,
194
+ // i.e., when the next character is whitespace, a comment start, or EOF
195
+ function dotTerminatesName(str, i) {
196
+ var next = str.charAt(i + 1);
197
+ return next === '' || wsOrHash.test(next);
198
+ }
189
199
  function createSinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why) {
190
200
  return new SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why);
191
201
  }
@@ -674,6 +684,16 @@ export class SinkParser {
674
684
  if (j < 0) {
675
685
  throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF when ']' expected after [ <propertyList>");
676
686
  }
687
+ if (str.slice(j, j + 1) == ".") {
688
+ // If a dot is found after a blank node, treat it as a statement terminator.
689
+ // Do NOT consume the '.' here: statement terminators are handled centrally by
690
+ // checkDot() (called by directiveOrStatement after statement()). Consuming the dot
691
+ // locally would bypass that unified logic and could cause inconsistencies.
692
+ // We do consume ']' below because it is a structural closer of the blank node,
693
+ // not a statement terminator.
694
+ res.push(subj);
695
+ return j; // leave '.' for checkDot()
696
+ }
677
697
  if (str.slice(j, j + 1) != "]") {
678
698
  throw BadSyntax(this._thisDoc, this.lines, str, j, "']' expected");
679
699
  }
@@ -1122,7 +1142,17 @@ export class SinkParser {
1122
1142
  return -1;
1123
1143
  }
1124
1144
  var i = j;
1125
- while (i < pyjslib_len(str) && _notNameChars.indexOf(str.charAt(i)) < 0) {
1145
+ while (i < pyjslib_len(str)) {
1146
+ var c = str.charAt(i);
1147
+ if (c === '.') {
1148
+ if (dotTerminatesName(str, i)) {
1149
+ break; // treat as statement terminator, not part of name
1150
+ }
1151
+ // else: accept '.' as part of name
1152
+ } else if (_notNameChars.indexOf(c) >= 0) {
1153
+ // Other invalid characters terminate the name
1154
+ break;
1155
+ }
1126
1156
  var i = i + 1;
1127
1157
  }
1128
1158
  res.push(str.slice(j, i));
@@ -1148,12 +1178,15 @@ export class SinkParser {
1148
1178
  var i = i + 1;
1149
1179
  while (i < pyjslib_len(str)) {
1150
1180
  var c = str.charAt(i);
1151
- if (_notNameChars.indexOf(c) < 0) {
1152
- var ln = ln + c;
1153
- var i = i + 1;
1154
- } else {
1181
+ if (c === '.') {
1182
+ if (dotTerminatesName(str, i)) {
1183
+ break; // dot ends the name here
1184
+ }
1185
+ } else if (_notNameChars.indexOf(c) >= 0) {
1155
1186
  break;
1156
1187
  }
1188
+ var ln = ln + c;
1189
+ var i = i + 1;
1157
1190
  }
1158
1191
  } else {
1159
1192
  var ln = "";
@@ -1164,12 +1197,15 @@ export class SinkParser {
1164
1197
  var ln = "";
1165
1198
  while (i < pyjslib_len(str)) {
1166
1199
  var c = str.charAt(i);
1167
- if (_notNameChars.indexOf(c) < 0) {
1168
- var ln = ln + c;
1169
- var i = i + 1;
1170
- } else {
1200
+ if (c === '.') {
1201
+ if (dotTerminatesName(str, i)) {
1202
+ break; // dot ends the name here
1203
+ }
1204
+ } else if (_notNameChars.indexOf(c) >= 0) {
1171
1205
  break;
1172
1206
  }
1207
+ var ln = ln + c;
1208
+ var i = i + 1;
1173
1209
  }
1174
1210
  res.push(new pyjslib_Tuple([pfx, ln]));
1175
1211
  return i;
package/esm/serialize.js CHANGED
@@ -40,7 +40,8 @@ contentType, callback, options) {
40
40
  return executeCallback(null, documentString);
41
41
  case TurtleContentType:
42
42
  case TurtleLegacyContentType:
43
- sz.setFlags('si'); // Suppress = for sameAs and => for implies
43
+ // Suppress = for sameAs and => for implies; preserve any user-specified flags (e.g., 'o')
44
+ sz.setFlags('si' + (opts.flags ? ' ' + opts.flags : ''));
44
45
  documentString = sz.statementsToN3(newSts);
45
46
  return executeCallback(null, documentString);
46
47
  case NTriplesContentType:
@@ -48,7 +49,8 @@ contentType, callback, options) {
48
49
  documentString = sz.statementsToNTriples(newSts);
49
50
  return executeCallback(null, documentString);
50
51
  case JSONLDContentType:
51
- sz.setFlags('si dr'); // turtle + dr (means no default, no relative prefix)
52
+ // turtle + dr (means no default, no relative prefix); preserve user flags
53
+ sz.setFlags('si dr' + (opts.flags ? ' ' + opts.flags : ''));
52
54
  documentString = sz.statementsToJsonld(newSts); // convert via turtle
53
55
  return executeCallback(null, documentString);
54
56
  case NQuadsContentType:
package/esm/serializer.js CHANGED
@@ -6,21 +6,20 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
6
6
  ** This is or was https://github.com/linkeddata/rdflib.js/blob/main/src/serializer.js
7
7
  ** Licence: MIT
8
8
  */
9
- import NamedNode from './named-node';
10
- import BlankNode from './blank-node';
9
+ import * as ttl2jsonld from '@frogcat/ttl2jsonld';
10
+ import solidNs from 'solid-namespace';
11
+ import CanonicalDataFactory from './factories/canonical-data-factory';
11
12
  import * as Uri from './uri';
12
13
  import * as Util from './utils-js';
13
- import CanonicalDataFactory from './factories/canonical-data-factory';
14
14
  import { createXSD } from './xsd';
15
- import solidNs from 'solid-namespace';
16
- import * as ttl2jsonld from '@frogcat/ttl2jsonld';
17
15
  export default function createSerializer(store) {
18
16
  return new Serializer(store);
19
17
  }
20
18
  ;
21
19
  export class Serializer {
22
20
  constructor(store) {
23
- _defineProperty(this, "_notQNameChars", '\t\r\n !"#$%&\'()*.,+/;<=>?@[\\]^`{|}~');
21
+ _defineProperty(this, "_notQNameChars", '\t\r\n !"#$%&\'()*,+/;<=>?@[\\]^`{|}~');
22
+ // issue#228
24
23
  _defineProperty(this, "_notNameChars", this._notQNameChars + ':');
25
24
  // stringToN3: String escaping for N3
26
25
  _defineProperty(this, "validPrefix", new RegExp(/^[a-zA-Z][a-zA-Z0-9]*$/));
@@ -53,6 +52,13 @@ export class Serializer {
53
52
  this.base = base;
54
53
  return this;
55
54
  }
55
+
56
+ /**
57
+ * Set serializer behavior flags. Letters can be combined with spaces.
58
+ * Examples: 'si', 'deinprstux', 'si dr', 'o'.
59
+ * Notable flags:
60
+ * - 'o': do not abbreviate to a prefixed name when the local part contains a dot
61
+ */
56
62
  setFlags(flags) {
57
63
  this.flags = flags || '';
58
64
  return this;
@@ -243,6 +249,28 @@ export class Serializer {
243
249
  toN3(f) {
244
250
  return this.statementsToN3(f.statements);
245
251
  }
252
+ // Validate if a string is a valid PN_LOCAL per Turtle 1.1 spec
253
+ // Allows dots inside the local name but not as trailing character
254
+ // Also allows empty local names (for URIs ending in / or #)
255
+ isValidPNLocal(local) {
256
+ // Empty local name is valid (e.g., ex: for http://example.com/)
257
+ if (local.length === 0) return true;
258
+
259
+ // Cannot end with a dot
260
+ if (local[local.length - 1] === '.') return false;
261
+
262
+ // Check each character (allow dots mid-string)
263
+ for (var i = 0; i < local.length; i++) {
264
+ var ch = local[i];
265
+ // Dot is allowed unless it's the last character (checked above)
266
+ if (ch === '.') continue;
267
+ // Other characters must not be in the blacklist
268
+ if (this._notNameChars.indexOf(ch) >= 0) {
269
+ return false;
270
+ }
271
+ }
272
+ return true;
273
+ }
246
274
  explicitURI(uri) {
247
275
  if (this.flags.indexOf('r') < 0 && this.base) {
248
276
  uri = Uri.refTo(this.base, uri);
@@ -605,13 +633,17 @@ export class Serializer {
605
633
  if (j >= 0 && this.flags.indexOf('p') < 0 && (
606
634
  // Can split at namespace but only if http[s]: URI or file: or ws[s] (why not others?)
607
635
  uri.indexOf('http') === 0 || uri.indexOf('ws') === 0 || uri.indexOf('file') === 0)) {
608
- var canSplit = true;
609
- for (var k = j + 1; k < uri.length; k++) {
610
- if (this._notNameChars.indexOf(uri[k]) >= 0) {
611
- canSplit = false;
612
- break;
613
- }
614
- }
636
+ var localid = uri.slice(j + 1);
637
+ var namesp = uri.slice(0, j + 1);
638
+ // Don't split if namespace is just the protocol (e.g., https://)
639
+ // A valid namespace should have content after the protocol
640
+ var minNamespaceLength = uri.indexOf('://') + 4; // e.g., "http://x" minimum
641
+ // Also don't split if namespace is the base directory (would serialize as relative URI)
642
+ var baseDir = this.base ? this.base.slice(0, Math.max(this.base.lastIndexOf('/'), this.base.lastIndexOf('#')) + 1) : null;
643
+ var namespaceIsBaseDir = baseDir && namesp === baseDir;
644
+ // If flag 'o' is present, forbid dots in local part when abbreviating
645
+ var forbidDotLocal = this.flags.indexOf('o') >= 0 && localid.indexOf('.') >= 0;
646
+ var canSplit = !namespaceIsBaseDir && !forbidDotLocal && namesp.length > minNamespaceLength && this.isValidPNLocal(localid);
615
647
  /*
616
648
  if (uri.slice(0, j + 1) === this.base + '#') { // base-relative
617
649
  if (canSplit) {
@@ -622,8 +654,6 @@ export class Serializer {
622
654
  }
623
655
  */
624
656
  if (canSplit) {
625
- var localid = uri.slice(j + 1);
626
- var namesp = uri.slice(0, j + 1);
627
657
  if (this.defaultNamespace && this.defaultNamespace === namesp && this.flags.indexOf('d') < 0) {
628
658
  // d -> suppress default
629
659
  if (this.flags.indexOf('k') >= 0 && this.keyords.indexOf(localid) < 0) {
@@ -844,7 +874,7 @@ export class Serializer {
844
874
  results = results.concat(['<' + t + ' rdf:resource="' + relURI(st.object) + '"/>']);
845
875
  break;
846
876
  case 'Literal':
847
- results = results.concat(['<' + t + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + '>' + escapeForXML(st.object.value) + '</' + t + '>']);
877
+ results = results.concat(['<' + t + (st.object.language ? ' xml:lang="' + st.object.language + '"' : st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') + '>' + escapeForXML(st.object.value) + '</' + t + '>']);
848
878
  break;
849
879
  case 'Collection':
850
880
  results = results.concat(['<' + t + ' rdf:parseType="Collection">', collectionXMLTree(st.object, stats), '</' + t + '>']);
@@ -895,7 +925,7 @@ export class Serializer {
895
925
  results = results.concat(['<' + qname(st.predicate) + ' rdf:resource="' + relURI(st.object) + '"/>']);
896
926
  break;
897
927
  case 'Literal':
898
- results = results.concat(['<' + qname(st.predicate) + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + '>' + escapeForXML(st.object.value) + '</' + qname(st.predicate) + '>']);
928
+ results = results.concat(['<' + qname(st.predicate) + (st.object.language ? ' xml:lang="' + st.object.language + '"' : st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + '>' + escapeForXML(st.object.value) + '</' + qname(st.predicate) + '>']);
899
929
  break;
900
930
  case 'Collection':
901
931
  results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Collection">', collectionXMLTree(st.object, stats), '</' + qname(st.predicate) + '>']);
package/lib/n3parser.js CHANGED
@@ -191,9 +191,19 @@ var ws = new RegExp("^[ \\t]*", 'g');
191
191
  var signed_integer = new RegExp("^[-+]?[0-9]+", 'g');
192
192
  var number_syntax = new RegExp("^([-+]?[0-9]+)(\\.[0-9]+)?([eE][-+]?[0-9]+)?", 'g');
193
193
  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?');
194
+
195
+ // Reused in tight loops to detect whitespace or comment after a dot
196
+ var wsOrHash = new RegExp("[\\s#]");
194
197
  var digitstring = new RegExp("^[0-9]+", 'g');
195
198
  var interesting = new RegExp("[\\\\\\r\\n\\\"]", 'g');
196
199
  var langcode = new RegExp("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*", 'g');
200
+
201
+ // Returns true when a dot at position i should terminate a name,
202
+ // i.e., when the next character is whitespace, a comment start, or EOF
203
+ function dotTerminatesName(str, i) {
204
+ var next = str.charAt(i + 1);
205
+ return next === '' || wsOrHash.test(next);
206
+ }
197
207
  function createSinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why) {
198
208
  return new SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why);
199
209
  }
@@ -682,6 +692,16 @@ class SinkParser {
682
692
  if (j < 0) {
683
693
  throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF when ']' expected after [ <propertyList>");
684
694
  }
695
+ if (str.slice(j, j + 1) == ".") {
696
+ // If a dot is found after a blank node, treat it as a statement terminator.
697
+ // Do NOT consume the '.' here: statement terminators are handled centrally by
698
+ // checkDot() (called by directiveOrStatement after statement()). Consuming the dot
699
+ // locally would bypass that unified logic and could cause inconsistencies.
700
+ // We do consume ']' below because it is a structural closer of the blank node,
701
+ // not a statement terminator.
702
+ res.push(subj);
703
+ return j; // leave '.' for checkDot()
704
+ }
685
705
  if (str.slice(j, j + 1) != "]") {
686
706
  throw BadSyntax(this._thisDoc, this.lines, str, j, "']' expected");
687
707
  }
@@ -1130,7 +1150,17 @@ class SinkParser {
1130
1150
  return -1;
1131
1151
  }
1132
1152
  var i = j;
1133
- while (i < pyjslib_len(str) && _notNameChars.indexOf(str.charAt(i)) < 0) {
1153
+ while (i < pyjslib_len(str)) {
1154
+ var c = str.charAt(i);
1155
+ if (c === '.') {
1156
+ if (dotTerminatesName(str, i)) {
1157
+ break; // treat as statement terminator, not part of name
1158
+ }
1159
+ // else: accept '.' as part of name
1160
+ } else if (_notNameChars.indexOf(c) >= 0) {
1161
+ // Other invalid characters terminate the name
1162
+ break;
1163
+ }
1134
1164
  var i = i + 1;
1135
1165
  }
1136
1166
  res.push(str.slice(j, i));
@@ -1156,12 +1186,15 @@ class SinkParser {
1156
1186
  var i = i + 1;
1157
1187
  while (i < pyjslib_len(str)) {
1158
1188
  var c = str.charAt(i);
1159
- if (_notNameChars.indexOf(c) < 0) {
1160
- var ln = ln + c;
1161
- var i = i + 1;
1162
- } else {
1189
+ if (c === '.') {
1190
+ if (dotTerminatesName(str, i)) {
1191
+ break; // dot ends the name here
1192
+ }
1193
+ } else if (_notNameChars.indexOf(c) >= 0) {
1163
1194
  break;
1164
1195
  }
1196
+ var ln = ln + c;
1197
+ var i = i + 1;
1165
1198
  }
1166
1199
  } else {
1167
1200
  var ln = "";
@@ -1172,12 +1205,15 @@ class SinkParser {
1172
1205
  var ln = "";
1173
1206
  while (i < pyjslib_len(str)) {
1174
1207
  var c = str.charAt(i);
1175
- if (_notNameChars.indexOf(c) < 0) {
1176
- var ln = ln + c;
1177
- var i = i + 1;
1178
- } else {
1208
+ if (c === '.') {
1209
+ if (dotTerminatesName(str, i)) {
1210
+ break; // dot ends the name here
1211
+ }
1212
+ } else if (_notNameChars.indexOf(c) >= 0) {
1179
1213
  break;
1180
1214
  }
1215
+ var ln = ln + c;
1216
+ var i = i + 1;
1181
1217
  }
1182
1218
  res.push(new pyjslib_Tuple([pfx, ln]));
1183
1219
  return i;
package/lib/serialize.js CHANGED
@@ -47,7 +47,8 @@ contentType, callback, options) {
47
47
  return executeCallback(null, documentString);
48
48
  case _types.TurtleContentType:
49
49
  case _types.TurtleLegacyContentType:
50
- sz.setFlags('si'); // Suppress = for sameAs and => for implies
50
+ // Suppress = for sameAs and => for implies; preserve any user-specified flags (e.g., 'o')
51
+ sz.setFlags('si' + (opts.flags ? ' ' + opts.flags : ''));
51
52
  documentString = sz.statementsToN3(newSts);
52
53
  return executeCallback(null, documentString);
53
54
  case _types.NTriplesContentType:
@@ -55,7 +56,8 @@ contentType, callback, options) {
55
56
  documentString = sz.statementsToNTriples(newSts);
56
57
  return executeCallback(null, documentString);
57
58
  case _types.JSONLDContentType:
58
- sz.setFlags('si dr'); // turtle + dr (means no default, no relative prefix)
59
+ // turtle + dr (means no default, no relative prefix); preserve user flags
60
+ sz.setFlags('si dr' + (opts.flags ? ' ' + opts.flags : ''));
59
61
  documentString = sz.statementsToJsonld(newSts); // convert via turtle
60
62
  return executeCallback(null, documentString);
61
63
  case _types.NQuadsContentType:
@@ -13,15 +13,21 @@ export class Serializer {
13
13
  store: any;
14
14
  rdfFactory: any;
15
15
  xsd: {
16
- boolean: NamedNode;
17
- dateTime: NamedNode;
18
- decimal: NamedNode;
19
- double: NamedNode;
20
- integer: NamedNode;
21
- langString: NamedNode;
22
- string: NamedNode;
16
+ boolean: import("./named-node").default;
17
+ dateTime: import("./named-node").default;
18
+ decimal: import("./named-node").default;
19
+ double: import("./named-node").default;
20
+ integer: import("./named-node").default;
21
+ langString: import("./named-node").default;
22
+ string: import("./named-node").default;
23
23
  };
24
24
  setBase(base: any): Serializer;
25
+ /**
26
+ * Set serializer behavior flags. Letters can be combined with spaces.
27
+ * Examples: 'si', 'deinprstux', 'si dr', 'o'.
28
+ * Notable flags:
29
+ * - 'o': do not abbreviate to a prefixed name when the local part contains a dot
30
+ */
25
31
  setFlags(flags: any): Serializer;
26
32
  toStr(x: any): any;
27
33
  fromStr(s: any): any;
@@ -51,6 +57,7 @@ export class Serializer {
51
57
  toN3(f: any): string;
52
58
  _notQNameChars: string;
53
59
  _notNameChars: string;
60
+ isValidPNLocal(local: any): boolean;
54
61
  explicitURI(uri: any): string;
55
62
  statementsToNTriples(sts: any): string;
56
63
  statementsToN3(sts: any): string;
@@ -65,4 +72,3 @@ export class Serializer {
65
72
  statementsToXML(sts: any): string;
66
73
  statementsToJsonld(sts: any): string;
67
74
  }
68
- import NamedNode from './named-node';
package/lib/serializer.js CHANGED
@@ -7,14 +7,12 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.Serializer = void 0;
8
8
  exports.default = createSerializer;
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
- var _namedNode = _interopRequireDefault(require("./named-node"));
11
- var _blankNode = _interopRequireDefault(require("./blank-node"));
10
+ var ttl2jsonld = _interopRequireWildcard(require("@frogcat/ttl2jsonld"));
11
+ var _solidNamespace = _interopRequireDefault(require("solid-namespace"));
12
+ var _canonicalDataFactory = _interopRequireDefault(require("./factories/canonical-data-factory"));
12
13
  var Uri = _interopRequireWildcard(require("./uri"));
13
14
  var Util = _interopRequireWildcard(require("./utils-js"));
14
- var _canonicalDataFactory = _interopRequireDefault(require("./factories/canonical-data-factory"));
15
15
  var _xsd = require("./xsd");
16
- var _solidNamespace = _interopRequireDefault(require("solid-namespace"));
17
- var ttl2jsonld = _interopRequireWildcard(require("@frogcat/ttl2jsonld"));
18
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
19
17
  /* Serialization of RDF Graphs
20
18
  **
@@ -30,7 +28,8 @@ function createSerializer(store) {
30
28
  ;
31
29
  class Serializer {
32
30
  constructor(store) {
33
- (0, _defineProperty2.default)(this, "_notQNameChars", '\t\r\n !"#$%&\'()*.,+/;<=>?@[\\]^`{|}~');
31
+ (0, _defineProperty2.default)(this, "_notQNameChars", '\t\r\n !"#$%&\'()*,+/;<=>?@[\\]^`{|}~');
32
+ // issue#228
34
33
  (0, _defineProperty2.default)(this, "_notNameChars", this._notQNameChars + ':');
35
34
  // stringToN3: String escaping for N3
36
35
  (0, _defineProperty2.default)(this, "validPrefix", new RegExp(/^[a-zA-Z][a-zA-Z0-9]*$/));
@@ -63,6 +62,13 @@ class Serializer {
63
62
  this.base = base;
64
63
  return this;
65
64
  }
65
+
66
+ /**
67
+ * Set serializer behavior flags. Letters can be combined with spaces.
68
+ * Examples: 'si', 'deinprstux', 'si dr', 'o'.
69
+ * Notable flags:
70
+ * - 'o': do not abbreviate to a prefixed name when the local part contains a dot
71
+ */
66
72
  setFlags(flags) {
67
73
  this.flags = flags || '';
68
74
  return this;
@@ -253,6 +259,28 @@ class Serializer {
253
259
  toN3(f) {
254
260
  return this.statementsToN3(f.statements);
255
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
+ }
256
284
  explicitURI(uri) {
257
285
  if (this.flags.indexOf('r') < 0 && this.base) {
258
286
  uri = Uri.refTo(this.base, uri);
@@ -615,13 +643,17 @@ class Serializer {
615
643
  if (j >= 0 && this.flags.indexOf('p') < 0 && (
616
644
  // Can split at namespace but only if http[s]: URI or file: or ws[s] (why not others?)
617
645
  uri.indexOf('http') === 0 || uri.indexOf('ws') === 0 || uri.indexOf('file') === 0)) {
618
- var canSplit = true;
619
- for (var k = j + 1; k < uri.length; k++) {
620
- if (this._notNameChars.indexOf(uri[k]) >= 0) {
621
- canSplit = false;
622
- break;
623
- }
624
- }
646
+ var localid = uri.slice(j + 1);
647
+ var namesp = uri.slice(0, j + 1);
648
+ // Don't split if namespace is just the protocol (e.g., https://)
649
+ // A valid namespace should have content after the protocol
650
+ var minNamespaceLength = uri.indexOf('://') + 4; // e.g., "http://x" minimum
651
+ // Also don't split if namespace is the base directory (would serialize as relative URI)
652
+ var baseDir = this.base ? this.base.slice(0, Math.max(this.base.lastIndexOf('/'), this.base.lastIndexOf('#')) + 1) : null;
653
+ var namespaceIsBaseDir = baseDir && namesp === baseDir;
654
+ // If flag 'o' is present, forbid dots in local part when abbreviating
655
+ var forbidDotLocal = this.flags.indexOf('o') >= 0 && localid.indexOf('.') >= 0;
656
+ var canSplit = !namespaceIsBaseDir && !forbidDotLocal && namesp.length > minNamespaceLength && this.isValidPNLocal(localid);
625
657
  /*
626
658
  if (uri.slice(0, j + 1) === this.base + '#') { // base-relative
627
659
  if (canSplit) {
@@ -632,8 +664,6 @@ class Serializer {
632
664
  }
633
665
  */
634
666
  if (canSplit) {
635
- var localid = uri.slice(j + 1);
636
- var namesp = uri.slice(0, j + 1);
637
667
  if (this.defaultNamespace && this.defaultNamespace === namesp && this.flags.indexOf('d') < 0) {
638
668
  // d -> suppress default
639
669
  if (this.flags.indexOf('k') >= 0 && this.keyords.indexOf(localid) < 0) {
@@ -854,7 +884,7 @@ class Serializer {
854
884
  results = results.concat(['<' + t + ' rdf:resource="' + relURI(st.object) + '"/>']);
855
885
  break;
856
886
  case 'Literal':
857
- results = results.concat(['<' + t + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + '>' + escapeForXML(st.object.value) + '</' + t + '>']);
887
+ results = results.concat(['<' + t + (st.object.language ? ' xml:lang="' + st.object.language + '"' : st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') + '>' + escapeForXML(st.object.value) + '</' + t + '>']);
858
888
  break;
859
889
  case 'Collection':
860
890
  results = results.concat(['<' + t + ' rdf:parseType="Collection">', collectionXMLTree(st.object, stats), '</' + t + '>']);
@@ -905,7 +935,7 @@ class Serializer {
905
935
  results = results.concat(['<' + qname(st.predicate) + ' rdf:resource="' + relURI(st.object) + '"/>']);
906
936
  break;
907
937
  case 'Literal':
908
- results = results.concat(['<' + qname(st.predicate) + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + '>' + escapeForXML(st.object.value) + '</' + qname(st.predicate) + '>']);
938
+ results = results.concat(['<' + qname(st.predicate) + (st.object.language ? ' xml:lang="' + st.object.language + '"' : st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + '>' + escapeForXML(st.object.value) + '</' + qname(st.predicate) + '>']);
909
939
  break;
910
940
  case 'Collection':
911
941
  results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Collection">', collectionXMLTree(st.object, stats), '</' + qname(st.predicate) + '>']);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rdflib",
3
3
  "description": "an RDF library for node.js. Suitable for client and server side.",
4
- "version": "2.3.0",
4
+ "version": "2.3.1-0007ffed",
5
5
  "private": false,
6
6
  "browserslist": [
7
7
  "> 0.5%"
@@ -101,7 +101,7 @@
101
101
  "test": "npm run test:unit && npm run test:serialize && npm run test:types",
102
102
  "test:clean": "rimraf tests/serialize/,*",
103
103
  "test:serialize": "npm run build && npm run test:serialize:all && npm run test:clean",
104
- "test:serialize:all": "npm run test:serialize:1 && npm run test:serialize:2 && npm run test:serialize:3 && npm run test:serialize:4 && npm run test:serialize:5 && npm run test:serialize:6 && npm run test:serialize:7 && npm run test:serialize:8 && npm run test:serialize:10 && npm run test:serialize:11 && npm run test:serialize:12 && npm run test:serialize:13 && npm run test:serialize:14 && npm run test:serialize:15",
104
+ "test:serialize:all": "npm run test:serialize:1 && npm run test:serialize:2 && npm run test:serialize:3 && npm run test:serialize:4 && npm run test:serialize:5 && npm run test:serialize:6 && npm run test:serialize:7 && npm run test:serialize:8 && npm run test:serialize:10 && npm run test:serialize:11 && npm run test:serialize:12 && npm run test:serialize:13 && npm run test:serialize:14 && npm run test:serialize:15 && npm run test:serialize:16 && npm run test:serialize:17 && npm run test:serialize:18",
105
105
  "test:serialize:1": "cd ./tests/serialize && node ./data.js -in=t1.ttl -format=application/rdf+xml -out=,t1.xml && fs-grep http://www.w3.org/2001/XMLSchema#integer ,t1.xml",
106
106
  "test:serialize:2": "cd ./tests/serialize && node ./data.js -in=t2.ttl -format=application/rdf+xml -out=,t2.xml && node diff ,t2.xml t2-ref.xml",
107
107
  "test:serialize:3": "cd ./tests/serialize && node ./data.js -in=t3.ttl -format=application/rdf+xml -out=,t3.xml && node diff ,t3.xml t3-ref.xml",
@@ -117,6 +117,8 @@
117
117
  "test:serialize:14": "cd ./tests/serialize && node ./data.js -in=t14.html -format=text/turtle -out=,t14.ttl && node diff ,t14.ttl t14-ref.ttl",
118
118
  "test:serialize:15": "cd ./tests/serialize && node ./data.js -in=t15.html -format=text/turtle -out=,t15.ttl && node diff ,t15.ttl t15-ref.ttl",
119
119
  "test:serialize:16": "cd ./tests/serialize && node ./data.js -in=t1.ttl -format=application/ld+json -out=,t1.jsonld && node diff ,t1.jsonld t16-ref.jsonld",
120
+ "test:serialize:17": "cd ./tests/serialize && node ./data.js -in=t17.ttl -format=application/rdf+xml -out=,t17.xml && node diff ,t17.xml t17-ref.xml",
121
+ "test:serialize:18": "cd ./tests/serialize && node ./data.js -in=t18.ttl -format=application/rdf+xml -out=,t18.xml && node diff ,t18.xml t18-ref.xml",
120
122
  "test:types": "tsc --noEmit --target es2019 --moduleResolution node tests/types/*.ts",
121
123
  "test:unit": "mocha --growl --require ./tests/babel-register.js tests/unit/**-test.*",
122
124
  "test:unit:egp": "mocha --require ./tests/babel-register.js tests/unit/fetcher-egp-test.js",