Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmlLayout.cs
1using SkiaSharp;
2using System;
4using System.IO;
5using System.Text;
6using System.Threading.Tasks;
7using System.Xml;
18using Waher.Events;
23using Waher.Script;
25using Waher.Security;
26
28{
29 internal class GraphInfo
30 {
31 public XmlDocument Xml;
32 public string FileName;
33 public string Title;
34 public bool Dynamic;
35 public byte[] DynamicContent;
36 public bool Converted;
37 }
38
44 {
45 internal const string DefaultContentType = ImageCodec.ContentTypePng;
46 internal const string DefaultFileExtension = ImageCodec.FileExtensionPng;
47 internal const SKEncodedImageFormat DefaultFormat = SKEncodedImageFormat.Png;
48 internal const int DefaultQuality = 100;
49
50 private static readonly Random rnd = new Random();
51 private static Scheduler scheduler = null;
52 private static string layoutFolder = null;
53 private static string contentRootFolder = null;
54
58 public XmlLayout()
59 {
60 }
61
67 public static void Init(string ContentRootFolder)
68 {
69 contentRootFolder = ContentRootFolder;
70 layoutFolder = Path.Combine(contentRootFolder, "Layout");
71
72 if (scheduler is null)
73 {
74 if (Types.TryGetModuleParameter("Scheduler", out Scheduler Scheduler))
75 scheduler = Scheduler;
76 else
77 {
78 scheduler = new Scheduler();
79
80 Log.Terminating += (Sender, e) =>
81 {
82 scheduler?.Dispose();
83 scheduler = null;
84 return Task.CompletedTask;
85 };
86 }
87 }
88
89 if (!Directory.Exists(layoutFolder))
90 Directory.CreateDirectory(layoutFolder);
91
92 DeleteOldFiles(TimeSpan.FromDays(7));
93 }
94
95 private static void DeleteOldFiles(object P)
96 {
97 if (P is TimeSpan MaxAge)
98 DeleteOldFiles(MaxAge, true);
99 }
100
106 public static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
107 {
108 if (string.IsNullOrEmpty(layoutFolder))
109 return;
110
111 DateTime Limit = DateTime.Now - MaxAge;
112 int Count = 0;
113
114 DirectoryInfo LayoutFolder = new DirectoryInfo(layoutFolder);
115 FileInfo[] Files = LayoutFolder.GetFiles("*.*");
116
117 foreach (FileInfo FileInfo in Files)
118 {
119 if (FileInfo.LastAccessTime < Limit)
120 {
121 try
122 {
123 File.Delete(FileInfo.FullName);
124 Count++;
125 }
126 catch (Exception ex)
127 {
128 Log.Error("Unable to delete old file: " + ex.Message, FileInfo.FullName);
129 }
130 }
131 }
132
133 if (Count > 0)
134 Log.Informational(Count.ToString() + " old file(s) deleted.", layoutFolder);
135
136 if (Reschedule)
137 {
138 lock (rnd)
139 {
140 scheduler.Add(DateTime.Now.AddDays(rnd.NextDouble() * 2), DeleteOldFiles, MaxAge);
141 }
142 }
143 }
144
150 public Grade Supports(string Language)
151 {
152 int i = Language.IndexOf(':');
153 if (i > 0)
154 Language = Language.Substring(0, i).TrimEnd();
155
156 switch (Language.ToLower())
157 {
158 case "layout":
159 return Grade.Excellent;
160 }
161
162 return Grade.NotAtAll;
163 }
164
168 public bool EvaluatesScript => true;
169
174 public void Register(MarkdownDocument Document)
175 {
176 // Do nothing.
177 }
178
188 public async Task<bool> RenderHtml(HtmlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
189 {
190 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
191 if (Info?.FileName is null || !Info.Converted)
192 return false;
193
194 string FileName = Info.FileName.Substring(contentRootFolder.Length).Replace(Path.DirectorySeparatorChar, '/');
195 if (!FileName.StartsWith("/"))
196 FileName = "/" + FileName;
197
198 StringBuilder Output = Renderer.Output;
199
200 Output.Append("<figure>");
201 Output.Append("<img src=\"");
202 if (Info.Dynamic)
203 Output.Append(ImageContent.GenerateUrl(Info.DynamicContent, DefaultContentType));
204 else
205 Output.Append(XML.HtmlAttributeEncode(FileName));
206
207 if (!string.IsNullOrEmpty(Info.Title))
208 {
209 Output.Append("\" alt=\"");
210 Output.Append(XML.HtmlAttributeEncode(Info.Title));
211
212 Output.Append("\" title=\"");
213 Output.Append(XML.HtmlAttributeEncode(Info.Title));
214 }
215 else
216 Output.Append("\" alt=\"2D-layout");
217
218 Output.Append("\" class=\"aloneUnsized\"/>");
219
220 if (!string.IsNullOrEmpty(Info.Title))
221 {
222 Output.Append("<figcaption>");
223 Output.Append(XML.HtmlValueEncode(Info.Title));
224 Output.Append("</figcaption>");
225 }
226
227 Output.AppendLine("</figure>");
228
229 return true;
230 }
231
242 internal static Task<GraphInfo> GetFileName(string Language, string[] Rows, Variables Session,
243 SKEncodedImageFormat ImageFormat, int Quality, string FileExtension)
244 {
245 return GetFileName(Language, MarkdownDocument.AppendRows(Rows), Session, ImageFormat, Quality, FileExtension);
246 }
247
258 internal static async Task<GraphInfo> GetFileName(string Language, string Xml, Variables Session,
259 SKEncodedImageFormat ImageFormat, int Quality, string FileExtension)
260 {
261 GraphInfo Result = new GraphInfo();
262 int i = Language.IndexOf(':');
263
264 if (i > 0)
265 {
266 Result.Title = Language.Substring(i + 1).Trim();
267 Language = Language.Substring(0, i).TrimEnd();
268 }
269 else
270 Result.Title = string.Empty;
271
272 string GraphBgColor = GetColor(Graph.GraphBgColorVariableName, Session);
273 string GraphFgColor = GetColor(Graph.GraphFgColorVariableName, Session);
274
275 string Hash = Hashes.ComputeSHA256HashString(Encoding.UTF8.GetBytes(Xml + Language + GraphBgColor + GraphFgColor));
276
277 string LayoutFolder = Path.Combine(contentRootFolder, "Layout");
278 string FileName = Path.Combine(LayoutFolder, Hash);
279 Result.FileName = FileName + "." + FileExtension;
280
281 if (File.Exists(Result.FileName))
282 Result.Converted = true;
283 else
284 {
285 Result.Xml = XML.ParseXml(Xml);
286
287 if (Layout2DDocument.IsLayoutXml(Result.Xml))
288 {
289 Layout2DDocument LayoutDoc = await Layout2DDocument.FromXml(Result.Xml, Session);
290 RenderSettings Settings = await LayoutDoc.GetRenderSettings(Session);
291
292 KeyValuePair<SKImage, Map[]> P = await LayoutDoc.Render(Settings);
293 using (SKImage Img = P.Key) // TODO: Maps
294 {
295 using (SKData Data = Img.Encode(ImageFormat, Quality))
296 {
297 Result.DynamicContent = Data.ToArray();
298 Result.Dynamic = LayoutDoc.Dynamic;
299
300 if (!LayoutDoc.Dynamic)
301 await Files.WriteAllBytesAsync(Result.FileName, Result.DynamicContent);
302 }
303 }
304
305 Result.Converted = true;
306 }
307 else
308 Result.Converted = false;
309 }
310
311 return Result;
312 }
313
314 private static string GetColor(string VariableName, Variables Variables)
315 {
316 if (Variables is null)
317 return null;
318
319 if (!Variables.TryGetVariable(VariableName, out Variable v))
320 return null;
321
322 if (v.ValueObject is SKColor Color)
323 return Graph.ToRGBAStyle(Color);
324 else if (v.ValueObject is string s)
325 return s;
326 else
327 return null;
328 }
329
339 public async Task<bool> RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
340 {
341 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
342 if (Info?.FileName is null || !Info.Converted)
343 return false;
344
345 if (Info.Dynamic)
346 {
347 ImageContent.GenerateMarkdown(Renderer.Output, Info.DynamicContent, DefaultContentType, Info.Title);
348 return true;
349 }
350 else
351 return await ImageContent.GenerateMarkdownFromFile(Renderer.Output, Info.FileName, Info.Title);
352 }
353
363 public async Task<bool> RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
364 {
365 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
366 if (Info?.FileName is null || !Info.Converted)
367 return false;
368
369 Renderer.Output.AppendLine(Info.Title);
370
371 return true;
372 }
373
383 public async Task<bool> RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
384 {
385 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
386 if (Info?.FileName is null || !Info.Converted)
387 return false;
388
389 XmlWriter Output = Renderer.XmlOutput;
390
391 if (Info.Dynamic)
392 {
393 await Wpf.Multimedia.ImageContent.OutputWpf(Output, new ImageSource()
394 {
395 Url = ImageContent.GenerateUrl(Info.DynamicContent, DefaultContentType)
396 }, Info.Title);
397 }
398 else
399 {
400 Output.WriteStartElement("Image");
401 Output.WriteAttributeString("Source", Info.FileName);
402 Output.WriteAttributeString("Stretch", "None");
403
404 if (!string.IsNullOrEmpty(Info.Title))
405 Output.WriteAttributeString("ToolTip", Info.Title);
406
407 Output.WriteEndElement();
408 }
409
410 return true;
411 }
412
422 public async Task<bool> RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
423 {
424 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
425 if (Info?.FileName is null || !Info.Converted)
426 return false;
427
428 XmlWriter Output = Renderer.XmlOutput;
429
430 if (Info.Dynamic)
431 {
432 await Xamarin.Multimedia.ImageContent.OutputXamarinForms(Output, new ImageSource()
433 {
434 Url = ImageContent.GenerateUrl(Info.DynamicContent, DefaultContentType)
435 });
436 }
437 else
438 {
439 Output.WriteStartElement("Image");
440 Output.WriteAttributeString("Source", Info.FileName);
441 Output.WriteEndElement();
442 }
443
444 return true;
445 }
446
456 public async Task<bool> RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
457 {
458 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
459 if (Info?.FileName is null || !Info.Converted)
460 return false;
461
462 if (Info.Dynamic)
463 Info.FileName = await Model.Multimedia.ImageContent.GetTemporaryFile(Info.DynamicContent, DefaultFileExtension.Substring(1));
464
465 StringBuilder Output = Renderer.Output;
466
467 Output.AppendLine("\\begin{figure}[h]");
468 Output.AppendLine("\\centering");
469
470 Output.Append("\\fbox{\\includegraphics{");
471 Output.Append(Info.FileName.Replace('\\', '/'));
472 Output.AppendLine("}}");
473
474 if (!string.IsNullOrEmpty(Info.Title))
475 {
476 Output.Append("\\caption{");
477 Output.Append(LatexRenderer.EscapeLaTeX(Info.Title));
478 Output.AppendLine("}");
479 }
480
481 Output.AppendLine("\\end{figure}");
482 Output.AppendLine();
483
484 return true;
485 }
486
494 public async Task<PixelInformation> GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
495 {
496 GraphInfo Info = await GetFileName(Language, Rows, Document.Settings.Variables, DefaultFormat, DefaultQuality, DefaultFileExtension);
497 if (Info?.FileName is null || !Info.Converted)
498 return null;
499
500 byte[] Data = await Files.ReadAllBytesAsync(Info.FileName);
501
502 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
503 {
504 return new PixelInformationPng(Data, Bitmap.Width, Bitmap.Height);
505 }
506 }
507
513 public Grade Supports(XmlDocument Xml)
514 {
515 return Layout2DDocument.IsLayoutXml(Xml) ? Grade.Excellent : Grade.NotAtAll;
516 }
517
524 public async Task<object> TransformXml(XmlDocument Xml, Variables Session)
525 {
526 Layout2DDocument LayoutDoc = await Layout2DDocument.FromXml(Xml, Session);
527 RenderSettings Settings = await LayoutDoc.GetRenderSettings(Session);
528
529 KeyValuePair<SKImage, Map[]> P = await LayoutDoc.Render(Settings);
530 using (SKImage Img = P.Key) // TODO: Maps
531 {
532 return PixelInformation.FromImage(Img);
533 }
534 }
535
545 public async Task<bool> RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
546 {
547 string Xml = MarkdownDocument.AppendRows(Rows);
548
549 try
550 {
551 string Title;
552 int i = Language.IndexOf(':');
553
554 if (i > 0)
555 {
556 Title = Language.Substring(i + 1).Trim();
557 Language = Language.Substring(0, i).TrimEnd();
558 }
559 else
560 Title = string.Empty;
561
562 XmlDocument Doc = XML.ParseXml(Xml);
563
565 Layout2DDocument LayoutDoc = await Layout2DDocument.FromXml(Doc, Variables);
566 RenderSettings Settings = await LayoutDoc.GetRenderSettings(Variables);
567 XmlWriter Output = Renderer.XmlOutput;
568
569 KeyValuePair<SKImage, Map[]> P = await LayoutDoc.Render(Settings);
570
571 using (SKImage Img = P.Key)
572 {
573 Output.WriteStartElement("imageStandalone");
574
575 Output.WriteAttributeString("contentType", DefaultContentType);
576 Output.WriteAttributeString("width", Img.Width.ToString());
577 Output.WriteAttributeString("height", Img.Height.ToString());
578
579 using (SKData Data = Img.Encode(DefaultFormat, DefaultQuality))
580 {
581 byte[] Bin = Data.ToArray();
582
583 Output.WriteStartElement("binary");
584 Output.WriteValue(Convert.ToBase64String(Bin));
585 Output.WriteEndElement();
586 }
587
588 Output.WriteStartElement("caption");
589 if (string.IsNullOrEmpty(Title))
590 Output.WriteElementString("text", "Layout");
591 else
592 Output.WriteElementString("text", Title);
593
594 Output.WriteEndElement();
595 Output.WriteEndElement();
596 }
597
598 return true;
599 }
600 catch (XmlException ex)
601 {
602 ex = XML.AnnotateException(ex, Xml);
603 Log.Exception(ex);
604 return false;
605 }
606 catch (Exception ex)
607 {
608 Log.Exception(ex);
609 return false;
610 }
611 }
612
613 }
614}
Contains information about an emoji image.
Definition: ImageSource.cs:7
Image encoder/decoder.
Definition: ImageCodec.cs:14
const string FileExtensionPng
png
Definition: ImageCodec.cs:75
const string ContentTypePng
image/png
Definition: ImageCodec.cs:30
Renders Contracts XML from a Markdown document.
Renders LaTeX from a Markdown document.
static string EscapeLaTeX(string s)
Escapes text for output in a LaTeX document.
Class managing 2D XML Layout integration into Markdown documents.
Definition: XmlLayout.cs:44
Grade Supports(XmlDocument Xml)
Checks how well the handler supports multimedia content of a given type.
Definition: XmlLayout.cs:513
Grade Supports(string Language)
Checks how well the handler supports multimedia content of a given type.
Definition: XmlLayout.cs:150
async Task< object > TransformXml(XmlDocument Xml, Variables Session)
Transforms the XML document before visualizing it.
Definition: XmlLayout.cs:524
static void Init(string ContentRootFolder)
Initializes the Layout2D-Markdown integration.
Definition: XmlLayout.cs:67
async Task< bool > RenderHtml(HtmlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates HTML for the code content.
Definition: XmlLayout.cs:188
async Task< bool > RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates plain text for the code content.
Definition: XmlLayout.cs:363
XmlLayout()
Class managing 2D XML Layout integration into Markdown documents.
Definition: XmlLayout.cs:58
async Task< bool > RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Markdown for the code content.
Definition: XmlLayout.cs:339
static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
Deletes generated files older than MaxAge .
Definition: XmlLayout.cs:106
void Register(MarkdownDocument Document)
Is called on the object when an instance of the element has been created in a document.
Definition: XmlLayout.cs:174
async Task< bool > RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates smart contract XML for the code content.
Definition: XmlLayout.cs:545
async Task< bool > RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates LaTeX for the code content.
Definition: XmlLayout.cs:456
bool EvaluatesScript
If script is evaluated for this type of code block.
Definition: XmlLayout.cs:168
async Task< PixelInformation > GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
Generates an image of the contents.
Definition: XmlLayout.cs:494
async Task< bool > RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates WPF XAML for the code content.
Definition: XmlLayout.cs:383
async Task< bool > RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Xamarin.Forms XAML for the code content.
Definition: XmlLayout.cs:422
Contains a markdown document. This markdown document class supports original markdown,...
static string AppendRows(string[] Rows)
Appends a set of rows into a single string with newlines between rows.
MarkdownSettings Settings
Markdown settings.
Variables Variables
Collection of variables. Providing such a collection enables script execution inside markdown documen...
static async Task< bool > GenerateMarkdownFromFile(StringBuilder Output, string FileName, string Title)
Generates Markdown embedding an image available in a file.
Definition: ImageContent.cs:56
static string GenerateUrl(string Language, string[] Rows, out string ContentType, out string Title)
Generates a data URL of an encoded image.
static void GenerateMarkdown(StringBuilder Output, byte[] Bin, string ContentType, string Title)
Generates Markdown embedding an encoded image.
Definition: ImageContent.cs:82
Renders HTML from a Markdown document.
Definition: HtmlRenderer.cs:25
Renders portable Markdown from a Markdown document.
Abstract base class for Markdown renderers.
Definition: Renderer.cs:15
readonly StringBuilder Output
Renderer output.
Definition: Renderer.cs:19
Renders plain text from a Markdown document.
Definition: TextRenderer.cs:18
Renders XAML (WPF flavour) from a Markdown document.
Renders XAML (Xamarin.Forms flavour) from a Markdown document.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:211
static XmlException AnnotateException(XmlException ex)
Creates a new XML Exception object, with reference to the source XML file, for information.
Definition: XML.cs:1762
static string HtmlAttributeEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe.
Definition: XML.cs:121
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
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
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Contains a 2D layout document.
bool Dynamic
If the layout is dynamic (i.e. contains script).
async Task< KeyValuePair< SKImage, Map[]> > Render(RenderSettings Settings)
Renders the layout to an image
static bool IsLayoutXml(XmlDocument Xml)
Checks if an XML document contains a layout document.
static Task< Layout2DDocument > FromXml(string Xml, params KeyValuePair< string, object >[] Attachments)
Parses a 2D layout document from its XML definition.
async Task< RenderSettings > GetRenderSettings(Variables Session)
Creates a render settings object.
Contains static methods
Definition: Files.cs:14
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Files.cs:33
static async Task< byte[]> ReadAllBytesAsync(string FileName)
Reads a binary file asynchronously.
Definition: Files.cs:20
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:34
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
Base class for graphs.
Definition: Graph.cs:88
const string GraphFgColorVariableName
Variable name for graph foreground color.
Definition: Graph.cs:107
const string GraphBgColorVariableName
Variable name for graph background color.
Definition: Graph.cs:102
static string ToRGBAStyle(SKColor Color)
Converts a color to an RGB(A) style string.
Definition: Graph.cs:919
Contains pixel information
static PixelInformation FromImage(SKImage Image)
Gets the pixel information from an SKImage.
Contains pixel information in PNG format
Contains information about a variable.
Definition: Variable.cs:10
Collection of variables.
Definition: Variables.cs:25
virtual bool TryGetVariable(string Name, out Variable Variable)
Tries to get a variable object, given its name.
Definition: Variables.cs:56
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static string ComputeSHA256HashString(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:449
Interface for all markdown handlers of code content that generates an image output.
Interface for all XML visalizers.
Interface for code content plain text renderers.
Interface for code content WPF XAML renderers.
Grade
Grade enumeration
Definition: Grade.cs:7