3using System.Diagnostics;
7using System.Threading.Tasks;
47 internal class GraphInfo
49 public string FileName;
50 public string TextFileName;
52 public string MapFileName;
62 private const int chartGenerationTimeout = 30000;
65 private static readonly Random rnd =
new Random();
66 private static Scheduler scheduler =
null;
67 private static string installationFolder =
null;
68 private static string binFolder =
null;
69 private static string graphVizFolder =
null;
70 private static string contentRootFolder =
null;
71 private static bool supportsDot =
false;
72 private static bool supportsNeato =
false;
73 private static bool supportsFdp =
false;
74 private static bool supportsSfdp =
false;
75 private static bool supportsTwopi =
false;
76 private static bool supportsCirco =
false;
91 public static void Init(
string ContentRootFolder)
95 contentRootFolder = ContentRootFolder;
97 if (scheduler is
null)
105 Log.Terminating += (Sender, e) =>
109 return Task.CompletedTask;
116 if (
string.IsNullOrEmpty(Folder))
117 Log.
Warning(
"GraphViz not found. GraphViz support will not be available in Markdown.");
123 new KeyValuePair<string, object>(
"Installation Folder", installationFolder),
124 new KeyValuePair<string, object>(
"Binary Folder", binFolder),
125 new KeyValuePair<string, object>(
"dot", supportsDot),
126 new KeyValuePair<string, object>(
"neato", supportsNeato),
127 new KeyValuePair<string, object>(
"fdp", supportsFdp),
128 new KeyValuePair<string, object>(
"sfdp", supportsSfdp),
129 new KeyValuePair<string, object>(
"twopi", supportsTwopi),
130 new KeyValuePair<string, object>(
"circo", supportsCirco));
149 await queue.CloseForTermination(
true, chartGenerationTimeout);
150 await queue.DisposeAsync();
166 if (!
string.IsNullOrEmpty(installationFolder) && Folder != installationFolder)
167 throw new Exception(
"GraphViz installation folder has already been set.");
171 installationFolder = Folder;
173 switch (Environment.OSVersion.Platform)
175 case PlatformID.Win32S:
176 case PlatformID.Win32Windows:
177 case PlatformID.Win32NT:
178 case PlatformID.WinCE:
180 binFolder = Path.Combine(installationFolder,
"bin");
183 case PlatformID.Unix:
184 case PlatformID.MacOSX:
185 binFolder = installationFolder;
189 supportsDot = File.Exists(Path.Combine(binFolder,
"dot" + Suffix));
190 supportsNeato = File.Exists(Path.Combine(binFolder,
"neato" + Suffix));
191 supportsFdp = File.Exists(Path.Combine(binFolder,
"fdp" + Suffix));
192 supportsSfdp = File.Exists(Path.Combine(binFolder,
"sfdp" + Suffix));
193 supportsTwopi = File.Exists(Path.Combine(binFolder,
"twopi" + Suffix));
194 supportsCirco = File.Exists(Path.Combine(binFolder,
"circo" + Suffix));
196 graphVizFolder = Path.Combine(contentRootFolder,
"GraphViz");
198 if (!Directory.Exists(graphVizFolder))
199 Directory.CreateDirectory(graphVizFolder);
201 DeleteOldFiles(TimeSpan.FromDays(7));
204 private static void DeleteOldFiles(
object P)
206 if (P is TimeSpan MaxAge)
207 DeleteOldFiles(MaxAge,
true);
217 if (
string.IsNullOrEmpty(graphVizFolder))
220 DateTime Limit = DateTime.Now - MaxAge;
223 DirectoryInfo GraphVizFolder =
new DirectoryInfo(graphVizFolder);
224 FileInfo[]
Files = GraphVizFolder.GetFiles(
"*.*");
226 foreach (FileInfo FileInfo
in Files)
228 if (FileInfo.LastAccessTime < Limit)
232 File.Delete(FileInfo.FullName);
237 Log.
Error(
"Unable to delete old file: " + ex.Message, FileInfo.FullName);
243 Log.
Informational(Count.ToString() +
" old file(s) deleted.", graphVizFolder);
249 scheduler.
Add(DateTime.Now.AddDays(rnd.NextDouble() * 2), DeleteOldFiles, MaxAge);
260 string InstallationFolder;
262 switch (Environment.OSVersion.Platform)
264 case PlatformID.Win32S:
265 case PlatformID.Win32Windows:
266 case PlatformID.Win32NT:
267 case PlatformID.WinCE:
270 if (
string.IsNullOrEmpty(InstallationFolder))
273 if (
string.IsNullOrEmpty(InstallationFolder))
276 if (
string.IsNullOrEmpty(InstallationFolder))
279 if (
string.IsNullOrEmpty(InstallationFolder))
282 if (
string.IsNullOrEmpty(InstallationFolder))
290 case PlatformID.Unix:
291 case PlatformID.MacOSX:
292 InstallationFolder =
"/opt/local/bin";
293 if (!Directory.Exists(InstallationFolder))
294 InstallationFolder =
null;
298 return InstallationFolder;
307 Folder = Environment.GetFolderPath(SpecialFolder);
324 if (
string.IsNullOrEmpty(Folder))
327 if (!Directory.Exists(Folder))
331 string BestFolder =
null;
332 double BestVersion = 0;
337 SubFolders = Directory.GetDirectories(Folder);
339 catch (UnauthorizedAccessException)
349 foreach (
string SubFolder
in SubFolders)
351 FolderName = Path.GetFileName(SubFolder);
352 if (!FolderName.StartsWith(
"Graphviz", StringComparison.CurrentCultureIgnoreCase))
358 if (BestFolder is
null || Version > BestVersion)
360 BestFolder = SubFolder;
361 BestVersion = Version;
375 int i = Language.IndexOf(
':');
377 Language = Language.Substring(0, i).TrimEnd();
379 switch (Language.ToLower())
383 return Grade.Excellent;
388 return Grade.Excellent;
393 return Grade.Excellent;
398 return Grade.Excellent;
403 return Grade.Excellent;
408 return Grade.Excellent;
412 return Grade.NotAtAll;
448 int i = Language.IndexOf(
':');
450 Title = Language.Substring(i + 1).Trim();
467 private class AsyncState
470 public string Language;
471 public string[] Rows;
475 private async Task ExecuteGraphViz(
object State)
477 AsyncState AsyncState = (AsyncState)State;
478 StringBuilder Output =
new StringBuilder();
482 GraphInfo Info = await GetFileName(AsyncState.Language, AsyncState.Rows,
ResultType.Svg,
true,
483 AsyncState.Document.Settings?.Variables);
486 await this.GenerateHTML(Output, Info);
492 XmlEntitiesOnly =
true
502 private async Task GenerateHTML(StringBuilder Output, GraphInfo Info)
504 Info.FileName = Info.FileName.Substring(contentRootFolder.Length).Replace(Path.DirectorySeparatorChar,
'/');
505 if (!Info.FileName.StartsWith(
"/"))
506 Info.FileName =
"/" + Info.FileName;
508 Output.Append(
"<figure>");
509 Output.Append(
"<img src=\"");
512 if (!
string.IsNullOrEmpty(Info.Title))
514 Output.Append(
"\" alt=\"");
517 Output.Append(
"\" title=\"");
521 Output.Append(
"\" alt=\"GraphViz graph");
523 if (!
string.IsNullOrEmpty(Info.MapFileName))
525 Output.Append(
"\" usemap=\"#Map");
526 Output.Append(Info.Hash);
529 Output.Append(
"\" class=\"aloneUnsized\"/>");
531 if (!
string.IsNullOrEmpty(Info.Title))
533 Output.Append(
"<figcaption>");
535 Output.Append(
"</figcaption>");
538 Output.AppendLine(
"</figure>");
540 if (!
string.IsNullOrEmpty(Info.MapFileName))
542 Output.Append(
"<map id=\"Map");
543 Output.Append(Info.Hash);
544 Output.Append(
"\" name=\"Map");
545 Output.Append(Info.Hash);
546 Output.AppendLine(
"\">");
549 string[] MapRows = Map.Split(
CommonTypes.
CRLF, StringSplitOptions.RemoveEmptyEntries);
552 for (i = 1, c = MapRows.Length - 1; i < c; i++)
553 Output.AppendLine(MapRows[i]);
555 Output.AppendLine(
"</map>");
559 internal static Task<GraphInfo> GetFileName(
string Language,
string[] Rows,
ResultType Type,
bool GenerateIfNotExists,
Variables Variables)
564 internal static async Task<GraphInfo> GetFileName(
string Language,
string GraphText,
ResultType Type,
bool GenerateIfNotExists,
Variables Variables)
566 GraphInfo Result =
new GraphInfo();
567 int i = Language.IndexOf(
':');
571 Result.Title = Language.Substring(i + 1).Trim();
572 Language = Language.Substring(0, i).TrimEnd();
575 Result.Title =
string.Empty;
582 string GraphVizFolder = Path.Combine(contentRootFolder,
"GraphViz");
583 string FileName = Path.Combine(GraphVizFolder, Result.Hash);
597 Result.TextFileName = FileName +
".txt";
598 Result.MapFileName = FileName +
".map";
600 if (File.Exists(Result.FileName))
602 FileInfo Info =
new FileInfo(Result.FileName);
605 if (!File.Exists(Result.MapFileName))
606 Result.MapFileName =
null;
612 if (!GenerateIfNotExists)
615 ChartRecord Rec =
new ChartRecord(Result, Language, GraphText, GraphFgColor, GraphBgColor, Type);
618 if (!await Rec.Wait())
619 throw new Exception(
"Unable to process GraphViz chart.");
626 private readonly GraphInfo info;
627 private readonly TaskCompletionSource<bool> result;
628 private readonly
string language;
629 private readonly
string graphText;
630 private readonly
string graphFgColor;
631 private readonly
string graphBgColor;
634 public ChartRecord(GraphInfo Result,
string Language,
string GraphText,
635 string GraphFgColor,
string GraphBgColor,
ResultType Type)
637 this.result =
new TaskCompletionSource<bool>();
639 this.language = Language;
640 this.graphText = GraphText;
641 this.graphFgColor = GraphFgColor;
642 this.graphBgColor = GraphBgColor;
649 public Task Execute()
651 return this.Execute(CancellationToken.None);
658 public async Task Execute(CancellationToken Cancel)
662 StringBuilder Arguments =
new StringBuilder();
664 Arguments.Append(
"-Tcmapx -o\"");
665 Arguments.Append(this.info.MapFileName);
666 Arguments.Append(
"\" -T");
667 Arguments.Append(this.type.ToString().ToLower());
669 if (!
string.IsNullOrEmpty(this.graphBgColor))
671 Arguments.Append(
" -Gbgcolor=\"");
672 Arguments.Append(this.graphBgColor);
673 Arguments.Append(
'"');
676 if (!
string.IsNullOrEmpty(this.graphFgColor))
678 Arguments.Append(
" -Gcolor=\"");
679 Arguments.Append(this.graphFgColor);
682 Arguments.Append(
"\" -Nfontcolor=\"");
683 Arguments.Append(this.graphFgColor);
684 Arguments.Append(
"\" -Nlabelfontcolor=\"");
685 Arguments.Append(this.graphFgColor);
686 Arguments.Append(
"\" -Npencolor=\"");
687 Arguments.Append(this.graphFgColor);
688 Arguments.Append(
"\" -Efontcolor=\"");
689 Arguments.Append(this.graphFgColor);
690 Arguments.Append(
"\" -Elabelfontcolor=\"");
691 Arguments.Append(this.graphFgColor);
692 Arguments.Append(
"\" -Epencolor=\"");
693 Arguments.Append(this.graphFgColor);
694 Arguments.Append(
'"');
697 Arguments.Append(
" -q -o\"");
698 Arguments.Append(this.info.FileName);
699 Arguments.Append(
"\" \"");
700 Arguments.Append(this.info.TextFileName +
"\"");
702 ProcessStartInfo ProcessInformation =
new ProcessStartInfo()
705 Arguments = Arguments.ToString(),
706 UseShellExecute =
false,
707 RedirectStandardError =
true,
708 RedirectStandardOutput =
true,
709 RedirectStandardInput =
false,
710 WorkingDirectory = graphVizFolder,
711 CreateNoWindow =
true,
712 WindowStyle = ProcessWindowStyle.Hidden
715 Process P =
new Process();
716 TaskCompletionSource<int> ExitSource =
new TaskCompletionSource<int>();
718 P.Exited += (Sender, e) =>
720 ExitSource.TrySetResult(P.ExitCode);
723 Task
_ = Task.Delay(chartGenerationTimeout).ContinueWith(Prev =>
729 if (File.Exists(
this.info.FileName))
730 File.Delete(this.info.FileName);
732 if (!
string.IsNullOrEmpty(this.info.MapFileName) &&
733 File.Exists(
this.info.MapFileName))
735 File.Delete(this.info.MapFileName);
744 ExitSource.TrySetException(
new TimeoutException(
"GraphViz process did not terminate properly."));
747 return Task.CompletedTask;
750 P.StartInfo = ProcessInformation;
751 P.EnableRaisingEvents =
true;
754 int ExitCode = await ExitSource.Task;
758 string Error = P.StandardError.ReadToEnd();
759 this.result.TrySetException(
new Exception(Error));
764 if (File.Exists(
this.info.MapFileName))
767 string[] MapRows = Map.Split(
CommonTypes.
CRLF, StringSplitOptions.RemoveEmptyEntries);
768 if (MapRows.Length <= 2)
770 File.Delete(this.info.MapFileName);
771 this.info.MapFileName =
null;
775 this.info.MapFileName =
null;
787 public void Processed(
bool Result)
789 this.result.TrySetResult(Result);
796 public Task<bool> Wait()
798 return this.result.Task;
806 public Task<bool> Wait(CancellationToken Cancel)
808 if (Cancel.CanBeCanceled)
809 Cancel.Register(() => this.result.TrySetException(
new OperationCanceledException(Cancel)));
811 return this.result.Task;
823 if (v.ValueObject is SKColor Color)
825 else if (v.ValueObject is
string s)
884 XmlWriter Output =
Renderer.XmlOutput;
886 Output.WriteStartElement(
"Image");
887 Output.WriteAttributeString(
"Source", Info.FileName);
888 Output.WriteAttributeString(
"Stretch",
"None");
890 if (!
string.IsNullOrEmpty(Info.Title))
891 Output.WriteAttributeString(
"ToolTip", Info.Title);
893 Output.WriteEndElement();
913 XmlWriter Output =
Renderer.XmlOutput;
915 Output.WriteStartElement(
"Image");
916 Output.WriteAttributeString(
"Source", Info.FileName);
917 Output.WriteEndElement();
936 Output.AppendLine(
"\\begin{figure}[h]");
937 Output.AppendLine(
"\\centering");
939 Output.Append(
"\\fbox{\\includegraphics{");
940 Output.Append(Info.FileName.Replace(
'\\',
'/'));
941 Output.AppendLine(
"}}");
943 if (!
string.IsNullOrEmpty(Info.Title))
945 Output.Append(
"\\caption{");
947 Output.AppendLine(
"}");
950 Output.AppendLine(
"\\end{figure}");
969 byte[] Data = await Runtime.IO.Files.ReadAllBytesAsync(Info.FileName);
971 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
994 byte[] Data = await Runtime.IO.Files.ReadAllBytesAsync(Info.FileName);
1001 XmlWriter Output =
Renderer.XmlOutput;
1002 int Width = Image.Width;
1003 int Height = Image.Height;
1005 Output.WriteStartElement(
"imageStandalone");
1007 Output.WriteAttributeString(
"contentType", ContentType);
1008 Output.WriteAttributeString(
"width", Width.ToString());
1009 Output.WriteAttributeString(
"height", Height.ToString());
1011 Output.WriteStartElement(
"binary");
1012 Output.WriteValue(Convert.ToBase64String(Data));
1013 Output.WriteEndElement();
1015 Output.WriteStartElement(
"caption");
1016 if (
string.IsNullOrEmpty(Info.Title))
1017 Output.WriteElementString(
"text",
"Graph");
1019 Output.WriteElementString(
"text", Info.Title);
1021 Output.WriteEndElement();
1022 Output.WriteEndElement();
1026 catch (Exception ex)
Helps with parsing of commong data types.
static readonly char[] CRLF
Contains the CR LF character sequence.
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
const string FileExtensionPng
png
const string ContentTypePng
image/png
const string FileExtensionSvg
svg
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Renders Contracts XML from a Markdown document.
Class managing GraphViz integration into Markdown documents.
async Task< bool > RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates LaTeX for the code content.
GraphViz()
Class managing GraphViz integration into Markdown documents.
bool EvaluatesScript
If script is evaluated for this type of code block.
async Task< bool > RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates WPF XAML for the code content.
async Task< bool > RenderHtml(HtmlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates HTML for the code content.
static string SearchForInstallationFolder()
Searches for the installation folder on the local machine.
static void SetInstallationFolder(string Folder)
Sets the installation folder of GraphViz.
async Task< bool > RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Xamarin.Forms XAML for the code content.
static async Task Terminate()
Terminates GraphViz processing.
static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
Deletes generated files older than MaxAge .
Grade Supports(string Language)
Checks how well the handler supports multimedia content of a given type.
async Task< bool > RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates smart contract XML for the code content.
async Task< bool > RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Markdown for the code content.
static void Init(string ContentRootFolder)
Initializes the GraphViz-Markdown integration.
void Register(MarkdownDocument Document)
Is called on the object when an instance of the element has been created in a document.
async Task< bool > RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates plain text for the code content.
async Task< PixelInformation > GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
Generates an image of the contents.
Renders LaTeX from a Markdown document.
static string EscapeLaTeX(string s)
Escapes text for output in a LaTeX document.
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.
void QueueAsyncTask(AsyncMarkdownProcessing Callback, object State)
Queues an asynchronous task to be executed. Asynchronous tasks will be executed after the main docume...
Variables Variables
Collection of variables. Providing such a collection enables script execution inside markdown documen...
Base64-encoded image content.
static async Task< bool > GenerateMarkdownFromFile(StringBuilder Output, string FileName, string Title)
Generates Markdown embedding an image available in a file.
Renders HTML from a Markdown document.
Contains settings that the HTML export uses to customize HTML output.
Renders portable Markdown from a Markdown document.
Abstract base class for Markdown renderers.
readonly StringBuilder Output
Renderer output.
Renders plain text from a Markdown document.
Renders XAML (WPF flavour) from a Markdown document.
Renders XAML (Xamarin.Forms flavour) from a Markdown document.
Static class helping modules to find files installed on the system.
static string ExecutableExtension
Extension used by executable files on the platform.
Helps with common XML-related tasks.
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
static string HtmlAttributeEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe.
Static class managing the application event log. Applications and services log events on this static ...
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.
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
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.
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.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Static class that dynamically manages types and interfaces available in the runtime environment.
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Processes work tasks, in an asynchronous manner.
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
void Dispose()
IDisposable.Dispose
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
const string GraphFgColorVariableName
Variable name for graph foreground color.
const string GraphBgColorVariableName
Variable name for graph background color.
static string ToRGBAStyle(SKColor Color)
Converts a color to an RGB(A) style string.
Contains information about a variable.
virtual bool TryGetVariable(string Name, out Variable Variable)
Tries to get a variable object, given its name.
Contains methods for simple hash calculations.
static string ComputeSHA256HashString(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Interface for code content contract renderers.
Interface for classes that help output asynchronous markdown output.
Task< string > GenerateStub(MarkdownOutputType Type, StringBuilder Output, string Title, MarkdownDocument Document)
Generates a stub in the output, that will be filled with the asynchronously generated content,...
Task ReportResult(MarkdownOutputType Type, string Id, string Result)
Method called when asynchronous result has been generated in a Markdown document.
Interface for code content LaTeX renderers.
Interface for all markdown handlers of code content that generates an image output.
Interface for code content HTML renderers.
Interface for code content Markdown renderers.
Interface for code content plain text renderers.
Interface for code content WPF XAML renderers.
Interface for code content Xamarin.Forms XAML renderers.
Interface for asynchronous operations.
ResultType
Image types supported by GraphViz
MarkdownOutputType
Markdown output type.