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 SkiaSharp;
2using System;
4using System.Text;
5using System.Text.RegularExpressions;
6using System.Threading.Tasks;
7using Waher.Events;
11using Waher.Script;
13
15{
20 {
21 private readonly string threadId;
22 private readonly SortedDictionary<string, SourceState> sources = new SortedDictionary<string, SourceState>();
23 private readonly MultiReadSingleWriteObject synchObject;
24 private readonly int maxPaletteSize;
25 private Dictionary<string, KeyValuePair<SKColor, int>> legend = null;
26 private DocumentType type = DocumentType.Empty;
27 private SKColor[] palette = null;
28 private object tag = null;
29 private int nrTop = 0;
30 private int nrBottom = 0;
31 private bool filterSources = false;
32
38 public Consolidator(string ThreadId, int MaxPaletteSize)
39 {
40 this.synchObject = new MultiReadSingleWriteObject(this);
41 this.threadId = ThreadId;
42 this.maxPaletteSize = MaxPaletteSize;
43 }
44
48 public string ThreadId => this.threadId;
49
53 public async Task<string[]> GetSources()
54 {
55 await this.synchObject.BeginRead();
56 try
57 {
58 string[] Result = new string[this.sources.Count];
59 this.sources.Keys.CopyTo(Result, 0);
60 return Result;
61 }
62 finally
63 {
64 await this.synchObject.EndRead();
65 }
66 }
67
71 public async Task<int> GetNrReportedSources()
72 {
73 await this.synchObject.BeginRead();
74 try
75 {
76 int Result = 0;
77
78 foreach (SourceState State in this.sources.Values)
79 {
80 if (!State.IsDefault)
81 Result++;
82 }
83
84 return Result;
85 }
86 finally
87 {
88 await this.synchObject.EndRead();
89 }
90 }
91
95 public bool FilterSources
96 {
97 get => this.filterSources;
98 set => this.filterSources = value;
99 }
100
104 public object Tag
105 {
106 get => this.tag;
107 set => this.tag = value;
108 }
109
116 public Task<bool> Add(string Source, MarkdownDocument Markdown)
117 {
118 return this.Add(Source, Markdown, string.Empty);
119 }
120
128 public Task<bool> Add(string Source, MarkdownDocument Markdown, string Id)
129 {
130 return this.Add(Source, Markdown, Id, false, false);
131 }
132
139 public Task<bool> Add(string Source, string Text)
140 {
141 return this.Add(Source, Text, string.Empty);
142 }
143
151 public Task<bool> Add(string Source, string Text, string Id)
152 {
153 return this.Add(Source, Text, Id, false, false);
154 }
155
163 public Task<bool> Update(string Source, MarkdownDocument Markdown, string Id)
164 {
165 return this.Add(Source, Markdown, Id, true, false);
166 }
167
175 public Task<bool> Update(string Source, string Text, string Id)
176 {
177 return this.Add(Source, Text, Id, true, false);
178 }
179
189 private async Task<bool> Add(string Source, string Text, string Id, bool Update, bool IsDefault)
190 {
192 return await this.Add(Source, Doc, Id, Update, IsDefault);
193 }
194
201 public Task<bool> AddDefault(string Source, string Text)
202 {
203 return this.Add(Source, Text, string.Empty, false, true);
204 }
205
212 public Task<bool> AddDefault(string Source, MarkdownDocument Markdown)
213 {
214 return this.Add(Source, Markdown, string.Empty, false, true);
215 }
216
226 private async Task<bool> Add(string Source, MarkdownDocument Markdown, string Id, bool Update, bool IsDefault)
227 {
228 DocumentType Type;
229 bool Result;
230
231 await this.synchObject.BeginWrite();
232 try
233 {
234 if ((Result = !this.sources.TryGetValue(Source, out SourceState State)) || State.IsDefault)
235 {
236 if (Result && this.filterSources)
237 return false;
238
239 State = new SourceState(Source, IsDefault);
240 this.sources[Source] = State;
241 }
242
243 if (Markdown is null)
244 return false;
245
246 if (Update)
247 Type = await State.Update(Markdown, Id);
248 else
249 Type = await State.Add(Markdown, Id);
250
251 if ((int)(this.type & Type) != 0)
252 this.type = (DocumentType)Math.Max((int)this.type, (int)Type);
253 else
254 this.type = DocumentType.Complex;
255
256 this.nrTop = 0;
257 this.nrBottom = 0;
258
259 switch (this.type)
260 {
261 case DocumentType.SingleCode:
262 int i, c, d, d0 = 0;
263 bool First = true;
264 string[] Rows0 = null;
265 string[] Rows;
266
267 foreach (SourceState Info in this.sources.Values)
268 {
269 Rows = Info.FirstDocument.Rows;
270 d = Rows.Length;
271
272 if (First)
273 {
274 First = false;
275 this.nrTop = this.nrBottom = d;
276 Rows0 = Rows;
277 d0 = d;
278 }
279 else
280 {
281 c = Math.Min(this.nrTop, d);
282
283 for (i = 0; i < c; i++)
284 {
285 if (Rows[i] != Rows0[i])
286 break;
287 }
288
289 this.nrTop = i;
290
291 c = Math.Min(this.nrBottom, d);
292
293 for (i = 0; i < c; i++)
294 {
295 if (Rows[d - i - 1] != Rows0[d0 - i - 1])
296 break;
297 }
298
299 this.nrBottom = i;
300 }
301 }
302
303 if (this.nrTop < 1 || this.nrBottom <= 1)
304 this.type = DocumentType.Complex;
305 break;
306
307 case DocumentType.SingleXml:
308 if (this.sources.Count >= 2)
309 this.type = DocumentType.Complex;
310 break;
311 }
312 }
313 finally
314 {
315 await this.synchObject.EndWrite();
316 }
317
318 if (Update)
319 await this.Raise(this.Updated, Source);
320 else
321 await this.Raise(this.Added, Source);
322
323 return Result;
324 }
325
326 private Task Raise(EventHandlerAsync<SourceEventArgs> Handler, string Source)
327 {
328 return Handler.Raise(this, new SourceEventArgs(Source));
329 }
330
334 public event EventHandlerAsync<SourceEventArgs> Added = null;
335
339 public event EventHandlerAsync<SourceEventArgs> Updated = null;
340
345 [Obsolete("Use GenerateMarkdownAsync() instead.")]
346 public string GenerateMarkdown()
347 {
348 return this.GenerateMarkdownAsync().Result;
349 }
350
355 public async Task<string> GenerateMarkdownAsync()
356 {
357 StringBuilder Markdown = new StringBuilder();
358
359 await this.synchObject.BeginRead();
360 try
361 {
362 switch (this.type)
363 {
364 case DocumentType.Empty:
365 return string.Empty;
366
367 case DocumentType.SingleNumber:
368 case DocumentType.SingleLine:
369
370 Markdown.AppendLine("| Nr | Source | Response |");
371
372 if (this.type == DocumentType.SingleNumber)
373 Markdown.AppendLine("|---:|:-------|-------:|");
374 else
375 Markdown.AppendLine("|---:|:-------|:-------|");
376
377 int Nr = 0;
378
379 foreach (KeyValuePair<string, SourceState> P in this.sources)
380 {
381 Markdown.Append("| ");
382 Markdown.Append((++Nr).ToString());
383 Markdown.Append(" | `");
384 Markdown.Append(P.Key);
385 Markdown.Append("` | ");
386 Markdown.Append((await P.Value.GetFirstText()).Trim());
387 Markdown.AppendLine(" |");
388 }
389 break;
390
391 case DocumentType.SingleParagraph:
392
393 Markdown.AppendLine("| Nr | Source | Response |");
394 Markdown.AppendLine("|---:|:-------|:-------|");
395
396 Nr = 0;
397
398 foreach (KeyValuePair<string, SourceState> P in this.sources)
399 {
400 Markdown.Append("| ");
401 Markdown.Append((++Nr).ToString());
402 Markdown.Append(" | `");
403 Markdown.Append(P.Key);
404 Markdown.Append("` | ");
405
406 foreach (string Row in P.Value.FirstDocument.Rows)
407 {
408 Markdown.Append(Row);
409 Markdown.Append("<br/>");
410 }
411
412 Markdown.AppendLine("|");
413 }
414 break;
415
416 case DocumentType.SingleCode:
417 case DocumentType.SingleXml:
418
419 ChunkedList<string> ConsolidatedRows = new ChunkedList<string>();
420 int j = 0;
421 int d = this.sources.Count;
422
423 foreach (KeyValuePair<string, SourceState> P in this.sources)
424 {
425 string[] Rows = P.Value.FirstDocument.Rows;
426 int i = 0;
427 int c = Rows.Length;
428
429 j++;
430 if (j > 1)
431 i += this.nrTop;
432
433 if (j < d)
434 c -= this.nrBottom;
435
436 for (; i < c; i++)
437 ConsolidatedRows.Add(Rows[i]);
438 }
439
440 if (ConsolidatedRows[0].StartsWith("```dot", StringComparison.OrdinalIgnoreCase))
441 OptimizeDotEdges(ConsolidatedRows);
442
443 foreach (string Row in ConsolidatedRows)
444 Markdown.AppendLine(Row);
445 break;
446
447 case DocumentType.SingleTable:
448
449 ConsolidatedTable Table = null;
450
451 try
452 {
453 foreach (KeyValuePair<string, SourceState> P in this.sources)
454 {
455 foreach (DocumentInformation Doc in P.Value.Documents)
456 {
457 if (!(Doc?.Table is null))
458 {
459 if (Table is null)
460 Table = await ConsolidatedTable.CreateAsync(P.Key, Doc.Table);
461 else
462 await Table.Add(P.Key, Doc.Table);
463 }
464 }
465 }
466
467 Table?.Export(Markdown);
468 }
469 catch (Exception ex)
470 {
471 Log.Exception(ex);
472 this.GenerateComplexLocked(Markdown);
473 }
474 break;
475
476 case DocumentType.SingleGraph:
477 Graph G = null;
478
479 try
480 {
481 int i;
482
483 this.legend ??= new Dictionary<string, KeyValuePair<SKColor, int>>();
484 this.palette ??= CreatePalette(this.maxPaletteSize);
485
486 foreach (KeyValuePair<string, SourceState> P in this.sources)
487 {
488 foreach (DocumentInformation Doc in P.Value.Documents)
489 {
490 if (!(Doc?.Graph is null))
491 {
492 i = this.legend.Count % this.maxPaletteSize;
493 if (Doc.Graph.TrySetDefaultColor(this.palette[i]))
494 this.legend[P.Key] = new KeyValuePair<SKColor, int>(this.palette[i], i);
495
496 if (G is null)
497 G = Doc.Graph;
498 else
499 G = (Graph)G.AddRightElementWise(Doc.Graph);
500 }
501 }
502 }
503
504 Markdown.AppendLine("```Graph");
505 G.ToXml(Markdown);
506 Markdown.AppendLine();
507 Markdown.AppendLine("```");
508
509 if (this.legend.Count > 0)
510 {
511 string[] Labels = new string[this.legend.Count];
512 SKColor[] Colors = new SKColor[this.legend.Count];
513
514 i = 0;
515 Markdown.AppendLine();
516
517 foreach (KeyValuePair<string, KeyValuePair<SKColor, int>> P in this.legend)
518 {
519 Labels[i] = P.Key;
520 Colors[i] = P.Value.Key;
521 i++;
522 }
523
524 string LegandXml = Legend.CreateLayout(Labels, Colors, SKColors.Black, 4, new Variables());
525
526 Markdown.AppendLine("```layout");
527 Markdown.AppendLine(LegandXml);
528 Markdown.AppendLine("```");
529 Markdown.AppendLine();
530 }
531
532 Markdown.AppendLine();
533 }
534 catch (Exception ex)
535 {
536 Log.Exception(ex);
537 this.GenerateComplexLocked(Markdown);
538 }
539 break;
540
541 case DocumentType.Complex:
542 default:
543 this.GenerateComplexLocked(Markdown);
544 break;
545 }
546 }
547 finally
548 {
549 await this.synchObject.EndRead();
550 }
551
552 return Markdown.ToString();
553 }
554
555 private static readonly Regex dotEdge = new Regex("^(?'A'(\"[^\"]*\"|'[^']*'|[^\\s]*))\\s*->\\s*(?'B'(\"[^\"]*\"|'[^']*'|[^\\s]*))\\s*([\\[](?'Note'[^\\]]*)[\\]]\\s*)?;\\s*$", RegexOptions.Singleline | RegexOptions.Compiled);
556
557 private static void OptimizeDotEdges(ChunkedList<string> Rows)
558 {
559 Match[] Matches;
560 Match M;
561 string Row, Row2, A, B, Note;
562 int i, j, c = Rows.Count;
563 bool Changed = false;
564
565 Matches = new Match[c];
566
567 for (i = 0; i < c; i++)
568 {
569 Row = Rows[i];
570 M = Matches[i];
571 if (M is null)
572 {
573 M = dotEdge.Match(Row);
574 Matches[i] = M;
575 }
576
577 if (!M.Success || M.Index > 0 || M.Length < Row.Length)
578 continue;
579
580 A = M.Groups["A"].Value;
581 B = M.Groups["B"].Value;
582 Note = M.Groups["Note"].Value;
583
584 for (j = i + 1; j < c; j++)
585 {
586 Row2 = Rows[j];
587 M = Matches[j];
588 if (M is null)
589 {
590 M = dotEdge.Match(Row2);
591 Matches[j] = M;
592 }
593
594 if (M.Success &&
595 M.Index == 0 &&
596 M.Length == Row.Length &&
597 M.Groups["A"].Value == B &&
598 M.Groups["B"].Value == A &&
599 M.Groups["Note"].Value == Note)
600 {
601 StringBuilder sb = new StringBuilder();
602
603 sb.Append(A);
604 sb.Append(" -> ");
605 sb.Append(B);
606 sb.Append(" [");
607 sb.Append(Note);
608
609 if (!string.IsNullOrEmpty(Note))
610 sb.Append(", ");
611
612 sb.Append("dir=both];");
613
614 Rows[i] = sb.ToString();
615 Matches[i] = null;
616 Rows[j] = string.Empty;
617 Matches[j] = null;
618 Changed = true;
619 break;
620 }
621 }
622 }
623
624 if (Changed)
625 {
626 for (i = 0; i < c;)
627 {
628 if (string.IsNullOrEmpty(Rows[i]))
629 {
630 Rows.RemoveAt(i);
631 c--;
632 }
633 else
634 i++;
635 }
636 }
637 }
638
644 public static SKColor[] CreatePalette(int N)
645 {
646 SKColor[] Result = new SKColor[N];
647 double d = 360.0 / Math.Max(N, 12);
648 int i;
649
650 for (i = 0; i < N; i++)
651 Result[i] = SKColor.FromHsl((float)(d * i), 100, 75);
652
653 return Result;
654 }
655
656 private void GenerateComplexLocked(StringBuilder Markdown)
657 {
658 foreach (KeyValuePair<string, SourceState> P in this.sources)
659 {
660 Markdown.Append('`');
661 Markdown.Append(P.Key);
662 Markdown.AppendLine("`");
663 Markdown.AppendLine();
664
665 DocumentInformation[] Info = P.Value.Documents;
666
667 if (Info.Length == 0)
668 Markdown.AppendLine(":\t");
669 else
670 {
671 bool First = true;
672
673 foreach (DocumentInformation Doc in P.Value.Documents)
674 {
675 if (First)
676 First = false;
677 else
678 Markdown.AppendLine(":\t");
679
680 if (!(Doc?.Rows is null))
681 {
682 foreach (string Row in Doc.Rows)
683 {
684 Markdown.Append(":\t");
685 Markdown.AppendLine(Row);
686 }
687 }
688 }
689 }
690
691 Markdown.AppendLine();
692 }
693 }
694
698 [Obsolete("Use DisposeAsync() instead.")]
699 public void Dispose()
700 {
701 try
702 {
703 this.DisposeAsync().Wait();
704 }
705 catch (Exception ex)
706 {
707 Log.Exception(ex);
708 }
709 }
710
714 public Task DisposeAsync()
715 {
716 return this.Disposed.Raise(this, EventArgs.Empty, false);
717 }
718
722 public event EventHandlerAsync Disposed = null;
723 }
724}
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:20
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:71
async Task< string[]> GetSources()
Consolidated sources.
Definition: Consolidator.cs:53
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:38
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:96
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:10
bool IsDefault
If the content is default content (true), or reported content (false).
Definition: SourceState.cs:37
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:14
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:1657
Creates a legend that can be displayed in association with a graph containing multiple series.
Definition: Legend.cs:18
static string CreateLayout(string[] Labels, SKColor[] Colors, SKColor FgColor, int NrColumns, Variables Variables)
Creates a graph bitmap containing a legend.
Definition: Legend.cs:130
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void RemoveAt(int Index)
Removes the item at the specified index.
int Count
Number of elements in collection.
Definition: ChunkedList.cs:68
Represents an object that allows single concurrent writers but multiple concurrent readers....
Base class for graphs.
Definition: Graph.cs:88
string ToXml()
Exports the graph to XML.
Definition: Graph.cs:1515
abstract bool TrySetDefaultColor(SKColor Color)
Tries to set the default color.
virtual ISemiGroupElementWise AddRightElementWise(ISemiGroupElementWise Element)
Tries to add an element to the current element, from the right, element-wise.
Definition: Graph.cs:222
Collection of variables.
Definition: Variables.cs:25
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.