Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
TurtleDocument.cs
1using System;
2using System.Collections.Generic;
3using System.Net.Http;
4using System.Numerics;
5using System.Text;
6using System.Threading.Tasks;
12
14{
18 public enum BlankNodeIdMode
19 {
23 Sequential,
24
28 Guid
29 }
30
37 {
38 private readonly Dictionary<string, string> namespaces = new Dictionary<string, string>
39 {
40 { "xsd", XmlSchema.Namespace },
41 { "ttl", "http://www.w3.org/2008/turtle#" }
42 };
43 private readonly Dictionary<string, ISemanticLiteral> dataTypes = new Dictionary<string, ISemanticLiteral>();
44 private readonly string text;
45 private readonly string blankNodeIdPrefix;
46 private readonly int len;
47 private readonly BlankNodeIdMode blankNodeIdMode;
48 private DateTimeOffset? date = null;
49 private Uri baseUri = null;
50 private int blankNodeIndex = 0;
51 private int pos = 0;
52
57 public TurtleDocument(string Text)
58 : this(Text, null)
59 {
60 }
61
67 public TurtleDocument(string Text, Uri BaseUri)
68 : this(Text, BaseUri, "n")
69 {
70 }
71
78 public TurtleDocument(string Text, Uri BaseUri, string BlankNodeIdPrefix)
79 : this(Text, BaseUri, BlankNodeIdPrefix, BlankNodeIdMode.Sequential)
80 {
81 }
82
90 public TurtleDocument(string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
91 {
92 this.text = Text;
93 this.len = this.text.Length;
94 this.baseUri = BaseUri;
95 this.blankNodeIdPrefix = BlankNodeIdPrefix;
96 this.blankNodeIdMode = BlankNodeIdMode;
97
98 if (!(this.baseUri is null))
99 this.namespaces[string.Empty] = this.baseUri.ToString();
100
101 this.ParseTriples();
102 }
103
107 public string Text => this.text;
108
112 public DateTimeOffset? Date => this.date;
113
114 private void ParseTriples()
115 {
116 this.ParseTriples(null);
117
118 if (this.pos < this.len)
119 throw this.ParsingException("Unexpected end of document.");
120 }
121
122 private void ParseTriples(ISemanticElement Subject)
123 {
124 ISemanticElement Predicate = null;
125 ISemanticElement Object;
126 int TriplePosition = Subject is null ? 0 : 1;
127 bool InBlankNode = !(Subject is null);
128
129 while (this.pos < this.len)
130 {
131 Object = this.ParseElement(TriplePosition);
132 if (Object is null)
133 {
134 if (Subject is null)
135 return;
136 else if (Predicate is null)
137 {
138 if (InBlankNode)
139 return;
140 else
141 throw this.ParsingException("Expected predicate.");
142 }
143 else
144 throw this.ParsingException("Expected object.");
145 }
146
147 if (Subject is null)
148 {
149 Subject = Object;
150 TriplePosition++;
151 }
152 else if (Predicate is null)
153 {
154 Predicate = Object;
155 TriplePosition++;
156 }
157 else
158 {
159 this.Add(new SemanticTriple(Subject, Predicate, Object));
160
161 switch (this.NextNonWhitespaceChar())
162 {
163 case '.':
164 if (InBlankNode)
165 throw this.ParsingException("Expected ]");
166
167 Subject = null;
168 Predicate = null;
169 TriplePosition = 0;
170 break;
171
172 case ';':
173 Predicate = null;
174 TriplePosition = 1;
175
176 this.SkipWhiteSpace();
177 if (this.PeekNextChar() == '.')
178 {
179 if (InBlankNode)
180 throw this.ParsingException("Expected ]");
181
182 this.pos++;
183 Subject = null;
184 TriplePosition = 0;
185 }
186 break;
187
188 case ',':
189 break;
190
191 case ']':
192 case '|':
193 return;
194
195 default:
196 if (InBlankNode)
197 throw this.ParsingException("Expected triple delimiter ] ; or ,");
198 else
199 throw this.ParsingException("Expected triple delimiter . ; or ,");
200 }
201 }
202 }
203 }
204
209 public override void Add(ISemanticTriple Triple)
210 {
211 base.Add(Triple);
212
213 this.SkipWhiteSpace();
214 if (this.PeekNextChars(2) == "{|")
215 {
216 this.pos += 2;
217
218 if (!(Triple is SemanticTriple T))
219 T = new SemanticTriple(Triple.Subject, Triple.Predicate, Triple.Object);
220
221 this.ParseTriples(T);
222
223 this.SkipWhiteSpace();
224 if (this.NextChar() != '}')
225 throw this.ParsingException("Expected }");
226 }
227 }
228
229 private ISemanticElement ParseElement(int TriplePosition)
230 {
231 while (true)
232 {
233 char ch = this.NextNonWhitespaceChar();
234
235 switch (ch)
236 {
237 case (char)0:
238 return null;
239
240 case '@':
241 string s = this.ParseName();
242
243 switch (s.ToLower())
244 {
245 case "base":
246 ch = this.NextNonWhitespaceChar();
247 if (ch != '<')
248 throw this.ParsingException("Expected <");
249
250 this.baseUri = this.ParseUri().Uri;
251
252 if (this.NextNonWhitespaceChar() != '.')
253 throw this.ParsingException("Expected .");
254
255 break;
256
257 case "prefix":
258 this.SkipWhiteSpace();
259
260 s = this.ParseName();
261
262 if (this.NextNonWhitespaceChar() != ':')
263 throw this.ParsingException("Expected :");
264
265 if (this.NextNonWhitespaceChar() != '<')
266 throw this.ParsingException("Expected <");
267
268 this.namespaces[s] = this.ParseUri().Uri.ToString();
269
270 if (this.NextNonWhitespaceChar() != '.')
271 throw this.ParsingException("Expected .");
272
273 break;
274
275 default:
276 throw this.ParsingException("Unrecognized keyword.");
277 }
278 break;
279
280 case '[':
281 if (TriplePosition == 1)
282 throw this.ParsingException("Predicate cannot be a blank node.");
283
284 BlankNode Node = this.CreateBlankNode();
285 this.ParseTriples(Node);
286
287 if (TriplePosition == 0)
288 {
289 this.SkipWhiteSpace();
290 if (this.PeekNextChar() == '.')
291 {
292 this.pos++;
293 return this.ParseElement(0);
294 }
295 }
296
297 return Node;
298
299 case '(':
300 return this.ParseCollection();
301
302 case ']':
303 return null;
304
305 case '<':
306 if (this.PeekNextChar() == '<') // Quoted triples, part of RDF-star
307 {
308 this.pos++;
309
310 ISemanticElement Subject = this.ParseElement(0);
311 ISemanticElement Predicate = this.ParseElement(1);
312 ISemanticElement Object = this.ParseElement(2);
313
314 if (this.NextNonWhitespaceChar() != '>')
315 throw this.ParsingException("Expected >");
316
317 if (this.NextNonWhitespaceChar() != '>')
318 throw this.ParsingException("Expected >");
319
320 return new SemanticTriple(Subject, Predicate, Object);
321 }
322 else
323 return this.ParseUri();
324
325 case '"':
326 case '\'':
327 if (TriplePosition != 2)
328 throw this.ParsingException("Literals can only occur in object position.");
329
330 if (this.pos < this.len - 1 && this.text[this.pos] == ch && this.text[this.pos + 1] == ch)
331 {
332 this.pos += 2;
333 s = this.ParseString(ch, true, true);
334 }
335 else
336 s = this.ParseString(ch, false, true);
337
338 string Language = null;
339
340 if (this.pos < this.len && this.text[this.pos] == '@')
341 {
342 this.pos++;
343 Language = this.ParseName();
344 }
345
346 if (this.pos < this.len - 1 && this.text[this.pos] == '^' && this.text[this.pos + 1] == '^')
347 {
348 this.pos += 2;
349
350 string DataType = this.ParseUriOrPrefixedToken().Uri.ToString();
351
352 if (!this.dataTypes.TryGetValue(DataType, out ISemanticLiteral LiteralType))
353 {
354 LiteralType = Types.FindBest<ISemanticLiteral, string>(DataType)
355 ?? new CustomLiteral(string.Empty, DataType);
356
357 this.dataTypes[DataType] = LiteralType;
358 }
359
360 return LiteralType.Parse(s, DataType, Language);
361 }
362 else if (!string.IsNullOrEmpty(Language))
363 return new StringLiteral(s, Language);
364 else
365 return new StringLiteral(s);
366
367 case ':':
368 return this.ParsePrefixedToken(string.Empty);
369
370 default:
371 if (char.IsWhiteSpace(ch))
372 break;
373
374 if (ch == '_')
375 {
376 if (this.NextNonWhitespaceChar() != ':')
377 throw this.ParsingException("Expected :");
378
379 return new BlankNode(this.ParseName());
380 }
381 else if (char.IsLetter(ch) || ch == ':')
382 {
383 this.pos--;
384 s = this.ParseName();
385
386 if (this.PeekNextChar() == ':')
387 {
388 this.pos++;
389 return this.ParsePrefixedToken(s);
390 }
391
392 switch (s)
393 {
394 case "a":
395 if (TriplePosition == 1)
396 return RdfDocument.RdfType;
397 break;
398
399 case "true":
400 if (TriplePosition == 2)
401 return new BooleanLiteral(true);
402 break;
403
404 case "false":
405 if (TriplePosition == 2)
406 return new BooleanLiteral(false);
407 break;
408 }
409
410 throw this.ParsingException("Expected :");
411 }
412 else
413 {
414 if (TriplePosition != 2)
415 throw this.ParsingException("Literals can only occur in object position.");
416
417 this.pos--;
418 return this.ParseNumber();
419 }
420 }
421 }
422 }
423
424 private BlankNode CreateBlankNode()
425 {
426 if (this.blankNodeIdMode == BlankNodeIdMode.Guid)
427 return new BlankNode(this.blankNodeIdPrefix + Guid.NewGuid().ToString());
428 else
429 return new BlankNode(this.blankNodeIdPrefix + (++this.blankNodeIndex).ToString());
430 }
431
432 private ISemanticElement ParseCollection()
433 {
434 LinkedList<ISemanticElement> Elements = null;
435
436 this.SkipWhiteSpace();
437
438 while (this.pos < this.len)
439 {
440 if (this.text[this.pos] == ')')
441 {
442 this.pos++;
443
444 if (Elements is null)
445 return RdfDocument.RdfNil;
446
447 LinkedListNode<ISemanticElement> Loop = Elements.First;
448 BlankNode Result = this.CreateBlankNode();
449 BlankNode Current = Result;
450
451 while (!(Loop is null))
452 {
453 this.Add(new SemanticTriple(Current, RdfDocument.RdfFirst, Loop.Value));
454
455 Loop = Loop.Next;
456
457 if (!(Loop is null))
458 {
459 BlankNode Next = this.CreateBlankNode();
460 this.Add(new SemanticTriple(Current, RdfDocument.RdfRest, Next));
461 Current = Next;
462 }
463 }
464
466
467 return Result;
468 }
469
470 ISemanticElement Element = this.ParseElement(2);
471 if (Element is null)
472 break;
473
474 if (Elements is null)
475 Elements = new LinkedList<ISemanticElement>();
476
477 Elements.AddLast(Element);
478 this.SkipWhiteSpace();
479 }
480
481 throw this.ParsingException("Expected )");
482 }
483
484 private UriNode ParseUriOrPrefixedToken()
485 {
486 if (this.pos >= this.len)
487 throw this.ParsingException("Expected URI or prefixed token.");
488
489 if (this.text[this.pos] == '<')
490 {
491 this.pos++;
492 return this.ParseUri();
493 }
494
495 string Prefix = this.ParseName();
496
497 if (this.NextChar() != ':')
498 throw this.ParsingException("Expected :");
499
500 return this.ParsePrefixedToken(Prefix);
501 }
502
503 private UriNode ParsePrefixedToken(string Prefix)
504 {
505 if (!this.namespaces.TryGetValue(Prefix, out string Namespace))
506 throw this.ParsingException("Prefix unknown.");
507
508 this.SkipWhiteSpace();
509
510 string LocalName = this.ParseName();
511
512 return new UriNode(new Uri(Namespace + LocalName), Prefix + ":" + LocalName);
513 }
514
515 private string ParseName()
516 {
517 if (!IsNameStartChar(this.PeekNextChar()))
518 return string.Empty;
519
520 int Start = this.pos++;
521
522 while (IsNameChar(this.PeekNextChar()))
523 this.pos++;
524
525 return this.text.Substring(Start, this.pos - Start);
526 }
527
533 public static bool IsNameStartChar(char ch)
534 {
535 if (ch < 'A')
536 return false;
537 else if (ch <= 'Z')
538 return true;
539 else if (ch < '_')
540 return false;
541 else if (ch == '_')
542 return true;
543 else if (ch < 'a')
544 return false;
545 else if (ch <= 'z')
546 return true;
547 else if (ch < '\xc0')
548 return false;
549 else if (ch <= '\xd6')
550 return true;
551 else if (ch < '\xd8')
552 return false;
553 else if (ch <= '\xf6')
554 return true;
555 else if (ch < '\xf8')
556 return false;
557 else if (ch <= '\x02ff')
558 return true;
559 else if (ch < '\x0370')
560 return false;
561 else if (ch <= '\x037d')
562 return true;
563 else if (ch < '\x037f')
564 return false;
565 else if (ch <= '\x1fff')
566 return true;
567 else if (ch < '\x037f')
568 return false;
569 else if (ch <= '\x1fff')
570 return true;
571 else if (ch < '\x200c')
572 return false;
573 else if (ch <= '\x200d')
574 return true;
575 else if (ch < '\x2070')
576 return false;
577 else if (ch <= '\x218f')
578 return true;
579 else if (ch < '\x2C00')
580 return false;
581 else if (ch <= '\x2FEF')
582 return true;
583 else if (ch < '\x3001')
584 return false;
585 else if (ch <= '\xD7FF')
586 return true;
587 else if (ch < '\xF900')
588 return false;
589 else if (ch <= '\xFDCF')
590 return true;
591 else if (ch < '\xFDF0')
592 return false;
593 else if (ch <= '\xFFFD')
594 return true;
595 else
596 return false;
597 }
598
604 public static bool IsNameChar(char ch)
605 {
606 if (IsNameStartChar(ch))
607 return true;
608 else if (ch < '-')
609 return false;
610 else if (ch == '-')
611 return true;
612 else if (ch < '0')
613 return false;
614 else if (ch <= '9')
615 return true;
616 else if (ch < '\x00B7')
617 return false;
618 else if (ch == '\x00B7')
619 return true;
620 else if (ch < '\x0300')
621 return false;
622 else if (ch <= '\x036F')
623 return true;
624 else if (ch < '\x203F')
625 return false;
626 else if (ch <= '\x2040')
627 return true;
628 else
629 return false;
630 }
631
632 private SemanticLiteral ParseNumber()
633 {
634 int Start = this.pos;
635 char ch = this.PeekNextChar();
636 bool HasDigits = false;
637 bool HasDecimal = false;
638 bool HasExponent = false;
639
640 if (ch == '+' || ch == '-')
641 {
642 this.pos++;
643 ch = this.PeekNextChar();
644 }
645
646 while (char.IsDigit(ch))
647 {
648 this.pos++;
649 ch = this.PeekNextChar();
650 HasDigits = true;
651 }
652
653 if (ch == '.')
654 {
655 HasDecimal = true;
656 this.pos++;
657 ch = this.PeekNextChar();
658
659 while (char.IsDigit(ch))
660 {
661 this.pos++;
662 ch = this.PeekNextChar();
663 }
664 }
665
666 if (ch == 'e' || ch == 'E')
667 {
668 HasExponent = true;
669 this.pos++;
670 ch = this.PeekNextChar();
671
672 if (ch == '+' || ch == '-')
673 {
674 this.pos++;
675 ch = this.PeekNextChar();
676 }
677
678 while (char.IsDigit(ch))
679 {
680 this.pos++;
681 ch = this.PeekNextChar();
682 }
683 }
684
685 if (this.pos > Start)
686 {
687 string s = this.text.Substring(Start, this.pos - Start);
688
689 if (HasExponent)
690 {
691 if (CommonTypes.TryParse(s, out double dbl))
692 return new DoubleLiteral(dbl, s);
693 else
694 throw this.ParsingException("Invalid double number.");
695 }
696 else if (HasDecimal)
697 {
698 if (CommonTypes.TryParse(s, out decimal dec))
699 return new DecimalLiteral(dec, s);
700 else
701 throw this.ParsingException("Invalid decimal number.");
702 }
703 else if (HasDigits)
704 {
705 if (BigInteger.TryParse(s, out BigInteger bi))
706 return new IntegerLiteral(bi, s);
707 else
708 throw this.ParsingException("Invalid integer number.");
709 }
710 }
711
712 throw this.ParsingException("Expected value element.");
713 }
714
715 private string ParseString(char EndChar, bool MultiLine, bool IncludeWhiteSpace)
716 {
717 StringBuilder sb = null;
718 int Start = this.pos;
719 char ch;
720
721 while ((ch = this.PeekNextChar()) != (char)0)
722 {
723 this.pos++;
724
725 if (ch == EndChar)
726 {
727 if (MultiLine)
728 {
729 if (this.pos < this.len - 1 && this.text[this.pos] == EndChar && this.text[this.pos + 1] == EndChar)
730 {
731 this.pos += 2;
732 return sb?.ToString() ?? this.text.Substring(Start, this.pos - Start - 3);
733 }
734 else
735 sb?.Append(ch);
736 }
737 else
738 return sb?.ToString() ?? this.text.Substring(Start, this.pos - Start - 1);
739 }
740 else if (ch == '\\')
741 {
742 if (sb is null)
743 {
744 sb = new StringBuilder();
745
746 if (this.pos > Start + 1)
747 sb.Append(this.text.Substring(Start, this.pos - Start - 1));
748 }
749
750 switch (ch = this.NextChar())
751 {
752 case (char)0:
753 throw this.ParsingException("Expected escape code.");
754
755 case 't':
756 sb.Append('\t');
757 break;
758
759 case 'n':
760 sb.Append('\n');
761 break;
762
763 case 'r':
764 sb.Append('\r');
765 break;
766
767 case 'v':
768 sb.Append('\v');
769 break;
770
771 case 'f':
772 sb.Append('\f');
773 break;
774
775 case 'b':
776 sb.Append('\b');
777 break;
778
779 case 'a':
780 sb.Append('\a');
781 break;
782
783 case 'u':
784 if (this.pos < this.len - 3 && int.TryParse(this.text.Substring(this.pos, 4), System.Globalization.NumberStyles.HexNumber, null, out int i))
785 {
786 sb.Append((char)i);
787 this.pos += 4;
788 }
789 else
790 throw this.ParsingException("Expected 4-character hexadecimal code.");
791 break;
792
793 case 'U':
794 if (this.pos < this.len - 7 && int.TryParse(this.text.Substring(this.pos, 8), System.Globalization.NumberStyles.HexNumber, null, out i))
795 {
796 sb.Append((char)i);
797 this.pos += 8;
798 }
799 else
800 throw this.ParsingException("Expected 8-character hexadecimal code.");
801 break;
802
803 default:
804 sb.Append(ch);
805 break;
806 }
807 }
808 else if (IncludeWhiteSpace || !char.IsWhiteSpace(ch))
809 sb?.Append(ch);
810 }
811
812 throw this.ParsingException("Expected " + new string(EndChar, MultiLine ? 3 : 1));
813 }
814
815 private ParsingException ParsingException(string Message)
816 {
817 return new ParsingException(Message, this.text, this.pos);
818 }
819
820 private UriNode ParseUri()
821 {
822 string Short = this.ParseString('>', false, false);
823
824 if (this.baseUri is null)
825 {
826 if (Uri.TryCreate(Short, UriKind.RelativeOrAbsolute, out Uri URI))
827 return new UriNode(URI, Short);
828 else
829 throw this.ParsingException("Invalid URI.");
830 }
831 else
832 {
833 if (string.IsNullOrEmpty(Short))
834 return new UriNode(this.baseUri, Short);
835 else if (Uri.TryCreate(this.baseUri, Short, out Uri URI))
836 return new UriNode(URI, Short);
837 else
838 throw this.ParsingException("Invalid URI.");
839 }
840 }
841
842 private void SkipLine()
843 {
844 char ch;
845
846 while ((ch = this.PeekNextChar()) != '\r' && ch != '\n' && ch != 0)
847 this.pos++;
848 }
849
850 private void SkipWhiteSpace()
851 {
852 char ch;
853
854 while (true)
855 {
856 ch = this.PeekNextChar();
857
858 if (char.IsWhiteSpace(ch))
859 this.pos++;
860 else if (ch == '#')
861 this.SkipLine();
862 else
863 break;
864 }
865 }
866
867 private char PeekNextChar()
868 {
869 if (this.pos < this.len)
870 return this.text[this.pos];
871 else
872 return (char)0;
873 }
874
875 private string PeekNextChars(int NrChars)
876 {
877 if (this.pos + NrChars <= this.len)
878 return this.text.Substring(this.pos, NrChars);
879 else
880 return string.Empty;
881 }
882
883 private char NextChar()
884 {
885 if (this.pos < this.len)
886 {
887 char ch = this.text[this.pos++];
888 while (ch == '#')
889 {
890 this.SkipLine();
891 if (this.pos < this.len)
892 ch = this.text[this.pos++];
893 else
894 return (char)0;
895 }
896
897 return ch;
898 }
899 else
900 return (char)0;
901 }
902
903 private char NextNonWhitespaceChar()
904 {
905 char ch;
906
907 do
908 {
909 ch = this.NextChar();
910 }
911 while (ch != 0 && char.IsWhiteSpace(ch));
912
913 return ch;
914 }
915
920 public Task DecodeMetaInformation(HttpResponseMessage HttpResponse)
921 {
922 this.date = HttpResponse.Headers.Date;
923 return Task.CompletedTask;
924 }
925 }
926}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
Represents a blank node
Definition: BlankNode.cs:7
Represents an integer literal of undefined size.
Abstract base class for semantic literal values.
Contains semantic information stored in an RDF document.
Definition: RdfDocument.cs:21
static UriNode RdfType
rdf:type predicate
Definition: RdfDocument.cs:25
static UriNode RdfNil
Predefined reference to end of collection.
Definition: RdfDocument.cs:40
static UriNode RdfRest
Predefined reference to next element in a collection.
Definition: RdfDocument.cs:35
static UriNode RdfFirst
Predefined reference to first element in a collection.
Definition: RdfDocument.cs:30
Contains semantic information stored in a turtle document. https://www.w3.org/TR/rdf12-turtle/ https:...
override void Add(ISemanticTriple Triple)
Adds a triple to the cube.
string Text
Original text of document.
TurtleDocument(string Text)
Contains semantic information stored in a turtle document.
Task DecodeMetaInformation(HttpResponseMessage HttpResponse)
Decodes meta-information available in the HTTP Response.
DateTimeOffset? Date
Server timestamp of document.
static bool IsNameChar(char ch)
Checks if a character can be included in a name.
static bool IsNameStartChar(char ch)
Checks if a character is a character that can start a name.
TurtleDocument(string Text, Uri BaseUri, string BlankNodeIdPrefix)
Contains semantic information stored in a turtle document.
TurtleDocument(string Text, Uri BaseUri)
Contains semantic information stored in a turtle document.
TurtleDocument(string Text, Uri BaseUri, string BlankNodeIdPrefix, BlankNodeIdMode BlankNodeIdMode)
Contains semantic information stored in a turtle document.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
Interface for content classes, that process information available in HTTP headers in the response.
Interface for semantic nodes.
Interface for semantic literals.
Interface for semantic triples.
ISemanticElement Object
Object element
ISemanticElement Predicate
Predicate element
ISemanticElement Subject
Subject element
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