Files
ZelWiki/ZelWiki.Engine.Implementation/ScopeFunctionHandler.cs
2025-02-20 15:20:28 +08:00

373 lines
18 KiB
C#

using System.Text;
using NTDLS.Helpers;
using ZelWiki.Engine.Function;
using ZelWiki.Engine.Implementation.Utility;
using ZelWiki.Engine.Library;
using ZelWiki.Engine.Library.Interfaces;
using static ZelWiki.Engine.Library.Constants;
namespace ZelWiki.Engine.Implementation
{
/// <summary>
/// Handled scope function calls.
/// </summary>
public class ScopeFunctionHandler : IScopeFunctionHandler
{
private static FunctionPrototypeCollection? _collection;
public FunctionPrototypeCollection Prototypes
{
get
{
if (_collection == null)
{
_collection = new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Scoped);
#region Prototypes.
_collection.Add("$$Code: <string>{language(auto,wiki,cpp,lua,graphql,swift,r,yaml,kotlin,scss,shell,vbnet,json,objectivec,perl,diff,wasm,php,xml,bash,csharp,css,go,ini,javascript,less,makefile,markdown,plaintext,python,python-repl,ruby,rust,sql,typescript)}='auto'");
_collection.Add("$$Bullets: <string>{type(unordered,ordered)}='unordered'");
_collection.Add("$$Order: <string>{direction(ascending,descending)}='ascending'");
_collection.Add("$$Jumbotron:");
_collection.Add("$$Callout: <string>{styleName(default,primary,secondary,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Background: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add("$$Foreground: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger,muted)}='default'");
_collection.Add("$$Alert: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Card: <string>{styleName(default,primary,secondary,light,dark,success,info,warning,danger)}='default' | <string>{titleText}=''");
_collection.Add("$$Collapse: <string>{linkText}='Show'");
_collection.Add("$$Table: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
_collection.Add("$$StripedTable: <boolean>{hasBorder}='true' | <boolean>{isFirstRowHeader}='true'");
_collection.Add("$$DefineSnippet: <string>[name]");
#endregion
}
return _collection;
}
}
/// <summary>
/// Called to handle function calls when proper prototypes are matched.
/// </summary>
/// <param name="state">Reference to the wiki state object</param>
/// <param name="function">The parsed function call and all its parameters and their values.</param>
/// <param name="scopeBody">The the text that the function is designed to affect.</param>
public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
{
scopeBody.EnsureNotNull($"The function '{function.Name}' scope body can not be null");
switch (function.Name.ToLower())
{
//------------------------------------------------------------------------------------------------------------------------------
case "code":
{
var html = new StringBuilder();
string language = function.Parameters.Get<string>("language");
if (string.IsNullOrEmpty(language) || language?.ToLower() == "auto")
{
html.Append($"<pre>");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
else
{
html.Append($"<pre class=\"language-{language}\">");
html.Append($"<code>{scopeBody.Replace("\r\n", "\n").Replace("\n", SoftBreak)}</code></pre>");
}
return new HandlerResult(html.ToString())
{
Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
};
}
//------------------------------------------------------------------------------------------------------------------------------
case "stripedtable":
case "table":
{
var html = new StringBuilder();
var hasBorder = function.Parameters.Get<bool>("hasBorder");
var isFirstRowHeader = function.Parameters.Get<bool>("isFirstRowHeader");
html.Append($"<table class=\"table");
if (function.Name.Equals("stripedtable", StringComparison.InvariantCultureIgnoreCase))
{
html.Append(" table-striped");
}
if (hasBorder)
{
html.Append(" table-bordered");
}
html.Append($"\">");
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int rowNumber = 0;
foreach (var lineText in lines)
{
var columns = lineText.Split("||");
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<thead>");
}
else if (rowNumber == 1 && isFirstRowHeader || rowNumber == 0 && isFirstRowHeader == false)
{
html.Append($"<tbody>");
}
html.Append($"<tr>");
foreach (var columnText in columns)
{
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"<td><strong>{columnText}</strong></td>");
}
else
{
html.Append($"<td>{columnText}</td>");
}
}
if (rowNumber == 0 && isFirstRowHeader)
{
html.Append($"</thead>");
}
html.Append($"</tr>");
rowNumber++;
}
html.Append($"</tbody>");
html.Append($"</table>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "bullets":
{
var html = new StringBuilder();
string type = function.Parameters.Get<string>("type");
if (type == "unordered")
{
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int currentLevel = 0;
foreach (var line in lines)
{
int newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
//Count how many '>' are at the start of the line.
}
newIndent++;
if (newIndent < currentLevel)
{
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ul>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ul>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
}
for (; currentLevel > 0; currentLevel--)
{
html.Append($"</ul>");
}
}
else if (type == "ordered")
{
var lines = scopeBody.Split(['\n'], StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).Where(o => o.Length > 0);
int currentLevel = 0;
foreach (var line in lines)
{
int newIndent = 0;
for (; newIndent < line.Length && line[newIndent] == '>'; newIndent++)
{
//Count how many '>' are at the start of the line.
}
newIndent++;
if (newIndent < currentLevel)
{
for (; currentLevel != newIndent; currentLevel--)
{
html.Append($"</ol>");
}
}
else if (newIndent > currentLevel)
{
for (; currentLevel != newIndent; currentLevel++)
{
html.Append($"<ol>");
}
}
html.Append($"<li>{line.Trim(['>'])}</li>");
}
for (; currentLevel > 0; currentLevel--)
{
html.Append($"</ol>");
}
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "definesnippet":
{
var html = new StringBuilder();
string name = function.Parameters.Get<string>("name");
if (!state.Snippets.TryAdd(name, scopeBody))
{
state.Snippets[name] = scopeBody;
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "alert":
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : $"alert-{style}";
if (!string.IsNullOrEmpty(titleText)) scopeBody = $"<h1>{titleText}</h1>{scopeBody}";
html.Append($"<div class=\"alert {style}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
case "order":
{
var html = new StringBuilder();
string direction = function.Parameters.Get<string>("direction");
var lines = scopeBody.Split("\n").Select(o => o.Trim()).ToList();
if (direction == "ascending")
{
html.Append(string.Join("\r\n", lines.OrderBy(o => o)));
}
else
{
html.Append(string.Join("\r\n", lines.OrderByDescending(o => o)));
}
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "jumbotron":
{
var html = new StringBuilder();
string titleText = function.Parameters.Get("titleText", "");
html.Append($"<div class=\"mt-4 p-5 bg-secondary text-white rounded\">");
if (!string.IsNullOrEmpty(titleText)) html.Append($"<h1>{titleText}</h1>");
html.Append($"<p>{scopeBody}</p>");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "foreground":
{
var html = new StringBuilder();
var style = BGFGStyle.GetForegroundStyle(function.Parameters.Get("styleName", "default")).Swap();
html.Append($"<p class=\"{style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</p>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "background":
{
var html = new StringBuilder();
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"p-3 mb-2 {style.ForegroundStyle} {style.BackgroundStyle}\">{scopeBody}</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "collapse":
{
var html = new StringBuilder();
string linkText = function.Parameters.Get<string>("linktext");
string uid = "A" + Guid.NewGuid().ToString().Replace("-", "");
html.Append($"<a data-bs-toggle=\"collapse\" href=\"#{uid}\" role=\"button\" aria-expanded=\"false\" aria-controls=\"{uid}\">{linkText}</a>");
html.Append($"<div class=\"collapse\" id=\"{uid}\">");
html.Append($"<div class=\"card card-body\"><p class=\"card-text\">{scopeBody}</p></div></div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "callout":
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
string style = function.Parameters.Get<string>("styleName").ToLower();
style = style == "default" ? "" : style;
html.Append($"<div class=\"bd-callout bd-callout-{style}\">");
if (string.IsNullOrWhiteSpace(titleText) == false) html.Append($"<h4>{titleText}</h4>");
html.Append($"{scopeBody}");
html.Append($"</div>");
return new HandlerResult(html.ToString());
}
//------------------------------------------------------------------------------------------------------------------------------
case "card":
{
var html = new StringBuilder();
string titleText = function.Parameters.Get<string>("titleText");
var style = BGFGStyle.GetBackgroundStyle(function.Parameters.Get("styleName", "default"));
html.Append($"<div class=\"card {style.ForegroundStyle} {style.BackgroundStyle} mb-3\">");
if (string.IsNullOrEmpty(titleText) == false)
{
html.Append($"<div class=\"card-header\">{titleText}</div>");
}
html.Append("<div class=\"card-body\">");
html.Append($"<p class=\"card-text\">{scopeBody}</p>");
html.Append("</div>");
html.Append("</div>");
return new HandlerResult(html.ToString());
}
}
return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
}
}
}