ln.templates/ln.templates/html/TemplateElement.cs

313 lines
12 KiB
C#

// /**
// * File: TemplateElement.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jint.Native;
using Jint.Native.Object;
using ln.templates.script;
namespace ln.templates.html
{
public partial class TemplateElement : Element
{
private static string[] hiddenTags = new[] { "template", "slot" };
Dictionary<string, NewExpression> expressions = new Dictionary<string, NewExpression>();
Dictionary<string, NewExpression> loops = new Dictionary<string, NewExpression>();
Dictionary<string, NewExpression> _vsets = new Dictionary<string, NewExpression>();
List<NewExpression> conditions = new List<NewExpression>();
private Dictionary<string, Element> _slots = new Dictionary<string, Element>();
private NewExpression classExpression;
bool hideTag;
public TemplateElement(string tagName)
:base(tagName)
{
hideTag = hiddenTags.Contains(tagName.ToLower());
}
public override void SetAttribute(string attributeName, string attributeValue)
{
if (attributeName.StartsWith("v-for:", StringComparison.InvariantCultureIgnoreCase))
loops.Add(attributeName.Substring(6), new NewExpression(attributeValue));
else if (attributeName.Equals("v-if", StringComparison.InvariantCultureIgnoreCase))
conditions.Add(new NewExpression(attributeValue));
else if (attributeName.StartsWith("v-set:", StringComparison.InvariantCultureIgnoreCase))
_vsets.Add(attributeName.Substring(6), new NewExpression(attributeValue));
else if (attributeName.Equals(":class", StringComparison.InvariantCultureIgnoreCase))
classExpression = new NewExpression(attributeValue);
else if (attributeName.StartsWith("::"))
base.SetAttribute(attributeName, attributeValue);
else if (attributeName[0] == ':')
expressions.Add(attributeName.Substring(1), new NewExpression(attributeValue));
else
base.SetAttribute(attributeName, attributeValue);
}
public void SetSlot(string slotName, Element slotElement)
{
_slots.Add(slotName, slotElement);
}
/**
* This is the render method to be called from external callers
*/
public virtual void RenderTemplate(Context renderContext)
{
if (loops.Count == 0)
{
RenderElement(renderContext);
}
else
{
Stack<KeyValuePair<string, NewExpression>> loopStack =
new Stack<KeyValuePair<string, NewExpression>>(loops);
DoLoop(renderContext, loopStack);
}
}
private Template TemplateFromExpression(Context renderContext, NewExpression expression)
{
object templateValue = expression.Resolve(renderContext.Engine);
if (!(templateValue is Template template))
{
template = renderContext.Resolver.GetTemplateByPath((string)templateValue);
}
return template;
}
protected virtual void DoLoop(Context renderContext, Stack<KeyValuePair<string, NewExpression>> loopStack)
{
if (loopStack.Count == 0)
{
RenderElement(renderContext);
} else
{
KeyValuePair<string, NewExpression> loop = loopStack.Pop();
IEnumerable enumerable = (IEnumerable)loop.Value.Resolve(renderContext.Engine);
if (enumerable != null)
{
bool hadBinding = renderContext.Engine.Realm.GlobalEnv.HasBinding(loop.Key);
JsValue _save = null;
if (hadBinding)
_save = renderContext.Engine.Realm.GlobalEnv.GetBindingValue(loop.Key, false);
try
{
foreach (object o in enumerable)
{
renderContext.Engine.SetValue(loop.Key, o);
DoLoop(renderContext, loopStack);
}
}
finally
{
if (hadBinding)
renderContext.Engine.Realm.GlobalEnv.SetMutableBinding(loop.Key, _save, false);
else
renderContext.Engine.Realm.GlobalEnv.DeleteBinding(loop.Key);
}
}
loopStack.Push(loop);
}
}
/**
* This is the render method to be called to render the element itself (tag, attributes, children, ... )
*/
public virtual void RenderElement(Context renderContext)
{
Dictionary<string, object> savesets = null;
if (checkConditions(renderContext))
{
if (_vsets.Count > 0)
{
savesets = new Dictionary<string, object>();
foreach (var vset in _vsets)
{
if (renderContext.Engine.Realm.GlobalEnv.HasBinding(vset.Key))
savesets.Add(vset.Key, renderContext.Engine.Realm.GlobalEnv.GetBindingValue(vset.Key, false));
renderContext.Engine.SetValue(vset.Key, vset.Value.Resolve(renderContext.Engine));
}
}
if (TryGetAttribute(renderContext, "v-include", out string templatePath))
{
Template template = renderContext.Resolver.GetTemplateByPath(templatePath);
Context context = new Context(renderContext);
if (this._slots.Count == 0)
context.SetSlot("", this);
else
context.SetSlots(this._slots);
template.Render(context);
}
else
{
if (!hideTag)
{
renderContext.TargetWriter.Write("<{0}", Name);
object classObject = null;
if (classExpression is not null)
{
classObject = renderContext.Engine.Evaluate("(function(){ return " + classExpression
.ExpressionText + ";})()");
HashSet<string> classes = new HashSet<string>(GetAttribute("class", "").Split(' ', StringSplitOptions.RemoveEmptyEntries));
var classObjectInstance = classObject as ObjectInstance;
foreach (var property in classObjectInstance.GetOwnProperties())
{
if ((property.Value.Value is JsBoolean jsBooleanValue) &&
jsBooleanValue.Equals(JsBoolean.True))
classes.Add(property.Key.ToString());
}
renderContext.TargetWriter.Write(" class=\"{0}\"", string.Join(" ", classes));
}
foreach (KeyValuePair<String, String> attributePair in Attributes)
{
if (!attributePair.Key.Equals("class") || (classExpression is null))
{
renderContext.TargetWriter.Write(" {0}=\"{1}\"", attributePair.Key,
attributePair.Value);
}
}
foreach (KeyValuePair<string,NewExpression> keyValuePair in expressions)
{
object value = keyValuePair.Value.Resolve(renderContext.Engine);
if (value != null)
{
renderContext.TargetWriter.Write(" {0}=\"{1}\"", keyValuePair.Key, value);
}
}
renderContext.TargetWriter.Write(">");
}
if (!IsVoid)
{
RenderElements(renderContext, Children);
if (!hideTag)
renderContext.TargetWriter.Write("</{0}>", Name);
}
}
if (savesets?.Count > 0)
{
foreach (var saveset in savesets)
renderContext.Engine.SetValue(saveset.Key, saveset.Value);
}
}
}
public string GetAttribute(Context context, string attributeName)
{
if (expressions.TryGetValue(attributeName, out NewExpression expression))
return expression.Resolve(context.Engine).ToString();
return GetAttribute(attributeName);
}
public bool TryGetAttribute(Context context, string attributeName, out string attributeValue)
{
if (expressions.TryGetValue(attributeName, out NewExpression expression))
{
attributeValue = expression.Resolve(context.Engine).ToString();
return true;
}
else if (HasAttribute(attributeName))
{
attributeValue = GetAttribute(attributeName);
return true;
}
attributeValue = null;
return false;
}
protected void RenderElements(Context renderContext, IEnumerable<Element> elements)
{
foreach (Element element in elements)
{
if (element is TemplateElement templateElement)
{
templateElement.RenderTemplate(renderContext);
}
else
{
renderContext.TargetWriter.Write(element.ToString());
}
}
}
bool checkConditions(Context renderContext)
{
foreach (NewExpression condition in conditions)
if (!condition.IsTrue(renderContext.Engine))
return false;
return true;
}
class TemplateScriptElement : TemplateElement
{
private StringBuilder scriptBuilder = new StringBuilder();
public TemplateScriptElement()
: base("template-script")
{
}
public override void AppendChild(Element element)
{
if (element is TextElement textElement)
{
scriptBuilder.Append(textElement.Text);
}
else
{
throw new NotSupportedException();
}
}
public override void RenderElement(Context renderContext) =>
renderContext.Engine.Execute(scriptBuilder.ToString());
}
public static TemplateElement Create(string tagName)
{
switch (tagName)
{
case "qrcode":
return new QrCodeElement(tagName);
case "slot":
return new SlotElement(tagName);
case "template-script":
return new TemplateScriptElement();
default:
return new TemplateElement(tagName);
}
}
}
}