3using System.Collections.Generic;
4using System.Diagnostics;
7using System.Threading.Tasks;
25using static System.Environment;
29 internal enum ResultType
35 internal class GraphInfo
37 public string BaseFileName;
38 public string TxtFileName;
39 public string ImageFileName;
40 public string PlantUmlFolder;
42 public string AsyncId;
52 private static readonly Random rnd =
new Random();
53 private static Scheduler scheduler =
null;
54 private static string jarPath =
null;
55 private static string javaPath =
null;
56 private static string plantUmlFolder =
null;
57 private static string contentRootFolder =
null;
72 public static void Init(
string ContentRootFolder)
76 contentRootFolder = ContentRootFolder;
78 if (scheduler is
null)
86 Log.Terminating += (Sender, e) =>
96 if (
string.IsNullOrEmpty(JarPath))
97 Log.
Warning(
"PlantUML not found. PlantUML support will not be available in Markdown.");
98 else if (
string.IsNullOrEmpty(JavaPath))
99 Log.
Warning(
"Java not found. PlantUML support will not be available in Markdown.");
105 new KeyValuePair<string, object>(
"Path", jarPath),
106 new KeyValuePair<string, object>(
"Java", javaPath));
125 public static void SetPath(
string JarPath,
string JavaPath)
127 if (!
string.IsNullOrEmpty(jarPath) && JarPath != jarPath)
128 throw new Exception(
"PlantUML an Java paths have already been set.");
132 plantUmlFolder = Path.Combine(contentRootFolder,
"PlantUML");
134 if (!Directory.Exists(plantUmlFolder))
135 Directory.CreateDirectory(plantUmlFolder);
137 DeleteOldFiles(TimeSpan.FromDays(7));
140 private static void DeleteOldFiles(
object P)
142 if (P is TimeSpan MaxAge)
143 DeleteOldFiles(MaxAge,
true);
153 if (
string.IsNullOrEmpty(plantUmlFolder))
156 DateTime Limit = DateTime.Now - MaxAge;
159 DirectoryInfo PlantUmlFolder =
new DirectoryInfo(plantUmlFolder);
160 FileInfo[] Files = PlantUmlFolder.GetFiles(
"*.*");
162 foreach (FileInfo FileInfo
in Files)
164 if (FileInfo.LastAccessTime < Limit)
168 File.Delete(FileInfo.FullName);
173 Log.
Error(
"Unable to delete old file: " + ex.Message, FileInfo.FullName);
179 Log.
Informational(Count.ToString() +
" old file(s) deleted.", plantUmlFolder);
185 scheduler.
Add(DateTime.Now.AddDays(rnd.NextDouble() * 2), DeleteOldFiles, MaxAge);
197 List<string> Folders =
new List<string>();
201 SpecialFolder.ProgramFiles,
202 SpecialFolder.ProgramFilesX86
207 Folders.Add(Path.Combine(RuntimeFolder, SpecialFolder.ProgramFiles.ToString()));
208 Folders.Add(Path.Combine(RuntimeFolder, SpecialFolder.ProgramFilesX86.ToString()));
211 string PathVar = Environment.GetEnvironmentVariable(
"PATH");
212 if (!
string.IsNullOrEmpty(PathVar))
214 string[] Paths = PathVar.Split(Path.PathSeparator);
215 Folders.AddRange(Paths);
218 string[] Folders2 = Folders.ToArray();
231 if (!
string.IsNullOrEmpty(jarPath) && !
string.IsNullOrEmpty(javaPath))
233 int i = Language.IndexOf(
':');
235 Language = Language.Substring(0, i).TrimEnd();
237 switch (Language.ToLower())
239 case "uml":
return Grade.Excellent;
240 case "plantuml":
return Grade.Perfect;
244 return Grade.NotAtAll;
273 bool GenerateIfNotExists = asyncHtmlOutput is
null;
274 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Svg, GenerateIfNotExists);
275 if (GenerateIfNotExists || File.Exists(Info.ImageFileName))
277 this.GenerateHTML(Output, Info);
283 foreach (KeyValuePair<AsyncMarkdownProcessing, object> P
in Document.AsyncTasks)
285 if (P.Value is AsyncState AsyncState && AsyncState.Type == ResultType.Svg && AsyncState.GraphInfos.Count < 10)
287 AsyncState.GraphInfos.Add(Info);
292 Document.QueueAsyncTask(this.ExecutePlantUml,
new AsyncState(ResultType.Svg, Info));
297 private class AsyncState
299 public readonly List<GraphInfo> GraphInfos;
300 public readonly ResultType Type;
302 public AsyncState(ResultType Type, GraphInfo Info)
305 this.GraphInfos =
new List<GraphInfo>()
312 private async Task ExecutePlantUml(
object State)
316 XmlEntitiesOnly =
true
319 AsyncState AsyncState = (AsyncState)State;
323 await ExecutePlantUml(AsyncState.Type, AsyncState.GraphInfos.ToArray());
325 foreach (GraphInfo Info
in AsyncState.GraphInfos)
341 foreach (GraphInfo Info
in AsyncState.GraphInfos)
350 private static async Task ExecutePlantUml(ResultType Type, params GraphInfo[] Files)
352 StringBuilder Arguments =
new StringBuilder();
353 Arguments.Append(
"-jar \"");
354 Arguments.Append(jarPath);
355 Arguments.Append(
"\" -quiet -charset UTF-8 -t");
356 Arguments.Append(Type.ToString().ToLower());
358 foreach (GraphInfo Info
in Files)
360 Arguments.Append(
" \"");
361 Arguments.Append(Info.TxtFileName);
362 Arguments.Append(
'"');
365 ProcessStartInfo ProcessInformation =
new ProcessStartInfo()
368 Arguments = Arguments.ToString(),
369 UseShellExecute =
false,
370 RedirectStandardError =
true,
371 RedirectStandardOutput =
true,
372 RedirectStandardInput =
false,
373 WorkingDirectory = Files[0].PlantUmlFolder,
374 CreateNoWindow =
true,
375 WindowStyle = ProcessWindowStyle.Hidden
378 Process P =
new Process();
379 TaskCompletionSource<int> ExitSource =
new TaskCompletionSource<int>();
380 StringBuilder StandardOutput =
new StringBuilder();
381 StringBuilder ErrorOutput =
new StringBuilder();
383 P.ErrorDataReceived += (Sender, e) =>
385 ErrorOutput.AppendLine(e.Data);
388 P.OutputDataReceived += (Sender, e) =>
390 StandardOutput.AppendLine(e.Data);
393 P.Exited += (Sender, e) =>
395 ExitSource.TrySetResult(P.ExitCode);
398 Task _ = Task.Delay(10000).ContinueWith(Prev =>
400 ExitSource.TrySetException(
new TimeoutException(
"PlantUML process did not terminate properly."));
403 P.StartInfo = ProcessInformation;
404 P.EnableRaisingEvents =
true;
407 int ExitCode = await ExitSource.Task;
411 string Error = P.StandardError.ReadToEnd();
412 throw new Exception(Error);
416 private void GenerateHTML(StringBuilder Output, GraphInfo Info)
418 Info.ImageFileName = Info.ImageFileName.Substring(contentRootFolder.Length).Replace(Path.DirectorySeparatorChar,
'/');
419 if (!Info.ImageFileName.StartsWith(
"/"))
420 Info.ImageFileName =
"/" + Info.ImageFileName;
422 Output.Append(
"<figure>");
423 Output.Append(
"<img src=\"");
426 if (!
string.IsNullOrEmpty(Info.Title))
428 Output.Append(
"\" alt=\"");
431 Output.Append(
"\" title=\"");
435 Output.Append(
"\" alt=\"PlantUML graph");
437 Output.Append(
"\" class=\"aloneUnsized\"/>");
439 if (!
string.IsNullOrEmpty(Info.Title))
441 Output.Append(
"<figcaption>");
443 Output.Append(
"</figcaption>");
446 Output.AppendLine(
"</figure>");
449 internal static Task<GraphInfo> GetGraphInfo(
string Language,
string[] Rows, ResultType Type)
454 internal static async Task<GraphInfo> GetGraphInfo(
string Language,
string Graph, ResultType Type)
457 string PlantUmlFolder = Path.Combine(contentRootFolder,
"PlantUML");
459 GraphInfo Result =
new GraphInfo()
461 BaseFileName = Path.Combine(PlantUmlFolder, FileName),
462 PlantUmlFolder = PlantUmlFolder
464 int i = Language.IndexOf(
':');
467 Result.Title = Language.Substring(i + 1).Trim();
469 Result.Title =
string.Empty;
471 Result.TxtFileName = Result.BaseFileName +
".txt";
472 if (!File.Exists(Result.TxtFileName))
490 internal static Task<GraphInfo> GetGraphInfo(
string Language,
string[] Rows, ResultType Type,
bool GenerateIfNotExists)
495 internal static async Task<GraphInfo> GetGraphInfo(
string Language,
string GraphDefinition, ResultType Type,
bool GenerateIfNotExists)
497 GraphInfo Result = await GetGraphInfo(Language, GraphDefinition, Type);
499 if (GenerateIfNotExists && !File.Exists(Result.ImageFileName))
500 await ExecutePlantUml(Type, Result);
516 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
535 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Svg,
true);
555 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
559 XmlWriter Output =
Renderer.XmlOutput;
561 Output.WriteStartElement(
"Image");
562 Output.WriteAttributeString(
"Source", Info.ImageFileName);
563 Output.WriteAttributeString(
"Stretch",
"None");
565 if (!
string.IsNullOrEmpty(Info.Title))
566 Output.WriteAttributeString(
"ToolTip", Info.Title);
568 Output.WriteEndElement();
583 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
587 XmlWriter Output =
Renderer.XmlOutput;
589 Output.WriteStartElement(
"Image");
590 Output.WriteAttributeString(
"Source", Info.ImageFileName);
591 Output.WriteEndElement();
607 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
610 Output.AppendLine(
"\\begin{figure}[h]");
611 Output.AppendLine(
"\\centering");
613 Output.Append(
"\\fbox{\\includegraphics{");
614 Output.Append(Info.ImageFileName.Replace(
'\\',
'/'));
615 Output.AppendLine(
"}}");
617 if (!
string.IsNullOrEmpty(Info.Title))
619 Output.Append(
"\\caption{");
621 Output.AppendLine(
"}");
624 Output.AppendLine(
"\\end{figure}");
639 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
645 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
664 GraphInfo Info = await GetGraphInfo(Language, Rows, ResultType.Png,
true);
674 XmlWriter Output =
Renderer.XmlOutput;
675 int Width = Image.Width;
676 int Height = Image.Height;
678 Output.WriteStartElement(
"imageStandalone");
680 Output.WriteAttributeString(
"contentType", ContentType);
681 Output.WriteAttributeString(
"width", Width.ToString());
682 Output.WriteAttributeString(
"height", Height.ToString());
684 Output.WriteStartElement(
"binary");
685 Output.WriteValue(Convert.ToBase64String(Data));
686 Output.WriteEndElement();
688 Output.WriteStartElement(
"caption");
689 if (
string.IsNullOrEmpty(Info.Title))
690 Output.WriteElementString(
"text",
"Graph");
692 Output.WriteElementString(
"text", Info.Title);
694 Output.WriteEndElement();
695 Output.WriteEndElement();
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.
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.
Base64-encoded image content.
static async Task< bool > GenerateMarkdownFromFile(StringBuilder Output, string FileName, string Title)
Generates Markdown embedding an image available in a file.
Class managing PlantUML integration into Markdown documents.
async Task< PixelInformation > GenerateImage(string[] Rows, string Language, MarkdownDocument Document)
Generates an image of the contents.
Grade Supports(string Language)
Checks how well the handler supports multimedia content of a given type.
async Task< bool > RenderMarkdown(MarkdownRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Markdown for the code content.
static void DeleteOldFiles(TimeSpan MaxAge, bool Reschedule)
Deletes generated files older than MaxAge .
static void Init(string ContentRootFolder)
Initializes the PlantUML-Markdown integration.
static void SetPath(string JarPath, string JavaPath)
Sets the full path of PlantUML.
async Task< bool > RenderWpfXaml(WpfXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates WPF XAML for the code content.
async Task< bool > RenderXamarinFormsXaml(XamarinFormsXamlRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates Xamarin.Forms 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.
async Task< bool > RenderText(TextRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates plain text for the code content.
bool EvaluatesScript
If script is evaluated for this type of code block.
async Task< bool > RenderLatex(LatexRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates LaTeX for the code content.
static void SearchForInstallationFolder(out string JarPath, out string JavaPath)
Searches for the installation folder on the local machine.
void Register(MarkdownDocument Document)
Is called on the object when an instance of the element has been created in a document.
PlantUml()
Class managing PlantUML integration into Markdown documents.
async Task< bool > RenderContractXml(ContractsRenderer Renderer, string[] Rows, string Language, int Indent, MarkdownDocument Document)
Generates smart contract XML for the code content.
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.
void Clear()
Clears the underlying StringBuilder.
override string ToString()
Returns the 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 Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Static class helping modules to find files installed on the system.
static string[] GetFolders(Environment.SpecialFolder[] Folders, params string[] AppendWith)
Gets the physical locations of special folders.
static string ExecutableExtension
Extension used by executable files on the platform.
static string FindLatestFile(Environment.SpecialFolder[] Folders, string Pattern, bool IncludeSubfolders)
Finds the latest file matching a search pattern, by searching in a set of folders,...
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.
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.