rdflib 2.2.32 → 2.2.33

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.
@@ -1,20 +1,20 @@
1
1
  /* @file Update Manager Class
2
2
  **
3
- ** 2007-07-15 originall sparl update module by Joe Presbrey <presbrey@mit.edu>
3
+ ** 2007-07-15 original SPARQL Update module by Joe Presbrey <presbrey@mit.edu>
4
4
  ** 2010-08-08 TimBL folded in Kenny's WEBDAV
5
- ** 2010-12-07 TimBL addred local file write code
5
+ ** 2010-12-07 TimBL added local file write code
6
6
  */
7
7
  import IndexedFormula from './store'
8
- import {docpart, join as uriJoin} from './uri'
9
- import Fetcher, {Options} from './fetcher'
8
+ import { docpart, join as uriJoin } from './uri'
9
+ import Fetcher, { Options } from './fetcher'
10
10
  import Namespace from './namespace'
11
11
  import Serializer from './serializer'
12
- import {isBlankNode, isStore} from './utils/terms'
12
+ import { isBlankNode, isStore } from './utils/terms'
13
13
  import * as Util from './utils-js'
14
14
  import Statement from './statement'
15
15
  import RDFlibNamedNode from './named-node'
16
- import {termValue} from './utils/termValue'
17
- import {BlankNode, NamedNode, Quad, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term,} from './tf-types'
16
+ import { termValue } from './utils/termValue'
17
+ import { BlankNode, NamedNode, Quad, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term, } from './tf-types'
18
18
 
19
19
  interface UpdateManagerFormula extends IndexedFormula {
20
20
  fetcher: Fetcher
@@ -45,7 +45,7 @@ export default class UpdateManager {
45
45
  /**
46
46
  * @param store - The quadstore to store data and metadata. Created if not passed.
47
47
  */
48
- constructor (store?: IndexedFormula) {
48
+ constructor(store?: IndexedFormula) {
49
49
  store = store || new IndexedFormula()
50
50
  if (store.updater) {
51
51
  throw new Error("You can't have two UpdateManagers for the same store")
@@ -70,65 +70,65 @@ export default class UpdateManager {
70
70
  this.patchControl = []
71
71
  }
72
72
 
73
- patchControlFor (doc: NamedNode) {
73
+ patchControlFor(doc: NamedNode) {
74
74
  if (!this.patchControl[doc.value]) {
75
75
  this.patchControl[doc.value] = []
76
76
  }
77
77
  return this.patchControl[doc.value]
78
78
  }
79
79
 
80
- isHttpUri(uri:string){
81
- return( uri.slice(0,4) === 'http' )
80
+ isHttpUri(uri: string) {
81
+ return (uri.slice(0, 4) === 'http')
82
82
  }
83
83
 
84
- /** Remove from the store HTTP authorization metadata
85
- * The editble function below relies on copies we have in the store
86
- * of the results of previous HTTP transactions. Howver, when
87
- * the user logs in, then that data misrepresents what would happen
88
- * if the user tried again.
89
- */
90
- flagAuthorizationMetadata (kb?: IndexedFormula) {
91
- if (!kb) {
92
- kb = this.store
93
- }
94
- const meta = kb.fetcher?.appNode
95
- const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), undefined, meta).map(st => st.subject)
96
- for (const request of requests) {
97
- const response = kb.any(request, this.ns.link('response'), null, meta) as Quad_Subject
98
- if (response !== undefined) { // ts
99
- kb.add(response, this.ns.link('outOfDate'), true as any, meta) // @@ Boolean is fine - fix types
84
+ /** Remove from the store HTTP authorization metadata
85
+ * The editable function below relies on copies we have in the store
86
+ * of the results of previous HTTP transactions. However, when
87
+ * the user logs in, then that data misrepresents what would happen
88
+ * if the user tried again.
89
+ */
90
+ flagAuthorizationMetadata(kb?: IndexedFormula) {
91
+ if (!kb) {
92
+ kb = this.store
93
+ }
94
+ const meta = kb.fetcher?.appNode
95
+ const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), undefined, meta).map(st => st.subject)
96
+ for (const request of requests) {
97
+ const response = kb.any(request, this.ns.link('response'), null, meta) as Quad_Subject
98
+ if (response !== undefined) { // ts
99
+ kb.add(response, this.ns.link('outOfDate'), true as any, meta) // @@ Boolean is fine - fix types
100
+ }
100
101
  }
101
102
  }
102
- }
103
103
 
104
- /**
105
- * Tests whether a file is editable.
106
- * If the file has a specific annotation that it is machine written,
107
- * for safety, it is editable (this doesn't actually check for write access)
108
- * If the file has wac-allow and accept patch headers, those are respected.
109
- * and local write access is determined by those headers.
110
- * This async version not only looks at past HTTP requests, it also makes new ones if necessary.
111
- *
112
- * @returns The method string SPARQL or DAV or
113
- * LOCALFILE or false if known, undefined if not known.
114
- */
115
- async checkEditable (uri: string | NamedNode, kb?: IndexedFormula): Promise<string | boolean | undefined> {
116
- if (!uri) {
117
- return false // Eg subject is bnode, no known doc to write to
118
- }
119
- if (!kb) {
120
- kb = this.store
121
- }
104
+ /**
105
+ * Tests whether a file is editable.
106
+ * If the file has a specific annotation that it is machine written,
107
+ * for safety, it is editable (this doesn't actually check for write access)
108
+ * If the file has wac-allow and accept patch headers, those are respected.
109
+ * and local write access is determined by those headers.
110
+ * This async version not only looks at past HTTP requests, it also makes new ones if necessary.
111
+ *
112
+ * @returns The method string N3PATCH or SPARQL or DAV or
113
+ * LOCALFILE or false if known, undefined if not known.
114
+ */
115
+ async checkEditable(uri: string | NamedNode, kb?: IndexedFormula): Promise<string | boolean | undefined> {
116
+ if (!uri) {
117
+ return false // Eg subject is bnode, no known doc to write to
118
+ }
119
+ if (!kb) {
120
+ kb = this.store
121
+ }
122
122
 
123
- const initial = this.editable(uri, kb)
124
- if (initial !== undefined) {
125
- return initial
126
- }
127
- await kb.fetcher?.load(uri)
128
- const final = this.editable(uri, kb)
129
- // console.log(`Loaded ${uri} just to check editable, result: ${final}.`)
130
- return final
131
- }
123
+ const initial = this.editable(uri, kb)
124
+ if (initial !== undefined) {
125
+ return initial
126
+ }
127
+ await kb.fetcher?.load(uri)
128
+ const final = this.editable(uri, kb)
129
+ // console.log(`Loaded ${uri} just to check editable, result: ${final}.`)
130
+ return final
131
+ }
132
132
  /**
133
133
  * Tests whether a file is editable.
134
134
  * If the file has a specific annotation that it is machine written,
@@ -140,7 +140,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
140
140
  * @returns The method string SPARQL or DAV or
141
141
  * LOCALFILE or false if known, undefined if not known.
142
142
  */
143
- editable (uri: string | NamedNode, kb?: IndexedFormula): string | boolean | undefined {
143
+ editable(uri: string | NamedNode, kb?: IndexedFormula): string | boolean | undefined {
144
144
  if (!uri) {
145
145
  return false // Eg subject is bnode, no known doc to write to
146
146
  }
@@ -149,11 +149,11 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
149
149
  }
150
150
  uri = termValue(uri)
151
151
 
152
- if ( !this.isHttpUri(uri as string) ) {
152
+ if (!this.isHttpUri(uri as string)) {
153
153
  if (kb.holds(
154
- kb.rdfFactory.namedNode(uri),
155
- kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
156
- kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#MachineEditableDocument'))) {
154
+ kb.rdfFactory.namedNode(uri),
155
+ kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
156
+ kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#MachineEditableDocument'))) {
157
157
  return 'LOCALFILE'
158
158
  }
159
159
  }
@@ -163,7 +163,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
163
163
  const meta = kb.fetcher?.appNode
164
164
  // const kb = s
165
165
 
166
- // @ts-ignore passes a string to kb.each, which expects a term. Should this work?
166
+ // @ts-ignore passes a string to kb.each, which expects a term. Should this work?
167
167
  var requests = kb.each(undefined, this.ns.link('requestedURI'), docpart(uri), meta)
168
168
  var method: string
169
169
  for (var r = 0; r < requests.length; r++) {
@@ -179,7 +179,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
179
179
  if (wacAllow) {
180
180
  for (var bit of wacAllow.split(',')) {
181
181
  var lr = bit.split('=')
182
- if (lr[0].includes('user') && !lr[1].includes('write') && !lr[1].includes('append') ) {
182
+ if (lr[0].includes('user') && !lr[1].includes('write') && !lr[1].includes('append')) {
183
183
  // console.log(' editable? excluded by WAC-Allow: ', wacAllow)
184
184
  return false
185
185
  }
@@ -189,6 +189,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
189
189
  if (acceptPatch.length) {
190
190
  for (let i = 0; i < acceptPatch.length; i++) {
191
191
  method = acceptPatch[i].value.trim()
192
+ if (method.indexOf('text/n3') >= 0) return 'N3PATCH'
192
193
  if (method.indexOf('application/sparql-update') >= 0) return 'SPARQL'
193
194
  if (method.indexOf('application/sparql-update-single-match') >= 0) return 'SPARQL'
194
195
  }
@@ -206,8 +207,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
206
207
  }
207
208
  }
208
209
 
209
- if ( !this.isHttpUri(uri as string) ) {
210
- if( !wacAllow ) return false;
210
+ if (!this.isHttpUri(uri as string)) {
211
+ if (!wacAllow) return false;
211
212
  else return 'LOCALFILE';
212
213
  }
213
214
 
@@ -237,13 +238,15 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
237
238
  return undefined // We don't know (yet) as we haven't had a response (yet)
238
239
  }
239
240
 
240
- anonymize (obj) {
241
- return (obj.toNT().substr(0, 2) === '_:' && this.mentioned(obj))
241
+ anonymize(obj) {
242
+ let anonymized = (obj.toNT().substr(0, 2) === '_:' && this.mentioned(obj))
242
243
  ? '?' + obj.toNT().substr(2)
243
- : obj.toNT()
244
+ : obj.toNT();
245
+
246
+ return anonymized;
244
247
  }
245
248
 
246
- anonymizeNT (stmt: Quad) {
249
+ anonymizeNT(stmt: Quad) {
247
250
  return this.anonymize(stmt.subject) + ' ' +
248
251
  this.anonymize(stmt.predicate) + ' ' +
249
252
  this.anonymize(stmt.object) + ' .'
@@ -257,7 +260,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
257
260
  * Returns a list of all bnodes occurring in a statement
258
261
  * @private
259
262
  */
260
- statementBnodes (st: Quad): BlankNode[] {
263
+ statementBnodes(st: Quad): BlankNode[] {
261
264
  return [st.subject, st.predicate, st.object].filter(function (x) {
262
265
  return isBlankNode(x)
263
266
  }) as BlankNode[]
@@ -267,7 +270,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
267
270
  * Returns a list of all bnodes occurring in a list of statements
268
271
  * @private
269
272
  */
270
- statementArrayBnodes (sts: ReadonlyArray<Quad>) {
273
+ statementArrayBnodes(sts: ReadonlyArray<Quad>) {
271
274
  var bnodes: BlankNode[] = []
272
275
  for (let i = 0; i < sts.length; i++) {
273
276
  bnodes = bnodes.concat(this.statementBnodes(sts[i]))
@@ -286,7 +289,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
286
289
  * Makes a cached list of [Inverse-]Functional properties
287
290
  * @private
288
291
  */
289
- cacheIfps () {
292
+ cacheIfps() {
290
293
  this.ifps = {}
291
294
  var a = this.store.each(undefined, this.ns.rdf('type'),
292
295
  this.ns.owl('InverseFunctionalProperty'))
@@ -304,7 +307,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
304
307
  * Returns a context to bind a given node, up to a given depth
305
308
  * @private
306
309
  */
307
- bnodeContext2 (x, source, depth) {
310
+ bnodeContext2(x, source, depth) {
308
311
  // Return a list of statements which indirectly identify a node
309
312
  // Depth > 1 if try further indirection.
310
313
  // Return array of statements (possibly empty), or null if failure
@@ -315,12 +318,12 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
315
318
  if (this.fps[sts[i].predicate.value]) {
316
319
  y = sts[i].subject
317
320
  if (!y.isBlank) {
318
- return [ sts[i] ]
321
+ return [sts[i]]
319
322
  }
320
323
  if (depth) {
321
324
  res = this.bnodeContext2(y, source, depth - 1)
322
325
  if (res) {
323
- return res.concat([ sts[i] ])
326
+ return res.concat([sts[i]])
324
327
  }
325
328
  }
326
329
  }
@@ -331,12 +334,12 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
331
334
  if (this.ifps[sts[i].predicate.value]) {
332
335
  y = sts[i].object
333
336
  if (!y.isBlank) {
334
- return [ sts[i] ]
337
+ return [sts[i]]
335
338
  }
336
339
  if (depth) {
337
340
  res = this.bnodeContext2(y, source, depth - 1)
338
341
  if (res) {
339
- return res.concat([ sts[i] ])
342
+ return res.concat([sts[i]])
340
343
  }
341
344
  }
342
345
  }
@@ -348,7 +351,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
348
351
  * Returns the smallest context to bind a given single bnode
349
352
  * @private
350
353
  */
351
- bnodeContext1 (x, source) {
354
+ bnodeContext1(x, source) {
352
355
  // Return a list of statements which indirectly identify a node
353
356
  // Breadth-first
354
357
  for (var depth = 0; depth < 3; depth++) { // Try simple first
@@ -363,7 +366,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
363
366
  /**
364
367
  * @private
365
368
  */
366
- mentioned (x) {
369
+ mentioned(x) {
367
370
  return this.store.statementsMatching(x, null, null, null).length !== 0 || // Don't pin fresh bnodes
368
371
  this.store.statementsMatching(null, x).length !== 0 ||
369
372
  this.store.statementsMatching(null, null, x).length !== 0
@@ -372,7 +375,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
372
375
  /**
373
376
  * @private
374
377
  */
375
- bnodeContext (bnodes, doc) {
378
+ bnodeContext(bnodes, doc) {
376
379
  var context = []
377
380
  if (bnodes.length) {
378
381
  this.cacheIfps()
@@ -389,7 +392,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
389
392
  * Returns the best context for a single statement
390
393
  * @private
391
394
  */
392
- statementContext (st: Quad) {
395
+ statementContext(st: Quad) {
393
396
  var bnodes = this.statementBnodes(st)
394
397
  return this.bnodeContext(bnodes, st.graph)
395
398
  }
@@ -397,7 +400,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
397
400
  /**
398
401
  * @private
399
402
  */
400
- contextWhere (context) {
403
+ contextWhere(context) {
401
404
  var updater = this
402
405
  return (!context || context.length === 0)
403
406
  ? ''
@@ -410,7 +413,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
410
413
  /**
411
414
  * @private
412
415
  */
413
- fire (
416
+ fire(
414
417
  uri: string,
415
418
  query: string,
416
419
  callbackFunction: CallBackFunction,
@@ -424,7 +427,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
424
427
  // console.log('UpdateManager: sending update to <' + uri + '>')
425
428
 
426
429
  options.noMeta = true;
427
- options.contentType = 'application/sparql-update';
430
+ options.contentType = options.contentType || 'application/sparql-update';
428
431
  options.body = query;
429
432
 
430
433
  return this.store.fetcher.webOperation('PATCH', uri, options)
@@ -447,7 +450,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
447
450
  })
448
451
  }
449
452
 
450
- // ARE THESE THEE FUNCTIONS USED? DEPROCATE?
453
+ // ARE THESE THREE FUNCTIONS USED? DEPRECATE?
451
454
 
452
455
  /** return a statemnet updating function
453
456
  *
@@ -455,7 +458,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
455
458
  * It returns an object which includes
456
459
  * function which can be used to change the object of the statement.
457
460
  */
458
- update_statement (statement: Quad) {
461
+ update_statement(statement: Quad) {
459
462
  if (statement && !statement.graph) {
460
463
  return
461
464
  }
@@ -483,7 +486,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
483
486
  }
484
487
  }
485
488
 
486
- insert_statement (st: Quad, callbackFunction: CallBackFunction): void {
489
+ insert_statement(st: Quad, callbackFunction: CallBackFunction): void {
487
490
  var st0 = st instanceof Array ? st[0] : st
488
491
  var query = this.contextWhere(this.statementContext(st0))
489
492
 
@@ -501,7 +504,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
501
504
  this.fire(st0.graph.value, query, callbackFunction)
502
505
  }
503
506
 
504
- delete_statement (st: Quad | Quad[], callbackFunction: CallBackFunction): void {
507
+ delete_statement(st: Quad | Quad[], callbackFunction: CallBackFunction): void {
505
508
  var st0 = st instanceof Array ? st[0] : st
506
509
  var query = this.contextWhere(this.statementContext(st0))
507
510
 
@@ -519,7 +522,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
519
522
  this.fire(st0.graph.value, query, callbackFunction)
520
523
  }
521
524
 
522
- /// //////////////////////
525
+ /// //////////////////////
523
526
 
524
527
  /**
525
528
  * Requests a now or future action to refresh changes coming downstream
@@ -530,7 +533,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
530
533
  * @param doc
531
534
  * @param action
532
535
  */
533
- requestDownstreamAction (doc: NamedNode, action): void {
536
+ requestDownstreamAction(doc: NamedNode, action): void {
534
537
  var control = this.patchControlFor(doc)
535
538
  if (!control.pendingUpstream) {
536
539
  action(doc)
@@ -549,18 +552,18 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
549
552
  * We want to start counting websocket notifications
550
553
  * to distinguish the ones from others from our own.
551
554
  */
552
- clearUpstreamCount (doc: NamedNode): void {
555
+ clearUpstreamCount(doc: NamedNode): void {
553
556
  var control = this.patchControlFor(doc)
554
557
  control.upstreamCount = 0
555
558
  }
556
559
 
557
- getUpdatesVia (doc: NamedNode): string | null {
560
+ getUpdatesVia(doc: NamedNode): string | null {
558
561
  var linkHeaders = this.store.fetcher.getHeader(doc, 'updates-via')
559
562
  if (!linkHeaders || !linkHeaders.length) return null
560
563
  return linkHeaders[0].trim()
561
564
  }
562
565
 
563
- addDownstreamChangeListener (doc: NamedNode, listener): void {
566
+ addDownstreamChangeListener(doc: NamedNode, listener): void {
564
567
  var control = this.patchControlFor(doc)
565
568
  if (!control.downstreamChangeListeners) { control.downstreamChangeListeners = [] }
566
569
  control.downstreamChangeListeners.push(listener)
@@ -569,7 +572,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
569
572
  })
570
573
  }
571
574
 
572
- reloadAndSync (doc: NamedNode): void {
575
+ reloadAndSync(doc: NamedNode): void {
573
576
  var control = this.patchControlFor(doc)
574
577
  var updater = this
575
578
 
@@ -591,7 +594,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
591
594
  }
592
595
  }
593
596
  control.reloading = false
594
- if (control.outOfDate){
597
+ if (control.outOfDate) {
595
598
  // console.log(' Extra reload because of extra update.')
596
599
  control.outOfDate = false
597
600
  tryReload()
@@ -631,7 +634,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
631
634
  *
632
635
  * @returns {boolean}
633
636
  */
634
- setRefreshHandler (doc: NamedNode, handler): boolean {
637
+ setRefreshHandler(doc: NamedNode, handler): boolean {
635
638
  let wssURI = this.getUpdatesVia(doc) // relative
636
639
  // var kb = this.store
637
640
  var theHandler = handler
@@ -672,7 +675,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
672
675
  var control = self.patchControlFor(doc)
673
676
  control.upstreamCount = 0
674
677
 
675
- socket.onerror = function onerror (err: Error) {
678
+ socket.onerror = function onerror(err: Error) {
676
679
  // console.log('Error on Websocket:', err)
677
680
  }
678
681
 
@@ -737,8 +740,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
737
740
  const thisUpdater = this
738
741
  const uniqueDocs: Array<NamedNode> = []
739
742
  docs.forEach(doc => {
740
- if (!uniqueDocs.find(uniqueDoc => uniqueDoc.equals(doc))) uniqueDocs.push(doc as NamedNode)
741
- })
743
+ if (!uniqueDocs.find(uniqueDoc => uniqueDoc.equals(doc))) uniqueDocs.push(doc as NamedNode)
744
+ })
742
745
  const updates = uniqueDocs.map(doc =>
743
746
  thisUpdater.update(deletions.filter(st => st.why.equals(doc)),
744
747
  insertions.filter(st => st.why.equals(doc))))
@@ -749,7 +752,103 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
749
752
  }
750
753
 
751
754
  /**
752
- * This high-level function updates the local store iff the web is changed successfully.
755
+ * @private
756
+ *
757
+ * This helper function constructs SPARQL Update query from resolved arguments.
758
+ *
759
+ * @param ds: deletions array.
760
+ * @param is: insertions array.
761
+ * @param bnodes_context: Additional context to uniquely identify any blank nodes.
762
+ */
763
+ constructSparqlUpdateQuery(
764
+ ds: ReadonlyArray<Statement>,
765
+ is: ReadonlyArray<Statement>,
766
+ bnodes_context,
767
+ ): string {
768
+ var whereClause = this.contextWhere(bnodes_context)
769
+ var query = ''
770
+ if (whereClause.length) { // Is there a WHERE clause?
771
+ if (ds.length) {
772
+ query += 'DELETE { '
773
+ for (let i = 0; i < ds.length; i++) {
774
+ query += this.anonymizeNT(ds[i]) + '\n'
775
+ }
776
+ query += ' }\n'
777
+ }
778
+ if (is.length) {
779
+ query += 'INSERT { '
780
+ for (let i = 0; i < is.length; i++) {
781
+ query += this.anonymizeNT(is[i]) + '\n'
782
+ }
783
+ query += ' }\n'
784
+ }
785
+ query += whereClause
786
+ } else { // no where clause
787
+ if (ds.length) {
788
+ query += 'DELETE DATA { '
789
+ for (let i = 0; i < ds.length; i++) {
790
+ query += this.anonymizeNT(ds[i]) + '\n'
791
+ }
792
+ query += ' } \n'
793
+ }
794
+ if (is.length) {
795
+ if (ds.length) query += ' ; '
796
+ query += 'INSERT DATA { '
797
+ for (let i = 0; i < is.length; i++) {
798
+ query += this.nTriples(is[i]) + '\n'
799
+ }
800
+ query += ' }\n'
801
+ }
802
+ }
803
+ return query;
804
+ }
805
+
806
+ /**
807
+ * @private
808
+ *
809
+ * This helper function constructs n3-patch query from resolved arguments.
810
+ *
811
+ * @param ds: deletions array.
812
+ * @param is: insertions array.
813
+ * @param bnodes_context: Additional context to uniquely identify any blanknodes.
814
+ */
815
+ constructN3PatchQuery(
816
+ ds: ReadonlyArray<Statement>,
817
+ is: ReadonlyArray<Statement>,
818
+ bnodes_context,
819
+ ): string {
820
+ var query = `
821
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
822
+ @prefix ex: <http://www.example.org/terms#>.
823
+
824
+ _:patch
825
+ `;
826
+ // If bnode context is non trivial, express it as ?conditions formula.
827
+ if (bnodes_context && bnodes_context.length > 0) {
828
+ query += `
829
+ solid:where {
830
+ ${bnodes_context.map((x) => this.anonymizeNT(x)).join('\n ')}
831
+ };`
832
+ }
833
+ if (ds.length > 0) {
834
+ query += `
835
+ solid:deletes {
836
+ ${ds.map((x) => this.anonymizeNT(x)).join('\n ')}
837
+ };`
838
+ }
839
+ if (is.length > 0) {
840
+ query += `
841
+ solid:inserts {
842
+ ${is.map((x) => this.anonymizeNT(x)).join('\n ')}
843
+ };`
844
+ }
845
+ query += " a solid:InsertDeletePatch .\n"
846
+
847
+ return query;
848
+ }
849
+
850
+ /**
851
+ * This high-level function updates the local store if the web is changed successfully.
753
852
  * Deletions, insertions may be undefined or single statements or lists or formulae (may contain bnodes which can be indirectly identified by a where clause).
754
853
  * The `why` property of each statement must be the same and give the web document to be updated.
755
854
  * @param deletions - Statement or statements to be deleted.
@@ -759,16 +858,16 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
759
858
  * @param options - Options for the fetch call
760
859
  */
761
860
  update(
762
- deletions: ReadonlyArray<Statement>,
763
- insertions: ReadonlyArray<Statement>,
764
- callback?: (
765
- uri: string | undefined | null,
766
- success: boolean,
767
- errorBody?: string,
768
- response?: Response | Error
769
- ) => void,
770
- secondTry?: boolean,
771
- options: Options = {}
861
+ deletions: ReadonlyArray<Statement>,
862
+ insertions: ReadonlyArray<Statement>,
863
+ callback?: (
864
+ uri: string | undefined | null,
865
+ success: boolean,
866
+ errorBody?: string,
867
+ response?: Response | Error
868
+ ) => void,
869
+ secondTry?: boolean,
870
+ options: Options = {}
772
871
  ): void | Promise<void> {
773
872
  if (!callback) {
774
873
  var thisUpdater = this
@@ -787,10 +886,10 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
787
886
  var kb = this.store
788
887
  var ds = !deletions ? []
789
888
  : isStore(deletions) ? deletions.statements
790
- : deletions instanceof Array ? deletions : [ deletions ]
889
+ : deletions instanceof Array ? deletions : [deletions]
791
890
  var is = !insertions ? []
792
891
  : isStore(insertions) ? insertions.statements
793
- : insertions instanceof Array ? insertions : [ insertions ]
892
+ : insertions instanceof Array ? insertions : [insertions]
794
893
  if (!(ds instanceof Array)) {
795
894
  throw new Error('Type Error ' + (typeof ds) + ': ' + ds)
796
895
  }
@@ -832,70 +931,42 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
832
931
  })
833
932
  })
834
933
 
835
- var protocol = this.editable(doc.value, kb)
934
+ var protocol = this.editable(doc.value, kb);
935
+
836
936
  if (protocol === false) {
837
937
  throw new Error('Update: Can\'t make changes in uneditable ' + doc)
838
938
  }
839
939
  if (protocol === undefined) { // Not enough metadata
840
940
  if (secondTry) {
841
- throw new Error('Update: Loaded ' + doc + "but stil can't figure out what editing protcol it supports.")
941
+ throw new Error('Update: Loaded ' + doc + "but still can't figure out what editing protocol it supports.")
842
942
  }
843
943
  // console.log(`Update: have not loaded ${doc} before: loading now...`);
844
944
  (this.store.fetcher.load(doc as NamedNode) as Promise<Response>).then(response => {
845
945
  this.update(deletions, insertions, callback, true, options)
846
946
  }, err => {
847
- if (err.response.status === 404) { // nonexistent files are fine
848
- this.update(deletions, insertions, callback, true, options)
849
- } else {
850
- throw new Error(`Update: Can't get updatability status ${doc} before patching: ${err}`)
851
- }
947
+ if (err.response.status === 404) { // nonexistent files are fine
948
+ this.update(deletions, insertions, callback, true, options)
949
+ } else {
950
+ throw new Error(`Update: Can't get updatability status ${doc} before patching: ${err}`)
951
+ }
852
952
  })
853
953
  return
854
- } else if ((protocol as string).indexOf('SPARQL') >= 0) {
954
+ } else if ((protocol as string).indexOf('SPARQL') >= 0 || (protocol as string).indexOf('N3PATCH') >= 0) {
955
+ var isSparql = (protocol as string).indexOf('SPARQL') >= 0
956
+
855
957
  var bnodes: BlankNode[] = []
856
958
  // change ReadOnly type to Mutable type
857
959
  type Mutable<Type> = {
858
960
  -readonly [Key in keyof Type]: Type[Key];
859
961
  }
860
-
962
+
861
963
  if (ds.length) bnodes = this.statementArrayBnodes(ds as Mutable<typeof ds>)
862
964
  if (is.length) bnodes = bnodes.concat(this.statementArrayBnodes(is as Mutable<typeof is>))
863
965
  var context = this.bnodeContext(bnodes, doc)
864
- var whereClause = this.contextWhere(context)
865
- var query = ''
866
- if (whereClause.length) { // Is there a WHERE clause?
867
- if (ds.length) {
868
- query += 'DELETE { '
869
- for (let i = 0; i < ds.length; i++) {
870
- query += this.anonymizeNT(ds[i]) + '\n'
871
- }
872
- query += ' }\n'
873
- }
874
- if (is.length) {
875
- query += 'INSERT { '
876
- for (let i = 0; i < is.length; i++) {
877
- query += this.anonymizeNT(is[i]) + '\n'
878
- }
879
- query += ' }\n'
880
- }
881
- query += whereClause
882
- } else { // no where clause
883
- if (ds.length) {
884
- query += 'DELETE DATA { '
885
- for (let i = 0; i < ds.length; i++) {
886
- query += this.anonymizeNT(ds[i]) + '\n'
887
- }
888
- query += ' } \n'
889
- }
890
- if (is.length) {
891
- if (ds.length) query += ' ; '
892
- query += 'INSERT DATA { '
893
- for (let i = 0; i < is.length; i++) {
894
- query += this.nTriples(is[i]) + '\n'
895
- }
896
- query += ' }\n'
897
- }
898
- }
966
+
967
+ var query = isSparql ? this.constructSparqlUpdateQuery(ds, is, context) : this.constructN3PatchQuery(ds, is, context);
968
+ options.contentType = isSparql ? 'application/sparql-update' : 'text/n3'
969
+
899
970
  // Track pending upstream patches until they have finished their callbackFunction
900
971
  control.pendingUpstream = control.pendingUpstream ? control.pendingUpstream + 1 : 1
901
972
  if ('upstreamCount' in control) {
@@ -953,7 +1024,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
953
1024
  }
954
1025
  }
955
1026
 
956
- updateDav (
1027
+ updateDav(
957
1028
  doc: Quad_Subject,
958
1029
  ds,
959
1030
  is,
@@ -971,7 +1042,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
971
1042
  if (!response) {
972
1043
  return null // throw "No record HTTP GET response for document: "+doc
973
1044
  }
974
- var contentType = (kb.the(response, this.ns.httph('content-type'))as Term).value
1045
+ var contentType = (kb.the(response, this.ns.httph('content-type')) as Term).value
975
1046
 
976
1047
  // prepare contents of revised document
977
1048
  let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy!
@@ -1024,7 +1095,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1024
1095
  * @param callbackFunction
1025
1096
  * @param options
1026
1097
  */
1027
- updateLocalFile (doc: NamedNode, ds, is, callbackFunction, options: Options = {}): void {
1098
+ updateLocalFile(doc: NamedNode, ds, is, callbackFunction, options: Options = {}): void {
1028
1099
  const kb = this.store
1029
1100
  // console.log('Writing back to local file\n')
1030
1101
 
@@ -1032,10 +1103,10 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1032
1103
  let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy!
1033
1104
 
1034
1105
  for (let i = 0; i < ds.length; i++) {
1035
- Util.RDFArrayRemove(newSts, ds[ i ])
1106
+ Util.RDFArrayRemove(newSts, ds[i])
1036
1107
  }
1037
1108
  for (let i = 0; i < is.length; i++) {
1038
- newSts.push(is[ i ])
1109
+ newSts.push(is[i])
1039
1110
  }
1040
1111
  // serialize to the appropriate format
1041
1112
  var dot = doc.value.lastIndexOf('.')
@@ -1044,7 +1115,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1044
1115
  }
1045
1116
  var ext = doc.value.slice(dot + 1)
1046
1117
 
1047
- let contentType = Fetcher.CONTENT_TYPE_BY_EXT[ ext ]
1118
+ let contentType = Fetcher.CONTENT_TYPE_BY_EXT[ext]
1048
1119
  if (!contentType) {
1049
1120
  throw new Error('File extension .' + ext + ' not supported for data write')
1050
1121
  }
@@ -1052,8 +1123,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1052
1123
  options.body = this.serialize(doc.value, newSts, contentType);
1053
1124
  options.contentType = contentType;
1054
1125
 
1055
- kb.fetcher.webOperation('PUT', doc.value, options).then( (response)=>{
1056
- if(!response.ok) return callbackFunction(doc.value,false,response.error)
1126
+ kb.fetcher.webOperation('PUT', doc.value, options).then((response) => {
1127
+ if (!response.ok) return callbackFunction(doc.value, false, response.error)
1057
1128
  for (let i = 0; i < ds.length; i++) {
1058
1129
  kb.remove(ds[i]);
1059
1130
  }
@@ -1069,7 +1140,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1069
1140
  *
1070
1141
  * @returns {string}
1071
1142
  */
1072
- serialize (uri: string, data: string | Quad[], contentType: string): string {
1143
+ serialize(uri: string, data: string | Quad[], contentType: string): string {
1073
1144
  const kb = this.store
1074
1145
  let documentString
1075
1146
 
@@ -1152,7 +1223,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1152
1223
  * @param doc {RDFlibNamedNode}
1153
1224
  * @param callbackFunction
1154
1225
  */
1155
- reload (
1226
+ reload(
1156
1227
  kb: IndexedFormula,
1157
1228
  doc: docReloadType,
1158
1229
  callbackFunction: (ok: boolean, message?: string, response?: Error | Response) => {} | void
@@ -1172,8 +1243,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1172
1243
  //@ts-ignore Where does onErrorWasCalled come from?
1173
1244
  } else if (response.onErrorWasCalled || response.status !== 200) {
1174
1245
  // console.log(' Non-HTTP error reloading data! onErrorWasCalled=' +
1175
- //@ts-ignore Where does onErrorWasCalled come from?
1176
- // response.onErrorWasCalled + ' status: ' + response.status)
1246
+ //@ts-ignore Where does onErrorWasCalled come from?
1247
+ // response.onErrorWasCalled + ' status: ' + response.status)
1177
1248
  callbackFunction(false, 'Non-HTTP error reloading data: ' + body, response)
1178
1249
  } else {
1179
1250
  var elapsedTimeMs = Date.now() - startTime
@@ -1185,8 +1256,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
1185
1256
  doc.reloadTimeCount += 1
1186
1257
 
1187
1258
  // console.log(' Fetch took ' + elapsedTimeMs + 'ms, av. of ' +
1188
- // doc.reloadTimeCount + ' = ' +
1189
- // (doc.reloadTimeTotal / doc.reloadTimeCount) + 'ms.')
1259
+ // doc.reloadTimeCount + ' = ' +
1260
+ // (doc.reloadTimeTotal / doc.reloadTimeCount) + 'ms.')
1190
1261
 
1191
1262
  callbackFunction(true)
1192
1263
  }