n3 2.3.0 → 2.5.0

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
@@ -83,14 +83,21 @@ export default class N3Parser {
83
83
  this._contextStack.push({ type, subject, predicate, object, graph });
84
84
  return;
85
85
  }
86
- this._contextStack.push({
86
+ const context = {
87
87
  type,
88
88
  subject, predicate, object, graph,
89
89
  inverse: this._inversePredicate,
90
90
  blankPrefix: this._prefixes._,
91
91
  quantified: this._quantified,
92
92
  emptyFormula: this._emptyFormula,
93
- });
93
+ };
94
+ // Prefix and base declarations are scoped to their formula
95
+ if (type === 'formula') {
96
+ context.prefixes = this._prefixes;
97
+ context.base = [this._base, this._basePath, this._baseRoot, this._baseScheme];
98
+ this._prefixes = Object.create(this._prefixes);
99
+ }
100
+ this._contextStack.push(context);
94
101
  // Every new scope resets the predicate direction
95
102
  this._inversePredicate = false;
96
103
  // In N3, blank nodes are scoped to a formula
@@ -122,7 +129,12 @@ export default class N3Parser {
122
129
  // Restore N3 context settings
123
130
  if (this._n3Mode) {
124
131
  this._inversePredicate = context.inverse;
125
- this._prefixes._ = context.blankPrefix;
132
+ if (type === 'formula') {
133
+ this._prefixes = context.prefixes;
134
+ [this._base, this._basePath, this._baseRoot, this._baseScheme] = context.base;
135
+ }
136
+ else
137
+ this._prefixes._ = context.blankPrefix;
126
138
  this._quantified = context.quantified;
127
139
  this._emptyFormula = context.emptyFormula;
128
140
  }
@@ -175,6 +187,28 @@ export default class N3Parser {
175
187
  }
176
188
  }
177
189
 
190
+ // ### `_readInFormulaContext` reads a token at the statement level of a formula
191
+ _readInFormulaContext(token) {
192
+ switch (token.type) {
193
+ case 'PREFIX':
194
+ this._sparqlStyle = true;
195
+ case '@prefix':
196
+ return this._readPrefix;
197
+ case 'BASE':
198
+ this._sparqlStyle = true;
199
+ case '@base':
200
+ return this._readBaseIRI;
201
+ default:
202
+ return this._readSubject(token);
203
+ }
204
+ }
205
+
206
+ // ### `_getStatementReader` returns the reader for the current statement scope
207
+ _getStatementReader() {
208
+ const context = this._contextStack[this._contextStack.length - 1];
209
+ return context && context.type === 'formula' ? this._readInFormulaContext : this._readInTopContext;
210
+ }
211
+
178
212
  // ### `_readEntity` reads an IRI, prefixed name, blank node, or variable
179
213
  _readEntity(token, quantifier) {
180
214
  let value;
@@ -245,7 +279,7 @@ export default class N3Parser {
245
279
  return this._error('Unexpected graph', token);
246
280
  this._saveContext('formula', this._graph,
247
281
  this._graph = this._factory.blankNode(), null, null);
248
- return this._readSubject;
282
+ return this._readInFormulaContext;
249
283
  case '}':
250
284
  // No subject; the graph in which we are reading is closed instead
251
285
  return this._readPunctuation(token);
@@ -303,6 +337,7 @@ export default class N3Parser {
303
337
  // ### `_readPredicate` reads a quad's predicate
304
338
  _readPredicate(token) {
305
339
  const type = token.type;
340
+ let pathable = false;
306
341
  switch (type) {
307
342
  case 'inverse':
308
343
  this._inversePredicate = true;
@@ -334,6 +369,7 @@ export default class N3Parser {
334
369
  else
335
370
  this._predicate = this._factory.literal(token.value, this._factory.namedNode(token.prefix));
336
371
 
372
+ pathable = true;
337
373
  break;
338
374
  case '(':
339
375
  // In N3, a list can be a predicate
@@ -347,16 +383,26 @@ export default class N3Parser {
347
383
  this._subject = this._factory.blankNode(), null);
348
384
  return this._readBlankNodeHead;
349
385
  }
386
+ return this._error('Disallowed blank node as predicate', token);
387
+ case '{':
388
+ // In N3, a formula can be a predicate
389
+ if (this._n3Mode) {
390
+ this._saveContext('formula', this._graph, this._subject,
391
+ this._graph = this._factory.blankNode(), null);
392
+ return this._readSubject;
393
+ }
394
+ return this._readEntity(token);
350
395
  case 'blank':
351
396
  if (!this._n3Mode)
352
397
  return this._error('Disallowed blank node as predicate', token);
353
398
  default:
354
399
  if ((this._predicate = this._readEntity(token)) === undefined)
355
400
  return;
401
+ pathable = this._n3Mode;
356
402
  }
357
403
  this._validAnnotation = true;
358
404
  // The next token must be an object
359
- return this._readObject;
405
+ return pathable ? this._getPathReader(this._readObject, 'predicate') : this._readObject;
360
406
  }
361
407
 
362
408
  // ### `_readObject` reads a quad's object
@@ -389,7 +435,7 @@ export default class N3Parser {
389
435
  return this._error('Unexpected graph', token);
390
436
  this._saveContext('formula', this._graph, this._subject, this._predicate,
391
437
  this._graph = this._factory.blankNode());
392
- return this._readSubject;
438
+ return this._readInFormulaContext;
393
439
  case '<<(':
394
440
  this._saveContext('<<(', this._graph, this._subject, this._predicate, null);
395
441
  this._graph = null;
@@ -434,11 +480,41 @@ export default class N3Parser {
434
480
  if (parentParent.type === '<<') {
435
481
  return this._error('Unexpected compound blank node expression in reified triple', token);
436
482
  }
483
+ if (token.type === 'id')
484
+ return this._readIriPropertyListId;
437
485
  this._predicate = null;
438
486
  return this._readPredicate(token);
439
487
  }
440
488
  }
441
489
 
490
+ // ### `_readIriPropertyListId` replaces a property list's blank node with its IRI
491
+ _readIriPropertyListId(token) {
492
+ const iri = this._readEntity(token);
493
+ if (iri === undefined)
494
+ return;
495
+ if (iri.termType !== 'NamedNode')
496
+ return this._error(`Expected IRI after id but got ${token.type}`, token);
497
+
498
+ const placeholder = this._subject;
499
+ this._subject = iri;
500
+ const context = this._contextStack[this._contextStack.length - 1];
501
+ if (context.subject === placeholder)
502
+ context.subject = iri;
503
+ if (context.predicate === placeholder)
504
+ context.predicate = iri;
505
+ if (context.object === placeholder)
506
+ context.object = iri;
507
+ this._predicate = null;
508
+ return this._readIriPropertyListPredicate;
509
+ }
510
+
511
+ // ### `_readIriPropertyListPredicate` requires properties after an IRI property list ID
512
+ _readIriPropertyListPredicate(token) {
513
+ if (token.type === ';' || token.type === ']' || token.type === '.' || token.type === '}')
514
+ return this._error(`Expected predicate but got ${token.type}`, token);
515
+ return this._readPredicate(token);
516
+ }
517
+
442
518
  // ### `_readBlankNodeTail` reads the end of a blank node
443
519
  _readBlankNodeTail(token) {
444
520
  if (token.type !== ']')
@@ -456,7 +532,7 @@ export default class N3Parser {
456
532
  return this._getContextEndReader();
457
533
  // If the blank node was the predicate, continue reading the object
458
534
  else if (this._predicate !== null)
459
- return this._readObject;
535
+ return this._getPathReader(this._readObject, 'predicate');
460
536
  // If the blank node was the subject, continue reading the predicate
461
537
  else
462
538
  // If the blank node was empty, it could be a named graph label
@@ -529,7 +605,7 @@ export default class N3Parser {
529
605
  // Was this list the parent's predicate?
530
606
  else if (this._object === null) {
531
607
  // The next token is the object
532
- next = this._readObject;
608
+ next = this._getPathReader(this._readObject, 'predicate');
533
609
  // No list tail if this was an empty list
534
610
  if (this._predicate === this.RDF_NIL)
535
611
  return next;
@@ -585,7 +661,7 @@ export default class N3Parser {
585
661
  this._saveContext('formula', this._graph, list, this.RDF_FIRST,
586
662
  this._graph = item);
587
663
  this._subject = null;
588
- return this._readSubject;
664
+ return this._readInFormulaContext;
589
665
  case '<<(':
590
666
  this._saveContext('<<(', this._graph, null, null, null);
591
667
  this._graph = null;
@@ -706,13 +782,12 @@ export default class N3Parser {
706
782
  token = null;
707
783
  }
708
784
 
709
- if (component === 'subject') {
710
- // A subject literal implies N3 mode, so the literal might start a path
711
- const reader = this._getPathEndReader(token, this._readPredicateOrNamedGraph);
712
- return reader || this._readPredicateOrNamedGraph(token);
785
+ if (component === 'subject' || component === 'predicate') {
786
+ // A subject or predicate literal implies N3 mode, so it might start a path
787
+ const next = component === 'subject' ? this._readPredicateOrNamedGraph : this._readObject;
788
+ const reader = this._getPathEndReader(token, next, component);
789
+ return reader || next.call(this, token);
713
790
  }
714
- if (component === 'predicate')
715
- return token === null ? this._readObject : this._readObject(token);
716
791
  return this._completeObjectLiteralPost(token, listItem);
717
792
  }
718
793
 
@@ -739,16 +814,13 @@ export default class N3Parser {
739
814
  return this._readDirCode;
740
815
  }
741
816
 
742
- // A subject literal implies N3 mode, so it might start a path.
743
- if (component === 'subject') {
744
- const reader = this._getPathEndReader(completed.token, next);
745
- if (reader)
746
- return reader;
747
- }
817
+ // A subject or predicate literal implies N3 mode, so it might start a path.
818
+ const reader = this._getPathEndReader(completed.token, next, component);
819
+ if (reader)
820
+ return reader;
748
821
 
749
- // If the token was consumed as a datatype, continue with the next component;
750
- // otherwise, consume the token now
751
- return completed.token === null ? next : next.call(this, completed.token);
822
+ // Consume the non-path token now
823
+ return next.call(this, completed.token);
752
824
  }
753
825
 
754
826
  // Completes a literal in subject position
@@ -823,13 +895,18 @@ export default class N3Parser {
823
895
  if (empty && this._emptyFormulaAsTrue) {
824
896
  if (this._subject === formula)
825
897
  this._subject = this.N3_TRUE;
898
+ else if (this._predicate === formula)
899
+ this._predicate = this.N3_TRUE;
826
900
  else
827
901
  this._object = this.N3_TRUE;
828
902
  }
829
903
 
830
- // If the formula was the subject, continue reading the predicate.
831
- // If the formula was the object, read punctuation.
832
- return this._object === null ? this._readPredicate : this._getContextEndReader();
904
+ // Continue according to the formula's position in the enclosing statement
905
+ if (this._object !== null)
906
+ return this._getPathReader(this._getContextEndReader(), 'object');
907
+ if (this._predicate !== null)
908
+ return this._getPathReader(this._readObject, 'predicate');
909
+ return this._getPathReader(this._readPredicate, 'subject');
833
910
  }
834
911
 
835
912
  // ### `_readPunctuation` reads punctuation between quads or quad parts
@@ -848,7 +925,7 @@ export default class N3Parser {
848
925
  case '.':
849
926
  this._subject = null;
850
927
  this._tripleTerm = null;
851
- next = this._contextStack.length ? this._readSubject : this._readInTopContext;
928
+ next = this._getStatementReader();
852
929
  if (inversePredicate) this._inversePredicate = false;
853
930
  break;
854
931
  // Semicolon means the subject is shared; predicate and object are different
@@ -1021,12 +1098,12 @@ export default class N3Parser {
1021
1098
  // SPARQL-style declarations don't have punctuation
1022
1099
  if (this._sparqlStyle) {
1023
1100
  this._sparqlStyle = false;
1024
- return this._readInTopContext(token);
1101
+ return this._getStatementReader().call(this, token);
1025
1102
  }
1026
1103
 
1027
1104
  if (token.type !== '.')
1028
1105
  return this._error('Expected declaration to end with a dot', token);
1029
- return this._readInTopContext;
1106
+ return this._getStatementReader();
1030
1107
  }
1031
1108
 
1032
1109
  // Reads a list of quantified symbols from a @forSome or @forAll statement
@@ -1078,18 +1155,19 @@ export default class N3Parser {
1078
1155
  }
1079
1156
 
1080
1157
  // ### `_getPathReader` reads a potential path and then resumes with the given function
1081
- _getPathReader(afterPath) {
1158
+ _getPathReader(afterPath, position) {
1082
1159
  this._afterPath = afterPath;
1160
+ this._pathPosition = position || (this._predicate === null ? 'subject' : 'object');
1083
1161
  return this._readPath;
1084
1162
  }
1085
1163
 
1086
1164
  // ### `_getPathEndReader` continues reading after a term that might start a path,
1087
1165
  // given the pending token that follows the term (or `null` if it was consumed)
1088
- _getPathEndReader(token, afterPath) {
1166
+ _getPathEndReader(token, afterPath, position) {
1089
1167
  // Other pending tokens are not handled here
1090
1168
  if (token !== null && token.type !== '!' && token.type !== '^')
1091
1169
  return null;
1092
- const reader = this._getPathReader(afterPath);
1170
+ const reader = this._getPathReader(afterPath, position);
1093
1171
  // If no token is pending, wait for the next one; otherwise, consume it now
1094
1172
  return token === null ? reader : reader.call(this, token);
1095
1173
  }
@@ -1103,6 +1181,7 @@ export default class N3Parser {
1103
1181
  case '^': return this._readBackwardPath;
1104
1182
  // Not a path; resume reading where we left off
1105
1183
  default:
1184
+ const afterPath = this._afterPath;
1106
1185
  const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
1107
1186
  // If we were reading a list item, we still need to output it
1108
1187
  if (parent && parent.type === 'item') {
@@ -1113,7 +1192,9 @@ export default class N3Parser {
1113
1192
  // Output the list item
1114
1193
  this._emit(this._subject, this.RDF_FIRST, item, this._graph);
1115
1194
  }
1116
- return this._afterPath(token);
1195
+ this._afterPath = null;
1196
+ this._pathPosition = null;
1197
+ return afterPath.call(this, token);
1117
1198
  }
1118
1199
  }
1119
1200
 
@@ -1124,10 +1205,11 @@ export default class N3Parser {
1124
1205
  // The next token is the predicate
1125
1206
  if ((predicate = this._readEntity(token)) === undefined)
1126
1207
  return;
1127
- // If we were reading a subject, replace the subject by the path's object
1128
- if (this._predicate === null)
1208
+ // Replace the path expression with the generated object in its current position
1209
+ if (this._pathPosition === 'subject')
1129
1210
  subject = this._subject, this._subject = object;
1130
- // If we were reading an object, replace the subject by the path's object
1211
+ else if (this._pathPosition === 'predicate')
1212
+ subject = this._predicate, this._predicate = object;
1131
1213
  else
1132
1214
  subject = this._object, this._object = object;
1133
1215
  // Emit the path's current quad and read its next section
@@ -1142,10 +1224,11 @@ export default class N3Parser {
1142
1224
  // The next token is the predicate
1143
1225
  if ((predicate = this._readEntity(token)) === undefined)
1144
1226
  return;
1145
- // If we were reading a subject, replace the subject by the path's subject
1146
- if (this._predicate === null)
1227
+ // Replace the path expression with the generated subject in its current position
1228
+ if (this._pathPosition === 'subject')
1147
1229
  object = this._subject, this._subject = subject;
1148
- // If we were reading an object, replace the subject by the path's subject
1230
+ else if (this._pathPosition === 'predicate')
1231
+ object = this._predicate, this._predicate = subject;
1149
1232
  else
1150
1233
  object = this._object, this._object = subject;
1151
1234
  // Emit the path's current quad and read its next section