2using System.Collections.Generic;
3using System.Diagnostics;
6using System.Threading.Tasks;
28 internal enum ResultType
34 internal class GraphInfo
36 public string FileName;
38 public string MapFileName;
48 private static readonly Random rnd =
new Random();
49 private static Scheduler scheduler =
null;
50 private static string installationFolder =
null;
51 private static string binFolder =
null;
52 private static string graphVizFolder =
null;
53 private static string contentRootFolder =
null;
54 private static bool supportsDot =
false;
55 private static bool supportsNeato =
false;
56 private static bool supportsFdp =
false;
57 private static bool supportsSfdp =
false;
58 private static bool supportsTwopi =
false;
59 private static bool supportsCirco =
false;
74 public static void Init(
string ContentRootFolder)
78 contentRootFolder = ContentRootFolder;
80 if (scheduler is
null)
88 Log.Terminating += (Sender, e) =>
98 if (
string.IsNullOrEmpty(Folder))
99 Log.
Warning(
"GraphViz not found. GraphViz support will not be available in Markdown.");
105 new KeyValuePair<string, object>(
"Installation Folder", installationFolder),
106 new KeyValuePair<string, object>(
"Binary Folder", binFolder),
107 new KeyValuePair<string, object>(
"dot", supportsDot),
108 new KeyValuePair<string, object>(
"neato", supportsNeato),
109 new KeyValuePair<string, object>(
"fdp", supportsFdp),
110 new KeyValuePair<string, object>(
"sfdp", supportsSfdp),
111 new KeyValuePair<string, object>(
"twopi", supportsTwopi),
112 new KeyValuePair<string, object>(
"circo", supportsCirco));
131 if (!
string.IsNullOrEmpty(installationFolder) && Folder != installationFolder)
132 throw new Exception(
"GraphViz installation folder has already been set.");
136 installationFolder = Folder;
138 switch (Environment.OSVersion.Platform)
140 case PlatformID.Win32S:
141 case PlatformID.Win32Windows:
142 case PlatformID.Win32NT:
143 case PlatformID.WinCE:
145 binFolder = Path.Combine(installationFolder,
"bin");
148 case PlatformID.Unix:
149 case PlatformID.MacOSX:
150 binFolder = installationFolder;
154 supportsDot = File.Exists(Path.Combine(binFolder,
"dot" + Suffix));
155 supportsNeato = File.Exists(Path.Combine(binFolder,
"neato" + Suffix));
156 supportsFdp = File.Exists(Path.Combine(binFolder,
"fdp" + Suffix));
157 supportsSfdp = File.Exists(Path.Combine(binFolder,
"sfdp" + Suffix));
158 supportsTwopi = File.Exists(Path.Combine(binFolder,
"twopi" + Suffix));
159 supportsCirco = File.Exists(Path.Combine(binFolder,
"circo" + Suffix));
161 graphVizFolder = Path.Combine(contentRootFolder,
"GraphViz");
163 if (!Directory.Exists(graphVizFolder))
164 Directory.CreateDirectory(graphVizFolder);
166 DeleteOldFiles(TimeSpan.FromDays(7));
169 private static void DeleteOldFiles(
object P)
171 if (P is TimeSpan MaxAge)
172 DeleteOldFiles(MaxAge,
true);
182 if (
string.IsNullOrEmpty(graphVizFolder))
185 DateTime Limit = DateTime.Now - MaxAge;
188 DirectoryInfo GraphVizFolder =
new DirectoryInfo(graphVizFolder);
189 FileInfo[] Files = GraphVizFolder.GetFiles(
"*.*");
191 foreach (FileInfo FileInfo
in Files)
193 if (FileInfo.LastAccessTime < Limit)
197 File.Delete(FileInfo.FullName);
202 Log.
Error(
"Unable to delete old file: " + ex.Message, FileInfo.FullName);
208 Log.
Informational(Count.ToString() +
" old file(s) deleted.", graphVizFolder);
214 scheduler.
Add(DateTime.Now.AddDays(rnd.NextDouble() * 2), DeleteOldFiles, MaxAge);
225 string InstallationFolder;
227 switch (Environment.OSVersion.Platform)
229 case PlatformID.Win32S:
230 case PlatformID.Win32Windows:
231 case PlatformID.Win32NT:
232 case PlatformID.WinCE:
235 if (
string.IsNullOrEmpty(InstallationFolder))
238 if (
string.IsNullOrEmpty(InstallationFolder))
241 if (
string.IsNullOrEmpty(InstallationFolder))
244 if (
string.IsNullOrEmpty(InstallationFolder))
247 if (
string.IsNullOrEmpty(InstallationFolder))
255 case PlatformID.Unix:
256 case PlatformID.MacOSX:
257 InstallationFolder =
"/opt/local/bin";
258 if (!Directory.Exists(InstallationFolder))
259 InstallationFolder =
null;
263 return InstallationFolder;
272 Folder = Environment.GetFolderPath(SpecialFolder);
289 if (
string.IsNullOrEmpty(Folder))
292 if (!Directory.Exists(Folder))
296 string BestFolder =
null;
297 double BestVersion = 0;
302 SubFolders = Directory.GetDirectories(Folder);
304 catch (UnauthorizedAccessException)
314 foreach (
string SubFolder
in SubFolders)
316 FolderName = Path.GetFileName(SubFolder);
317 if (!FolderName.StartsWith(
"Graphviz", StringComparison.CurrentCultureIgnoreCase))
323 if (BestFolder is
null || Version > BestVersion)
325 BestFolder = SubFolder;
326 BestVersion = Version;
340 int i = Language.IndexOf(
':');
342 Language = Language.Substring(0, i).TrimEnd();
344 switch (Language.ToLower())
348 return Grade.Excellent;
353 return Grade.Excellent;
358 return Grade.Excellent;
363 return Grade.Excellent;
368 return Grade.Excellent;
373 return Grade.Excellent;
377 return Grade.NotAtAll;
405 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Svg, asyncHtmlOutput is
null, Document.
Settings?.
Variables);
413 int i = Language.IndexOf(
':');
415 Title = Language.Substring(i + 1).Trim();
432 private class AsyncState
435 public string Language;
436 public string[] Rows;
440 private async Task ExecuteGraphViz(
object State)
442 AsyncState AsyncState = (AsyncState)State;
443 StringBuilder Output =
new StringBuilder();
447 GraphInfo Info = await GetFileName(AsyncState.Language, AsyncState.Rows, ResultType.Svg,
true,
448 AsyncState.Document.Settings?.Variables);
451 await this.GenerateHTML(Output, Info);
457 XmlEntitiesOnly =
true
467 private async Task GenerateHTML(StringBuilder Output, GraphInfo Info)
469 Info.FileName = Info.FileName.Substring(contentRootFolder.Length).Replace(Path.DirectorySeparatorChar,
'/');
470 if (!Info.FileName.StartsWith(
"/"))
471 Info.FileName =
"/" + Info.FileName;
473 Output.Append(
"<figure>");
474 Output.Append(
"<img src=\"");
477 if (!
string.IsNullOrEmpty(Info.Title))
479 Output.Append(
"\" alt=\"");
482 Output.Append(
"\" title=\"");
486 Output.Append(
"\" alt=\"GraphViz graph");
488 if (!
string.IsNullOrEmpty(Info.MapFileName))
490 Output.Append(
"\" usemap=\"#Map");
491 Output.Append(Info.Hash);
494 Output.Append(
"\" class=\"aloneUnsized\"/>");
496 if (!
string.IsNullOrEmpty(Info.Title))
498 Output.Append(
"<figcaption>");
500 Output.Append(
"</figcaption>");
503 Output.AppendLine(
"</figure>");
505 if (!
string.IsNullOrEmpty(Info.MapFileName))
507 Output.Append(
"<map id=\"Map");
508 Output.Append(Info.Hash);
509 Output.Append(
"\" name=\"Map");
510 Output.Append(Info.Hash);
511 Output.AppendLine(
"\">");
514 string[] MapRows = Map.Split(
CommonTypes.
CRLF, StringSplitOptions.RemoveEmptyEntries);
517 for (i = 1, c = MapRows.Length - 1; i < c; i++)
518 Output.AppendLine(MapRows[i]);
520 Output.AppendLine(
"</map>");
524 internal static Task<GraphInfo> GetFileName(
string Language,
string[] Rows, ResultType Type,
bool GenerateIfNotExists,
Variables Variables)
529 internal static async Task<GraphInfo> GetFileName(
string Language,
string GraphText, ResultType Type,
bool GenerateIfNotExists,
Variables Variables)
531 GraphInfo Result =
new GraphInfo();
532 int i = Language.IndexOf(
':');
536 Result.Title = Language.Substring(i + 1).Trim();
537 Language = Language.Substring(0, i).TrimEnd();
540 Result.Title =
string.Empty;
547 string GraphVizFolder = Path.Combine(contentRootFolder,
"GraphViz");
548 string FileName = Path.Combine(GraphVizFolder, Result.Hash);
562 Result.MapFileName = FileName +
".map";
564 if (File.Exists(Result.FileName))
566 if (!File.Exists(Result.MapFileName))
567 Result.MapFileName =
null;
572 if (!GenerateIfNotExists)
575 string TxtFileName = FileName +
".txt";
578 StringBuilder Arguments =
new StringBuilder();
580 Arguments.Append(
"-Tcmapx -o\"");
581 Arguments.Append(Result.MapFileName);
582 Arguments.Append(
"\" -T");
583 Arguments.Append(Type.ToString().ToLower());
585 if (!
string.IsNullOrEmpty(GraphBgColor))
587 Arguments.Append(
" -Gbgcolor=\"");
588 Arguments.Append(GraphBgColor);
589 Arguments.Append(
'"');
592 if (!
string.IsNullOrEmpty(GraphFgColor))
594 Arguments.Append(
" -Gcolor=\"");
595 Arguments.Append(GraphFgColor);
598 Arguments.Append(
"\" -Nfontcolor=\"");
599 Arguments.Append(GraphFgColor);
600 Arguments.Append(
"\" -Nlabelfontcolor=\"");
601 Arguments.Append(GraphFgColor);
602 Arguments.Append(
"\" -Npencolor=\"");
603 Arguments.Append(GraphFgColor);
604 Arguments.Append(
"\" -Efontcolor=\"");
605 Arguments.Append(GraphFgColor);
606 Arguments.Append(
"\" -Elabelfontcolor=\"");
607 Arguments.Append(GraphFgColor);
608 Arguments.Append(
"\" -Epencolor=\"");
609 Arguments.Append(GraphFgColor);
610 Arguments.Append(
"\"");
613 Arguments.Append(
" -q -o\"");
614 Arguments.Append(Result.FileName);
615 Arguments.Append(
"\" \"");
616 Arguments.Append(TxtFileName +
"\"");
618 ProcessStartInfo ProcessInformation =
new ProcessStartInfo()
621 Arguments = Arguments.ToString(),
622 UseShellExecute =
false,
623 RedirectStandardError =
true,
624 RedirectStandardOutput =
true,
625 RedirectStandardInput =
false,
626 WorkingDirectory = GraphVizFolder,
627 CreateNoWindow =
true,
628 WindowStyle = ProcessWindowStyle.Hidden
631 Process P =
new Process();
632 TaskCompletionSource<GraphInfo> ResultSource =
new TaskCompletionSource<GraphInfo>();
634 P.ErrorDataReceived += (Sender, e) =>
636 ResultSource.TrySetException(
new Exception(
"Unable to generate graph:\r\n\r\n" + e.Data));
639 P.Exited += async (Sender, e) =>
645 string ErrorText = await P.StandardError.ReadToEndAsync();
646 ResultSource.TrySetException(
new Exception(
"Unable to generate graph. Exit code: " + P.ExitCode.ToString() +
"\r\n\r\n" + ErrorText));
651 string[] MapRows = Map.Split(
CommonTypes.
CRLF, StringSplitOptions.RemoveEmptyEntries);
652 if (MapRows.Length <= 2)
654 File.Delete(Result.MapFileName);
655 Result.MapFileName =
null;
658 ResultSource.TrySetResult(Result);
667 Task _ = Task.Delay(10000).ContinueWith(Prev => ResultSource.TrySetException(
new TimeoutException(
"GraphViz process did not terminate properly.")));
669 P.StartInfo = ProcessInformation;
670 P.EnableRaisingEvents =
true;
673 return await ResultSource.Task;
684 if (v.ValueObject is SKColor Color)
686 else if (v.ValueObject is
string s)
703 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Svg,
true, Document.
Settings?.
Variables);
723 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
741 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
745 XmlWriter Output =
Renderer.XmlOutput;
747 Output.WriteStartElement(
"Image");
748 Output.WriteAttributeString(
"Source", Info.FileName);
749 Output.WriteAttributeString(
"Stretch",
"None");
751 if (!
string.IsNullOrEmpty(Info.Title))
752 Output.WriteAttributeString(
"ToolTip", Info.Title);
754 Output.WriteEndElement();
770 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
774 XmlWriter Output =
Renderer.XmlOutput;
776 Output.WriteStartElement(
"Image");
777 Output.WriteAttributeString(
"Source", Info.FileName);
778 Output.WriteEndElement();
794 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
797 Output.AppendLine(
"\\begin{figure}[h]");
798 Output.AppendLine(
"\\centering");
800 Output.Append(
"\\fbox{\\includegraphics{");
801 Output.Append(Info.FileName.Replace(
'\\',
'/'));
802 Output.AppendLine(
"}}");
804 if (!
string.IsNullOrEmpty(Info.Title))
806 Output.Append(
"\\caption{");
808 Output.AppendLine(
"}");
811 Output.AppendLine(
"\\end{figure}");
826 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
832 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
851 GraphInfo Info = await GetFileName(Language, Rows, ResultType.Png,
true, Document.
Settings?.
Variables);
861 XmlWriter Output =
Renderer.XmlOutput;
862 int Width = Image.Width;
863 int Height = Image.Height;
865 Output.WriteStartElement(
"imageStandalone");
867 Output.WriteAttributeString(
"contentType", ContentType);
868 Output.WriteAttributeString(
"width", Width.ToString());
869 Output.WriteAttributeString(
"height", Height.ToString());
871 Output.WriteStartElement(
"binary");
872 Output.WriteValue(Convert.ToBase64String(Data));
873 Output.WriteEndElement();
875 Output.WriteStartElement(
"caption");
876 if (
string.IsNullOrEmpty(Info.Title))
877 Output.WriteElementString(
"text",
"Graph");
879 Output.WriteElementString(
"text", Info.Title);
881 Output.WriteEndElement();
882 Output.WriteEndElement();
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.
const string FileExtensionPng
png
const string ContentTypePng
image/png
const string FileExtensionSvg
svg
Static class managing encoding and decoding of internet content.
static Task< object > 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 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 managing loading of resources stored as embedded resources or in content files.
static async Task< byte[]> ReadAllBytesAsync(string FileName)
Reads a binary file asynchronously.
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 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 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.
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, ScheduledEventCallback 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.
MarkdownOutputType
Markdown output type.