Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Consolidator.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Text.RegularExpressions;
5using System.Threading.Tasks;
6using SkiaSharp;
7using Waher.Events;
9using Waher.Script;
11
13{
18 {
19 private readonly string threadId;
20 private readonly SortedDictionary<string, SourceState> sources = new SortedDictionary<string, SourceState>();
21 private readonly MultiReadSingleWriteObject synchObject;
22 private readonly int maxPaletteSize;
23 private Dictionary<string, KeyValuePair<SKColor, int>> legend = null;
24 private DocumentType type = DocumentType.Empty;
25 private SKColor[] palette = null;
26 private object tag = null;
27 private int nrTop = 0;
28 private int nrBottom = 0;
29 private bool filterSources = false;
30
36 public Consolidator(string ThreadId, int MaxPaletteSize)
37 {
38 this.synchObject = new MultiReadSingleWriteObject(this);
39 this.threadId = ThreadId;
40 this.maxPaletteSize = MaxPaletteSize;
41 }
42
46 public string ThreadId => this.threadId;
47
51 public async Task<string[]> GetSources()
52 {
53 await this.synchObject.BeginRead();
54 try
55 {
56 string[] Result = new string[this.sources.Count];
57 this.sources.Keys.CopyTo(Result, 0);
58 return Result;
59 }
60 finally
61 {
62 await this.synchObject.EndRead();
63 }
64 }
65
69 public async Task<int> GetNrReportedSources()
70 {
71 await this.synchObject.BeginRead();
72 try
73 {
74 int Result = 0;
75
76 foreach (SourceState State in this.sources.Values)
77 {
78 if (!State.IsDefault)
79 Result++;
80 }
81
82 return Result;
83 }
84 finally
85 {
86 await this.synchObject.EndRead();
87 }
88 }
89
93 public bool FilterSources
94 {
95 get => this.filterSources;
96 set => this.filterSources = value;
97 }
98
102 public object Tag
103 {
104 get => this.tag;
105 set => this.tag = value;
106 }
107
114 public Task<bool> Add(string Source, MarkdownDocument Markdown)
115 {
116 return this.Add(Source, Markdown, string.Empty);
117 }
118
126 public Task<bool> Add(string Source, MarkdownDocument Markdown, string Id)
127 {
128 return this.Add(Source, Markdown, Id, false, false);
129 }
130
137 public Task<bool> Add(string Source, string Text)
138 {
139 return this.Add(Source, Text, string.Empty);
140 }
141
149 public Task<bool> Add(string Source, string Text, string Id)
150 {
151 return this.Add(Source, Text, Id, false, false);
152 }
153
161 public Task<bool> Update(string Source, MarkdownDocument Markdown, string Id)
162 {
163 return this.Add(Source, Markdown, Id, true, false);
164 }
165
173 public Task<bool> Update(string Source, string Text, string Id)
174 {
175 return this.Add(Source, Text, Id, true, false);
176 }
177
187 private async Task<bool> Add(string Source, string Text, string Id, bool Update, bool IsDefault)
188 {
190 return await this.Add(Source, Doc, Id, Update, IsDefault);
191 }
192
199 public Task<bool> AddDefault(string Source, string Text)
200 {
201 return this.Add(Source, Text, string.Empty, false, true);
202 }
203
210 public Task<bool> AddDefault(string Source, MarkdownDocument Markdown)
211 {
212 return this.Add(Source, Markdown, string.Empty, false, true);
213 }
214
224 private async Task<bool> Add(string Source, MarkdownDocument Markdown, string Id, bool Update, bool IsDefault)
225 {
226 DocumentType Type;
227 bool Result;
228
229 await this.synchObject.BeginWrite();
230 try
231 {
232 if ((Result = !this.sources.TryGetValue(Source, out SourceState State)) || State.IsDefault)
233 {
234 if (Result && this.filterSources)
235 return false;
236
237 State = new SourceState(Source, IsDefault);
238 this.sources[Source] = State;
239 }
240
241 if (Markdown is null)
242 return false;
243
244 if (Update)
245 Type = await State.Update(Markdown, Id);
246 else
247 Type = await State.Add(Markdown, Id);
248
249 if ((int)(this.type & Type) != 0)
250 this.type = (DocumentType)Math.Max((int)this.type, (int)Type);
251 else
252 this.type = DocumentType.Complex;
253
254 this.nrTop = 0;
255 this.nrBottom = 0;
256
257 switch (this.type)
258 {
259 case DocumentType.SingleCode:
260 int i, c, d, d0 = 0;
261 bool First = true;
262 string[] Rows0 = null;
263 string[] Rows;
264
265 foreach (SourceState Info in this.sources.Values)
266 {
267 Rows = Info.FirstDocument.Rows;
268 d = Rows.Length;
269
270 if (First)
271 {
272 First = false;
273 this.nrTop = this.nrBottom = d;
274 Rows0 = Rows;
275 d0 = d;
276 }
277 else
278 {
279 c = Math.Min(this.nrTop, d);
280
281 for (i = 0; i < c; i++)
282 {
283 if (Rows[i] != Rows0[i])
284 break;
285 }
286
287 this.nrTop = i;
288
289 c = Math.Min(this.nrBottom, d);
290
291 for (i = 0; i < c; i++)
292 {
293 if (Rows[d - i - 1] != Rows0[d0 - i - 1])
294 break;
295 }
296
297 this.nrBottom = i;
298 }
299 }
300
301 if (this.nrTop < 1 || this.nrBottom <= 1)
302 this.type = DocumentType.Complex;
303 break;
304
305 case DocumentType.SingleXml:
306 if (this.sources.Count >= 2)
307 this.type = DocumentType.Complex;
308 break;
309 }
310 }
311 finally
312 {
313 await this.synchObject.EndWrite();
314 }
315
316 if (Update)
317 await this.Raise(this.Updated, Source);
318 else
319 await this.Raise(this.Added, Source);
320
321 return Result;
322 }
323
324 private Task Raise(EventHandlerAsync<SourceEventArgs> Handler, string Source)
325 {
326 return Handler.Raise(this, new SourceEventArgs(Source));
327 }
328
332 public event EventHandlerAsync<SourceEventArgs> Added = null;
333
337 public event EventHandlerAsync<SourceEventArgs> Updated = null;
338
343 [Obsolete("Use GenerateMarkdownAsync() instead.")]
344 public string GenerateMarkdown()
345 {
346 return this.GenerateMarkdownAsync().Result;
347 }
348
353 public async Task<string> GenerateMarkdownAsync()
354 {
355 StringBuilder Markdown = new StringBuilder();
356
357 await this.synchObject.BeginRead();
358 try
359 {
360 switch (this.type)
361 {
362 case DocumentType.Empty:
363 return string.Empty;
364
365 case DocumentType.SingleNumber:
366 case DocumentType.SingleLine:
367
368 Markdown.AppendLine("| Nr | Source | Response |");
369
370 if (this.type == DocumentType.SingleNumber)
371 Markdown.AppendLine("|---:|:-------|-------:|");
372 else
373 Markdown.AppendLine("|---:|:-------|:-------|");
374
375 int Nr = 0;
376
377 foreach (KeyValuePair<string, SourceState> P in this.sources)
378 {
379 Markdown.Append("| ");
380 Markdown.Append((++Nr).ToString());
381 Markdown.Append(" | `");
382 Markdown.Append(P.Key);
383 Markdown.Append("` | ");
384 Markdown.Append((await P.Value.GetFirstText()).Trim());
385 Markdown.AppendLine(" |");
386 }
387 break;
388
389 case DocumentType.SingleParagraph:
390
391 Markdown.AppendLine("| Nr | Source | Response |");
392 Markdown.AppendLine("|---:|:-------|:-------|");
393
394 Nr = 0;
395
396 foreach (KeyValuePair<string, SourceState> P in this.sources)
397 {
398 Markdown.Append("| ");
399 Markdown.Append((++Nr).ToString());
400 Markdown.Append(" | `");
401 Markdown.Append(P.Key);
402 Markdown.Append("` | ");
403
404 foreach (string Row in P.Value.FirstDocument.Rows)
405 {
406 Markdown.Append(Row);
407 Markdown.Append("<br/>");
408 }
409
410 Markdown.AppendLine("|");
411 }
412 break;
413
414 case DocumentType.SingleCode:
415 case DocumentType.SingleXml:
416
417 List<string> ConsolidatedRows = new List<string>();
418 int j = 0;
419 int d = this.sources.Count;
420
421 foreach (KeyValuePair<string, SourceState> P in this.sources)
422 {
423 string[] Rows = P.Value.FirstDocument.Rows;
424 int i = 0;
425 int c = Rows.Length;
426
427 j++;
428 if (j > 1)
429 i += this.nrTop;
430
431 if (j < d)
432 c -= this.nrBottom;
433
434 for (; i < c; i++)
435 ConsolidatedRows.Add(Rows[i]);
436 }
437
438 if (ConsolidatedRows[0].StartsWith("```dot", StringComparison.OrdinalIgnoreCase))
439 OptimizeDotEdges(ConsolidatedRows);
440
441 foreach (string Row in ConsolidatedRows)
442 Markdown.AppendLine(Row);
443 break;
444
445 case DocumentType.SingleTable:
446
447 ConsolidatedTable Table = null;
448
449 try
450 {
451 foreach (KeyValuePair<string, SourceState> P in this.sources)
452 {
453 foreach (DocumentInformation Doc in P.Value.Documents)
454 {
455 if (!(Doc?.Table is null))
456 {
457 if (Table is null)
458 Table = await ConsolidatedTable.CreateAsync(P.Key, Doc.Table);
459 else
460 await Table.Add(P.Key, Doc.Table);
461 }
462 }
463 }
464
465 Table?.Export(Markdown);
466 }
467 catch (Exception ex)
468 {
469 Log.Exception(ex);
470 this.GenerateComplexLocked(Markdown);
471 }
472 break;
473
474 case DocumentType.SingleGraph:
475 Graph G = null;
476
477 try
478 {
479 int i;
480
481 if (this.legend is null)
482 this.legend = new Dictionary<string, KeyValuePair<SKColor, int>>();
483
484 if (this.palette is null)
485 this.palette = CreatePalette(this.maxPaletteSize);
486
487 foreach (KeyValuePair<string, SourceState> P in this.sources)
488 {
489 foreach (DocumentInformation Doc in P.Value.Documents)
490 {
491 if (!(Doc?.Graph is null))
492 {
493 i = this.legend.Count % this.maxPaletteSize;
494 if (Doc.Graph.TrySetDefaultColor(this.palette[i]))
495 this.legend[P.Key] = new KeyValuePair<SKColor, int>(this.palette[i], i);
496
497 if (G is null)
498 G = Doc.Graph;
499 else
500 G = (Graph)G.AddRightElementWise(Doc.Graph);
501 }
502 }
503 }
504
505 Markdown.AppendLine("```Graph");
506 G.ToXml(Markdown);
507 Markdown.AppendLine();
508 Markdown.AppendLine("```");
509
510 if (this.legend.Count > 0)
511 {
512 string[] Labels = new string[this.legend.Count];
513 SKColor[] Colors = new SKColor[this.legend.Count];
514 bool First = true;
515
516 Markdown.AppendLine();
517 Markdown.Append("{{Legend([");
518
519 foreach (KeyValuePair<string, KeyValuePair<SKColor, int>> P in this.legend)
520 {
521 if (First)
522 First = false;
523 else
524 Markdown.Append(',');
525
526 Markdown.Append(Expression.ToString(P.Key));
527 }
528
529 Markdown.Append("],[");
530 First = true;
531
532 foreach (KeyValuePair<string, KeyValuePair<SKColor, int>> P in this.legend)
533 {
534 if (First)
535 First = false;
536 else
537 Markdown.Append(',');
538
539 Markdown.Append(Graph.ToRGBAStyle(P.Value.Key));
540 }
541
542 Markdown.AppendLine("],4)}}");
543 }
544
545 Markdown.AppendLine();
546 }
547 catch (Exception ex)
548 {
549 Log.Exception(ex);
550 this.GenerateComplexLocked(Markdown);
551 }
552 break;
553
554 case DocumentType.Complex:
555 default:
556 this.GenerateComplexLocked(Markdown);
557 break;
558 }
559 }
560 finally
561 {
562 await this.synchObject.EndRead();
563 }
564
565 return Markdown.ToString();
566 }
567
568 private static readonly Regex dotEdge = new Regex("^(?'A'(\"[^\"]*\"|'[^']*'|[^\\s]*))\\s*->\\s*(?'B'(\"[^\"]*\"|'[^']*'|[^\\s]*))\\s*([\\[](?'Note'[^\\]]*)[\\]]\\s*)?;\\s*$", RegexOptions.Singleline | RegexOptions.Compiled);
569
570 private static void OptimizeDotEdges(List<string> Rows)
571 {
572 Match[] Matches;
573 Match M;
574 string Row, Row2, A, B, Note;
575 int i, j, c = Rows.Count;
576 bool Changed = false;
577
578 Matches = new Match[c];
579
580 for (i = 0; i < c; i++)
581 {
582 Row = Rows[i];
583 M = Matches[i];
584 if (M is null)
585 {
586 M = dotEdge.Match(Row);
587 Matches[i] = M;
588 }
589
590 if (!M.Success || M.Index > 0 || M.Length < Row.Length)
591 continue;
592
593 A = M.Groups["A"].Value;
594 B = M.Groups["B"].Value;
595 Note = M.Groups["Note"].Value;
596
597 for (j = i + 1; j < c; j++)
598 {
599 Row2 = Rows[j];
600 M = Matches[j];
601 if (M is null)
602 {
603 M = dotEdge.Match(Row2);
604 Matches[j] = M;
605 }
606
607 if (M.Success &&
608 M.Index == 0 &&
609 M.Length == Row.Length &&
610 M.Groups["A"].Value == B &&
611 M.Groups["B"].Value == A &&
612 M.Groups["Note"].Value == Note)
613 {
614 StringBuilder sb = new StringBuilder();
615
616 sb.Append(A);
617 sb.Append(" -> ");
618 sb.Append(B);
619 sb.Append(" [");
620 sb.Append(Note);
621
622 if (!string.IsNullOrEmpty(Note))
623 sb.Append(", ");
624
625 sb.Append("dir=both];");
626
627 Rows[i] = sb.ToString();
628 Matches[i] = null;
629 Rows[j] = string.Empty;
630 Matches[j] = null;
631 Changed = true;
632 break;
633 }
634 }
635 }
636
637 if (Changed)
638 {
639 for (i = 0; i < c;)
640 {
641 if (string.IsNullOrEmpty(Rows[i]))
642 {
643 Rows.RemoveAt(i);
644 c--;
645 }
646 else
647 i++;
648 }
649 }
650 }
651
657 public static SKColor[] CreatePalette(int N)
658 {
659 SKColor[] Result = new SKColor[N];
660 double d = 360.0 / Math.Max(N, 12);
661 int i;
662
663 for (i = 0; i < N; i++)
664 Result[i] = SKColor.FromHsl((float)(d * i), 100, 75);
665
666 return Result;
667 }
668
669 private void GenerateComplexLocked(StringBuilder Markdown)
670 {
671 foreach (KeyValuePair<string, SourceState> P in this.sources)
672 {
673 Markdown.Append('`');
674 Markdown.Append(P.Key);
675 Markdown.AppendLine("`");
676 Markdown.AppendLine();
677
678 DocumentInformation[] Info = P.Value.Documents;
679
680 if (Info.Length == 0)
681 Markdown.AppendLine(":\t");
682 else
683 {
684 bool First = true;
685
686 foreach (DocumentInformation Doc in P.Value.Documents)
687 {
688 if (First)
689 First = false;
690 else
691 Markdown.AppendLine(":\t");
692
693 if (!(Doc?.Rows is null))
694 {
695 foreach (string Row in Doc.Rows)
696 {
697 Markdown.Append(":\t");
698 Markdown.AppendLine(Row);
699 }
700 }
701 }
702 }
703
704 Markdown.AppendLine();
705 }
706 }
707
711 [Obsolete("Use DisposeAsync() instead.")]
712 public void Dispose()
713 {
714 try
715 {
716 this.DisposeAsync().Wait();
717 }
718 catch (Exception ex)
719 {
720 Log.Exception(ex);
721 }
722 }
723
727 public Task DisposeAsync()
728 {
729 return this.Disposed.Raise(this, EventArgs.Empty, false);
730 }
731
735 public event EventHandlerAsync Disposed = null;
736 }
737}
async Task Export(StringBuilder Markdown)
Exports the consolidated table to Markdown.
async Task Add(string Source, Table MarkdownTable)
Adds data from a Markdown table to the consolidated table.
static async Task< ConsolidatedTable > CreateAsync(string Source, Table MarkdownTable)
Creates a consolidated table.
Consolidates Markdown from multiple sources, sharing the same thread.
Definition: Consolidator.cs:18
Task< bool > Add(string Source, MarkdownDocument Markdown, string Id)
Adds incoming markdown information.
Task< bool > Add(string Source, string Text, string Id)
Adds incoming markdown information.
Task< bool > AddDefault(string Source, string Text)
Adds default markdown to present, until a proper response is returned.
Task< bool > Add(string Source, string Text)
Adds incoming markdown information.
Task< bool > Update(string Source, string Text, string Id)
Updates incoming markdown information.
object Tag
External tag object that can be tagged to the object by its owner.
async Task< int > GetNrReportedSources()
Number of sources that have reported content.
Definition: Consolidator.cs:69
async Task< string[]> GetSources()
Consolidated sources.
Definition: Consolidator.cs:51
string GenerateMarkdown()
Generates consolidated markdown from all sources.
Consolidator(string ThreadId, int MaxPaletteSize)
Consolidates Markdown from multiple sources, sharing the same thread.
Definition: Consolidator.cs:36
EventHandlerAsync< SourceEventArgs > Added
Event raised when content from a source has been added.
Task< bool > AddDefault(string Source, MarkdownDocument Markdown)
Adds default markdown to present, until a proper response is returned.
async Task< string > GenerateMarkdownAsync()
Generates consolidated markdown from all sources.
Task< bool > Update(string Source, MarkdownDocument Markdown, string Id)
Updates incoming markdown information.
bool FilterSources
If input should be restricted to a defined set of sources.
Definition: Consolidator.cs:94
EventHandlerAsync Disposed
Event raised when consolidator has been disposed.
static SKColor[] CreatePalette(int N)
Creates a palette for graphs.
EventHandlerAsync< SourceEventArgs > Updated
Event raised when content from a source has been updated.
Task< bool > Add(string Source, MarkdownDocument Markdown)
Adds incoming markdown information.
Graph Graph
Graph object, if DocumentType.SingleGraph
Table Table
Table object, if DocumentType.SingleTable
Maintains the state of one source.
Definition: SourceState.cs:11
bool IsDefault
If the content is default content (true), or reported content (false).
Definition: SourceState.cs:38
Contains a markdown document. This markdown document class supports original markdown,...
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Represents an object that allows single concurrent writers but multiple concurrent readers....
Class managing a script expression.
Definition: Expression.cs:39
static string ToString(double Value)
Converts a value to a string, that can be parsed as part of an expression.
Definition: Expression.cs:4496
Base class for graphs.
Definition: Graph.cs:79
string ToXml()
Exports the graph to XML.
Definition: Graph.cs:1499
abstract bool TrySetDefaultColor(SKColor Color)
Tries to set the default color.
static string ToRGBAStyle(SKColor Color)
Converts a color to an RGB(A) style string.
Definition: Graph.cs:903
virtual ISemiGroupElementWise AddRightElementWise(ISemiGroupElementWise Element)
Tries to add an element to the current element, from the right, element-wise.
Definition: Graph.cs:205
DocumentType
Type of markdown document.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.