Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
JsonLdDocument.cs
1using System;
3using System.Net.Http;
4using System.Threading.Tasks;
8
10{
17 {
18 private static readonly Cache<string, KeyValuePair<DateTimeOffset, Dictionary<string, object>>> contextObjects =
19 new Cache<string, KeyValuePair<DateTimeOffset, Dictionary<string, object>>>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromHours(1));
20
21 private readonly object parsedJson;
22 private readonly BlankNodeIdMode blankNodeIdMode;
23 private readonly string text;
24 private readonly string blankNodeIdPrefix;
25 private DateTimeOffset? date = null;
26 private int blankNodeIndex = 0;
27
35 private JsonLdDocument(object Doc, string Text, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
36 {
37 this.parsedJson = Doc;
38 this.text = Text;
39 this.blankNodeIdMode = BlankNodeIdMode;
40 this.blankNodeIdPrefix = BlankNodeIdPrefix;
41 }
42
47 public static Task<JsonLdDocument> CreateAsync(string Text)
48 {
49 return CreateAsync(JSON.Parse(Text), Text, null);
50 }
51
57 public static Task<JsonLdDocument> CreateAsync(string Text, Uri BaseUri)
58 {
59 return CreateAsync(JSON.Parse(Text), Text, BaseUri, "n");
60 }
61
68 public static Task<JsonLdDocument> CreateAsync(string Text, Uri BaseUri, string BlankNodeIdPrefix)
69 {
70 return CreateAsync(JSON.Parse(Text), Text, BaseUri, BlankNodeIdPrefix, BlankNodeIdMode.Sequential);
71 }
72
80 public static Task<JsonLdDocument> CreateAsync(string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
81 {
82 return CreateAsync(JSON.Parse(Text), Text, BaseUri, BlankNodeIdPrefix, BlankNodeIdMode);
83 }
84
90 public static Task<JsonLdDocument> CreateAsync(object Doc, string Text)
91 {
92 return CreateAsync(Doc, Text, null);
93 }
94
101 public static Task<JsonLdDocument> CreateAsync(object Doc, string Text, Uri BaseUri)
102 {
103 return CreateAsync(Doc, Text, BaseUri, "n");
104 }
105
113 public static Task<JsonLdDocument> CreateAsync(object Doc, string Text, Uri BaseUri, string BlankNodeIdPrefix)
114 {
115 return CreateAsync(Doc, Text, BaseUri, BlankNodeIdPrefix, BlankNodeIdMode.Sequential);
116 }
117
126 public static async Task<JsonLdDocument> CreateAsync(object Doc, string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
127 {
128 JsonLdDocument Result = new JsonLdDocument(Doc, Text, BlankNodeIdPrefix, BlankNodeIdMode);
129 await Result.Parse(Doc, null, BaseUri, Result.CreateBlankNode());
130 return Result;
131 }
132
133 private BlankNode CreateBlankNode()
134 {
135 if (this.blankNodeIdMode == BlankNodeIdMode.Guid)
136 return new BlankNode(this.blankNodeIdPrefix + Guid.NewGuid().ToString());
137 else
138 return new BlankNode(this.blankNodeIdPrefix + (++this.blankNodeIndex).ToString());
139 }
140
141 private UriNode CreateUriNode(string Reference, Uri BaseUri, JsonLdContext Context)
142 {
143 return new UriNode(this.CreateUri(Reference, BaseUri, Context), Reference);
144 }
145
146 private UriNode CreateUriNode(string Reference, Uri BaseUri, string ShortName, JsonLdContext Context)
147 {
148 return new UriNode(this.CreateUri(Reference, BaseUri, Context), ShortName);
149 }
150
151 private Uri CreateUri(string Reference, Uri BaseUri, JsonLdContext Context)
152 {
153 ExpandPrefixes(ref Reference, Context);
154 BaseUri = Context?.Base ?? BaseUri;
155
156 if (BaseUri is null)
157 {
158 if (Uri.TryCreate(Reference, UriKind.Absolute, out Uri URI))
159 return URI;
160 else
161 throw this.ParsingException("Invalid URI: " + Reference);
162 }
163 else
164 {
165 if (string.IsNullOrEmpty(Reference))
166 return BaseUri;
167 else if (Uri.TryCreate(BaseUri, Reference, out Uri URI))
168 return URI;
169 else
170 throw this.ParsingException("Invalid URI: " + Reference + " (base: " + BaseUri.ToString() + ")");
171 }
172 }
173
174 private Exception ParsingException(string Message)
175 {
176 return new Exception(Message);
177 }
178
179 private async Task<ISemanticElement> Parse(object Doc, JsonLdContext Context, Uri BaseUri, ISemanticElement Subject)
180 {
181 ISemanticElement ReturnValue = null;
182
183 if (Doc is Dictionary<string, object> DocObj)
184 {
185 string Name;
186 object Value;
187 ISemanticElement ParsedValue;
188 Uri ParsedUri;
189 string s;
190
191 foreach (KeyValuePair<string, object> P in DocObj)
192 {
193 Name = P.Key;
194 Value = P.Value;
195
196 if (!TryGetContextName(Name, Context, out object ContextValue))
197 ContextValue = null;
198 else if (ContextValue is string Alias)
199 Name = Alias;
200
201 if (Name.StartsWith("@"))
202 {
203 switch (Name)
204 {
205 case "@context":
206 Context = await this.GetContext(Value, BaseUri, Context);
207 break;
208
209 case "@id":
210 s = Value as string
211 ?? throw this.ParsingException("Unsupported @id value: " + Value.ToString());
212
213 Subject = this.CreateUriNode(s, BaseUri, Context);
214 break;
215
216 case "@type":
217 Context = await this.AddType(Subject, Value, BaseUri, Context);
218 break;
219
220 case "@graph":
221 await this.Parse(P.Value, Context, BaseUri, Subject);
222 break;
223
224 case "@version":
225 break;
226
227 case "@base":
228 s = Value as string
229 ?? throw this.ParsingException("Unsupported @base value: " + Value.ToString());
230
231 BaseUri = this.CreateUri(s, BaseUri, Context);
232 break;
233
234 case "@value":
235 ReturnValue = await this.ParseValue(BaseUri, Value, Context);
236 break;
237
238 default:
239 throw this.ParsingException("Unrecognized keyword: " + Name);
240 }
241 }
242 else
243 {
244 ParsedUri = null;
245 ParsedValue = null;
246
247 if (ContextValue is null)
248 {
249 if (Uri.TryCreate(Name, UriKind.Absolute, out Uri UriValue)) // Not relative to base URI.
250 {
251 ContextValue = Name;
252 ParsedUri = UriValue;
253 }
254 else
255 continue;
256 }
257
258 s = ContextValue as string;
259 if (!(s is null))
260 {
261 Name = s;
262
263 if (!TryGetContextName(Name, Context, out ContextValue))
264 ContextValue = Name;
265 }
266
267 if (ContextValue is Dictionary<string, object> ContextValueObj)
268 {
269 foreach (KeyValuePair<string, object> P2 in ContextValueObj)
270 {
271 switch (P2.Key)
272 {
273 case "@id":
274 s = P2.Value as string
275 ?? throw this.ParsingException("Unsupported @id value: " + P2.Value.ToString());
276
277 Name = s;
278 ExpandPrefixes(ref Name, Context);
279 break;
280
281 case "@type":
282 s = P2.Value as string
283 ?? throw this.ParsingException("Unsupported @type value: " + P2.Value.ToString());
284
285 switch (s)
286 {
287 case "@id":
288 Value = this.CreateUri(Value?.ToString(), BaseUri, Context);
289 break;
290
291 default:
292 Uri TypeUri = this.CreateUri(s, BaseUri, Context);
293 ParsedValue = SemanticElements.Parse(Value?.ToString(), TypeUri.AbsoluteUri, string.Empty);
294 break;
295 }
296 break;
297
298 case "@vocab":
299 s = P2.Value as string
300 ?? throw this.ParsingException("Unsupported @vocab value: " + P2.Value.ToString());
301
302 ParsedUri = this.CreateUri(s, BaseUri, Context);
303 break;
304
305 case "@base":
306 s = P2.Value as string
307 ?? throw this.ParsingException("Unsupported @base value: " + P2.Value.ToString());
308
309 BaseUri = this.CreateUri(s, BaseUri, Context);
310 break;
311
312 case "@prefix":
313 break;
314
315 case "@context":
316 JsonLdContext ScopedContext = await this.GetContext(P2.Value, BaseUri, Context);
317 if (!(Context is null))
318 ScopedContext.Append(Context);
319
320 Context = ScopedContext;
321 break;
322
323 default:
324 throw this.ParsingException("Unhandled context keyword: " + P2.Key);
325 }
326 }
327
328 if (ParsedUri is null && !(Context.Vocabulary is null))
329 ParsedUri = this.CreateUri(Name, Context.Vocabulary, Context);
330 }
331
332 if (!(ParsedUri is null) || Uri.TryCreate(BaseUri, Name, out ParsedUri))
333 {
334 UriNode Predicate = new UriNode(ParsedUri, Name);
335
336 if (ParsedValue is null)
337 {
338 if (Value is Array ValueArray)
339 {
340 foreach (object ValueItem in ValueArray)
341 {
342 ParsedValue = await this.ParseValue(BaseUri, ValueItem, Context);
343 this.Add(new SemanticTriple(Subject, Predicate, ParsedValue));
344 }
345 }
346 else
347 {
348 ParsedValue = await this.ParseValue(BaseUri, Value, Context);
349 this.Add(new SemanticTriple(Subject, Predicate, ParsedValue));
350 }
351 }
352 else
353 this.Add(new SemanticTriple(Subject, Predicate, ParsedValue));
354 }
355 }
356 }
357 }
358 else if (Doc is Array DocArray)
359 {
360 foreach (object DocItem in DocArray)
361 await this.Parse(DocItem, Context, BaseUri, this.CreateBlankNode());
362 }
363
364 return ReturnValue;
365 }
366
367 private async Task<JsonLdContext> GetContext(object Value, Uri BaseUri, JsonLdContext Context)
368 {
369 if (Value is Dictionary<string, object> ContextObj)
370 return new JsonLdContext(ContextObj, BaseUri);
371 else if (Value is Array ContextArray)
372 {
373 JsonLdContext Result = new JsonLdContext();
374
375 foreach (object ContextItem in ContextArray)
376 {
377 JsonLdContext Result2 = await this.GetContext(ContextItem, BaseUri, Context);
378 Result.Append(Result2, BaseUri);
379 Context = Result;
380 }
381
382 return Result;
383 }
384 else
385 {
386 Uri ContextUri;
387
388 if (Value is string Url)
389 ContextUri = this.CreateUri(Url, BaseUri, Context);
390 else if (Value is Uri Uri2)
391 ContextUri = Uri2;
392 else
393 throw this.ParsingException("Expected context URI.");
394
395 if (contextObjects.TryGetValue(ContextUri.AbsolutePath, out KeyValuePair<DateTimeOffset, Dictionary<string, object>> P2))
396 {
397 try
398 {
399 ContentResponse Content = await InternetContent.GetAsync(ContextUri,
400 new KeyValuePair<string, string>("Accept", JsonLdCodec.DefaultContentType),
401 new KeyValuePair<string, string>("If-Modified-Since", CommonTypes.EncodeRfc822(P2.Key)));
402
403 if (!Content.HasError &&
404 Content.Decoded is JsonLdDocument ContextDoc &&
405 ContextDoc.parsedJson is Dictionary<string, object> ContextObj2 &&
406 ContextObj2.TryGetValue("@context", out object Obj) &&
407 Obj is Dictionary<string, object> ContextObj3)
408 {
409 Context = new JsonLdContext(ContextObj3, BaseUri);
410 contextObjects[ContextUri.AbsolutePath] = new KeyValuePair<DateTimeOffset, Dictionary<string, object>>(
411 ContextDoc.Date ?? DateTimeOffset.Now, ContextObj3);
412 }
413 else
414 Context = new JsonLdContext(P2.Value, BaseUri);
415 }
416 catch (Exception)
417 {
418 Context = new JsonLdContext(P2.Value, BaseUri);
419 }
420 }
421 else
422 {
423 ContentResponse Content = await InternetContent.GetAsync(ContextUri,
424 new KeyValuePair<string, string>("Accept", JsonLdCodec.DefaultContentType));
425
426 if (!Content.HasError &&
427 Content.Decoded is JsonLdDocument ContextDoc &&
428 ContextDoc.parsedJson is Dictionary<string, object> ContextObj2 &&
429 ContextObj2.TryGetValue("@context", out object Obj) &&
430 Obj is Dictionary<string, object> ContextObj3)
431 {
432 Context = new JsonLdContext(ContextObj3, BaseUri);
433 contextObjects[ContextUri.AbsolutePath] = new KeyValuePair<DateTimeOffset, Dictionary<string, object>>(
434 ContextDoc.Date ?? DateTimeOffset.Now, ContextObj3);
435 }
436 else
437 throw this.ParsingException("Context reference not a JSON-LD document. Expected Content-Type: " + JsonLdCodec.DefaultContentType);
438 }
439
440 return Context;
441 }
442 }
443
444 private async Task<JsonLdContext> AddType(ISemanticElement Subject, object Value, Uri BaseUri, JsonLdContext Context)
445 {
446 if (Value is string s)
447 {
448 if (TryGetContextName(s, Context, out object ContextValue))
449 {
450 Context = await this.AddType(Subject, ContextValue, BaseUri, Context);
451 }
452 else
453 this.Add(new SemanticTriple(Subject, RdfDocument.RdfType, this.CreateUriNode(s, BaseUri, Context)));
454 }
455 else if (Value is Dictionary<string, object> TypeObj)
456 {
457 foreach (KeyValuePair<string, object> P in TypeObj)
458 {
459 switch (P.Key)
460 {
461 case "@id":
462 Context = await this.AddType(Subject, P.Value, BaseUri, Context);
463 break;
464
465 case "@context":
466 JsonLdContext ScopedContext = await this.GetContext(P.Value, BaseUri, Context);
467 if (!(Context is null))
468 ScopedContext.Append(Context);
469
470 Context = ScopedContext;
471 break;
472 }
473 }
474 }
475 else if (Value is Array A)
476 {
477 foreach (object Item in A)
478 await this.AddType(Subject, Item, BaseUri, Context);
479 }
480 else
481 throw this.ParsingException("Unsupported @type: " + Value?.ToString());
482
483 return Context;
484 }
485
486 private static bool ExpandPrefixes(ref string Name, JsonLdContext Context)
487 {
488 if (Context is null)
489 return false;
490
491 int i = Name.IndexOf(':');
492 if (i < 0)
493 return false;
494
495 string Prefix = Name.Substring(0, i);
496
497 if (!Context.TryGetObjectValue(Prefix, out object Value))
498 return false;
499
500 if (Value is string PrefixUrl)
501 {
502 Name = PrefixUrl + Name.Substring(i + 1);
503 return true;
504 }
505
506 if (Value is Dictionary<string, object> Obj)
507 {
508 foreach (KeyValuePair<string, object> P in Obj)
509 {
510 switch (P.Key)
511 {
512 case "@id":
513 PrefixUrl = P.Value as string;
514 if (!(PrefixUrl is null))
515 {
516 Name = PrefixUrl + Name.Substring(i + 1);
517 return true;
518 }
519 break;
520 }
521 }
522 }
523
524 return false;
525 }
526
527 private static bool TryGetContextName(string Name, JsonLdContext Context, out object Value)
528 {
529 if (Name.StartsWith("@"))
530 {
531 Value = null;
532 return false;
533 }
534
535 if (Context is null)
536 {
537 Value = null;
538 return false;
539 }
540
541 if (ExpandPrefixes(ref Name, Context))
542 {
543 Value = Name;
544 return true;
545 }
546
547 if (Context.TryGetObjectValue(Name, out Value))
548 return true;
549
550 if (!(Context.Vocabulary is null) && Name.IndexOf(':') < 0)
551 {
552 Value = Context.Vocabulary.AbsoluteUri + Name;
553 return true;
554 }
555
556 Value = null;
557 return false;
558 }
559
560 private async Task<ISemanticElement> ParseValue(Uri BaseUri, object Value, JsonLdContext Context)
561 {
562 if (Value is Dictionary<string, object> Obj)
563 {
564 ISemanticElement Object = null;
565
566 foreach (KeyValuePair<string, object> P in Obj)
567 {
568 switch (P.Key)
569 {
570 case "@id":
571 if (P.Value is string s)
572 Object = this.CreateUriNode(s, BaseUri, Context);
573 else
574 throw this.ParsingException("Invalid @id: " + P.Value.ToString());
575 break;
576
577 case "@base":
578 s = P.Value as string;
579 if (!(s is null))
580 BaseUri = this.CreateUri(s, BaseUri, Context);
581 else
582 throw this.ParsingException("Invalid @base: " + P.Value.ToString());
583 break;
584
585 }
586 }
587
588 if (Object is null)
589 Object = this.CreateBlankNode();
590
591 return await this.Parse(Obj, Context, BaseUri, Object) ?? Object;
592 }
593 else if (Value is Uri Uri)
594 return new UriNode(Uri, Uri.ToString());
595 else
597 }
598
602 public object ParsedJson => this.parsedJson;
603
607 public string Text => this.text;
608
612 public DateTimeOffset? Date => this.date;
613
618 public Task DecodeMetaInformation(HttpResponseMessage HttpResponse)
619 {
620 this.date = HttpResponse.Headers.Date;
621 return Task.CompletedTask;
622 }
623
624 }
625}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Definition: CommonTypes.cs:669
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > GetAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a resource, given its URI.
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:45
override void Add(ISemanticTriple Triple)
Adds a triple to the cube.
Encoder and Decoder of semantic information in JSON-LD Documents.
Definition: JsonLdCodec.cs:15
const string DefaultContentType
application/ld+json
Definition: JsonLdCodec.cs:46
Contains semantic information stored in a JSON-LD document.
DateTimeOffset? Date
Server timestamp of document.
static Task< JsonLdDocument > CreateAsync(string Text, Uri BaseUri)
Contains semantic information stored in a turtle document.
static Task< JsonLdDocument > CreateAsync(object Doc, string Text, Uri BaseUri)
Contains semantic information stored in a turtle document.
string Text
Text representation.
static Task< JsonLdDocument > CreateAsync(string Text, Uri BaseUri, string BlankNodeIdPrefix)
Contains semantic information stored in a turtle document.
static Task< JsonLdDocument > CreateAsync(string Text)
Contains semantic information stored in a turtle document.
static Task< JsonLdDocument > CreateAsync(string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
Contains semantic information stored in a turtle document.
static Task< JsonLdDocument > CreateAsync(object Doc, string Text)
Contains semantic information stored in a turtle document.
static Task< JsonLdDocument > CreateAsync(object Doc, string Text, Uri BaseUri, string BlankNodeIdPrefix)
Contains semantic information stored in a turtle document.
static async Task< JsonLdDocument > CreateAsync(object Doc, string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
Contains semantic information stored in a turtle document.
Task DecodeMetaInformation(HttpResponseMessage HttpResponse)
Decodes meta-information available in the HTTP Response.
object ParsedJson
Original content, as parsed JSON.
Represents a blank node
Definition: BlankNode.cs:7
static ISemanticLiteral EncapsulateLiteral(object Value)
Encapsulates an object as a semantic literal.
static ISemanticElement Parse(string Value, string DataType, string Language)
Parses a string literal value.
Contains semantic information stored in an RDF document.
Definition: RdfDocument.cs:23
static UriNode RdfType
rdf:type predicate
Definition: RdfDocument.cs:27
Implements an in-memory cache.
Definition: Cache.cs:17
Interface for content classes, that process information available in HTTP headers in the response.
Interface for semantic nodes.
BlankNodeIdMode
How blank node IDs are generated
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
Prefix
SI prefixes. http://physics.nist.gov/cuu/Units/prefixes.html
Definition: Prefixes.cs:11