Initial Commit

master
Harald Wolff-Thobaben 2020-11-18 00:25:16 +01:00
commit dc520039f9
29 changed files with 2499 additions and 0 deletions

41
.gitignore vendored 100644
View File

@ -0,0 +1,41 @@
# Autosave files
*~
# build
[Oo]bj/
[Bb]in/
packages/
TestResults/
# globs
Makefile.in
*.DS_Store
*.sln.cache
*.suo
*.cache
*.pidb
*.userprefs
*.usertasks
config.log
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.user
*.tar.gz
tarballs/
test-results/
Thumbs.db
.vs/
# Mac bundle stuff
*.dmg
*.app
# resharper
*_Resharper.*
*.Resharper
# dotCover
*.dotCover

559
Expression.cs 100644
View File

@ -0,0 +1,559 @@
using System;
using System.Reflection;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
namespace ln.templates
{
delegate bool ConditionDelegate(char ch);
public class Expression
{
public String Source { get; }
private Eval TopEval { get; set; }
public Expression(String expr)
{
Source = expr;
Compile();
}
public object Evaluate(object o)
{
Context context = new Context(o, null);
return TopEval.Evaluate(context);
}
public object Evaluate(Context context)
{
return TopEval.Evaluate(context);
}
public bool IsTrue(Context context)
{
try
{
object v = Evaluate(context);
if ((v is bool) && ((bool)v))
return true;
if ((v is string) && (!String.Empty.Equals(v)))
return true;
if ((v is int) && ((int)v != 0))
return true;
return false;
}
catch (Exception)
{
return false;
}
}
private void Compile()
{
try
{
StringReader reader = new StringReader(Source);
List<Token> tokens = new List<Token>();
while (reader.Peek() != -1)
ReadToken(reader, tokens);
Queue<Token> qtokens = new Queue<Token>(tokens);
TopEval = BuildExpression(qtokens);
} catch (Exception e)
{
throw new Exception(String.Format("Failed to compile expression: {0}", Source), e);
}
}
private Eval BuildName(Token nameToken,Eval parent,Queue<Token> tokens)
{
if ((tokens.Count > 0) && (tokens.Peek().TokenType == TokenType.OPENLIST))
{
tokens.Dequeue();
List<Eval> pl = new List<Eval>();
while (tokens.Peek().TokenType != TokenType.ENDLIST)
{
pl.Add(BuildExpression(tokens));
if (tokens.Peek().TokenType == TokenType.ENDLIST)
break;
if (tokens.Peek().TokenType != TokenType.COMMA)
throw new FormatException("Expected ',' between parameters to call statement");
tokens.Dequeue();
}
tokens.Dequeue();
return new MethodCall(parent, nameToken.Value, pl.ToArray());
}
else
{
return new Name(parent, nameToken.Value);
}
}
private Eval BuildPath(Queue<Token> tokens)
{
Eval currentEval = BuildName(tokens.Dequeue(), null, tokens);
while (tokens.Count > 0)
{
Token token = tokens.Peek();
switch (token.TokenType)
{
case TokenType.DOT:
tokens.Dequeue();
currentEval = BuildName(tokens.Dequeue(), currentEval, tokens);
break;
case TokenType.OPENINDEXER:
tokens.Dequeue();
Eval index = BuildExpression(tokens);
token = tokens.Dequeue();
if (token.TokenType != TokenType.ENDINDEXER)
throw new FormatException("Expected ']'");
currentEval = new Indexer(currentEval, index);
break;
default:
return currentEval;
}
}
return currentEval;
}
private Eval BuildExpression(Queue<Token> tokens)
{
Token next = tokens.Peek();
switch (next.TokenType)
{
case TokenType.NAME:
return BuildPath(tokens);
case TokenType.NUMBER:
return new Number(tokens.Dequeue().Value);
case TokenType.STRING:
return new CString(tokens.Dequeue().Value);
}
throw new FormatException(String.Format("unexpected Token: {0}", next.Value));
}
private void ReadToken(TextReader reader, List<Token> tokens)
{
if (reader.Peek() == -1)
return;
char ch = (char)reader.Peek();
if (char.IsWhiteSpace(ch))
{
reader.Read();
}
else if (char.IsLetter(ch) || (ch == '_'))
{
ReadName(reader, tokens);
}
else if (char.IsDigit(ch))
{
ReadNumber(reader, tokens);
}
else if (ch == '"')
{
ReadString(reader, tokens);
}
else if (ch == '.')
{
reader.Read();
tokens.Add(new Token(TokenType.DOT, "."));
}
else if (ch == '[')
{
reader.Read();
tokens.Add(new Token(TokenType.OPENINDEXER, new string(new char[] { ch })));
}
else if (ch == ']')
{
reader.Read();
tokens.Add(new Token(TokenType.ENDINDEXER, new string(new char[] { ch })));
}
else if (ch == '(')
{
reader.Read();
tokens.Add(new Token(TokenType.OPENLIST, new string(new char[] { ch })));
}
else if (ch == ')')
{
reader.Read();
tokens.Add(new Token(TokenType.ENDLIST, new string(new char[] { ch })));
}
else if (ch == ',')
{
reader.Read();
tokens.Add(new Token(TokenType.COMMA, new string(new char[] { ch })));
}
else
{
throw new FormatException(String.Format("Unexpected character: {0}", (char)reader.Peek()));
}
}
private void ReadName(TextReader reader, List<Token> tokens)
{
StringBuilder sb = new StringBuilder();
int ch = 0;
while (char.IsLetterOrDigit((char)(ch = reader.Peek())) || (ch == '_'))
{
sb.Append((char)reader.Read());
}
tokens.Add(new Token(TokenType.NAME, sb.ToString()));
}
private void ReadNumber(TextReader reader, List<Token> tokens)
{
StringBuilder sb = new StringBuilder();
int ch = 0;
while (char.IsDigit((char)(ch = reader.Peek())) | (ch == '.'))
{
sb.Append((char)reader.Read());
}
tokens.Add(new Token(TokenType.NUMBER, sb.ToString()));
}
private void ReadString(TextReader reader, List<Token> tokens)
{
StringBuilder sb = new StringBuilder();
int ch = 0;
reader.Read();
while ((ch = reader.Peek())!='"')
{
if (ch == '\\')
{
reader.Read();
char sch = (char)reader.Read();
switch (sch)
{
case 'n':
sb.Append('\n');
break;
case 'r':
sb.Append('\r');
break;
case 't':
sb.Append('\t');
break;
default:
sb.Append(sch);
break;
}
}
else
{
sb.Append((char)reader.Read());
}
}
reader.Read();
tokens.Add(new Token(TokenType.STRING, sb.ToString()));
}
enum TokenType { NAME, DOT, NUMBER, STRING, OPENINDEXER, ENDINDEXER, OPENLIST, ENDLIST, COMMA }
class Token
{
public TokenType TokenType;
public string Value;
public Token(TokenType tokenType,String value)
{
this.TokenType = tokenType;
this.Value = value;
}
public override string ToString()
{
return String.Format("[Token TokenType={0:8} Value={1}]", TokenType, Value);
}
}
public class Context
{
public object This { get; }
public Dictionary<string,object> MappedValues { get; }
public Context(Context source)
: this(source.This, source.MappedValues)
{
}
public Context(object o,IEnumerable<KeyValuePair<string,object>> mappedValues)
{
this.This = o;
this.MappedValues = new Dictionary<string, object>();
if (mappedValues != null)
foreach (KeyValuePair<string, object> kvp in mappedValues)
MappedValues.Add(kvp.Key,kvp.Value);
}
public void AddMappedValue(string name, object value)
{
MappedValues.Add(name, value);
}
public void SetMappedValue(string name, object value)
{
MappedValues[name] = value;
}
public void RemoveMappedValue(string name)
{
MappedValues.Remove(name);
}
public object Evaluate(string name)
{
if (MappedValues.ContainsKey(name))
return MappedValues[name];
if (This != null)
{
FieldInfo fieldInfo = This.GetType().GetField(name);
if (fieldInfo != null)
{
return fieldInfo.GetValue(This);
}
PropertyInfo propertyInfo = This.GetType().GetProperty(name);
if (propertyInfo != null)
{
return propertyInfo.GetValue(This);
}
}
throw new KeyNotFoundException(name);
}
}
abstract class Eval
{
public Eval Parent { get; }
public Eval(Eval parent)
{
this.Parent = parent;
}
public abstract object Evaluate(Context context);
}
class Name : Eval
{
public string name;
public Name(Eval parent,String name)
:base(parent)
{
this.name = name;
}
public override object Evaluate(Context context)
{
if (Parent == null)
{
return context.Evaluate(name);
}
object p = Parent.Evaluate(context);
if (p != null)
{
FieldInfo fieldInfo = p.GetType().GetField(name);
if (fieldInfo != null)
{
return fieldInfo.GetValue(p);
}
PropertyInfo propertyInfo = p.GetType().GetProperty(name);
if (propertyInfo != null)
{
return propertyInfo.GetValue(p);
}
throw new KeyNotFoundException(name);
}
throw new NullReferenceException();
}
}
class Indexer : Eval
{
Eval index;
public Indexer(Eval parent,Eval index)
:base(parent)
{
this.index = index;
}
public override object Evaluate(Context context)
{
object i = index.Evaluate(context);
Type itype = i.GetType();
object p = Parent.Evaluate(context);
if (p is Array)
{
Array pa = p as Array;
return pa.GetValue((int)i);
}
foreach (PropertyInfo pi in p.GetType().GetProperties())
{
ParameterInfo[] infos = pi.GetIndexParameters();
if (infos.Length == 1)
{
if (infos[0].ParameterType.IsAssignableFrom(itype))
{
return pi.GetValue(p, new object[] { i });
}
}
}
throw new KeyNotFoundException();
}
}
class MethodCall : Eval
{
Eval[] Parameters;
String Name;
public MethodCall(Eval parent, String name,params Eval[] p)
: base(parent)
{
Name = name;
Parameters = p;
}
public override object Evaluate(Context context)
{
object p = Parent.Evaluate(context);
object[] pvalues = new object[Parameters.Length];
Type[] ptypes = new Type[Parameters.Length];
for (int i=0;i<pvalues.Length;i++)
{
pvalues[i] = Parameters[i].Evaluate(context);
if (pvalues[i] == null)
ptypes[i] = null;
else
ptypes[i] = pvalues[i].GetType();
}
MethodInfo methodInfo = p.GetType().GetRuntimeMethod(Name, ptypes);
return methodInfo.Invoke(p, pvalues);
}
}
class Number : Eval
{
object value;
public Number(String sourceValue)
: base(null)
{
try
{
value = int.Parse(sourceValue);
} catch
{
value = float.Parse(sourceValue);
}
}
public override object Evaluate(Context context)
{
return value;
}
}
class CString : Eval
{
string value;
public CString(String sourceValue)
: base(null)
{
this.value = sourceValue;
}
public override object Evaluate(Context context)
{
return this.value;
}
}
//private String read(TextReader reader,ConditionDelegate condition)
//{
// StringBuilder sb = new StringBuilder();
// int ch;
// while (((ch = reader.Peek())!=-1)&&condition((char)ch))
// {
// sb.Append((char)ch);
// }
// return sb.ToString();
//}
//private Token ReadToken(TextReader reader)
//{
// int ch = reader.Peek();
// if (char.IsLetter((char)ch))
// return ReadFieldName(reader);
// throw new FormatException();
//}
//private Token ReadPath(TextReader reader)
//{
// String read(reader, (ch) => char.IsLetterOrDigit(ch));
//}
//abstract class Token
//{
// public abstract object Evaluate();
//}
//class Field : Token
//{
// Token owner;
// String fieldName;
// public Field(Token owner,String fieldName)
// {
// this.owner = owner;
// this.fieldName = fieldName;
// }
// public override object Evaluate()
// {
// if (o == null)
// return null;
// Type t = o.GetType();
// FieldInfo fieldInfo = t.GetField(this.fieldName);
// if (fieldInfo != null)
// {
// return fieldInfo.GetValue(owner.Evaluate());
// }
// PropertyInfo propertyInfo = t.GetProperty(this.fieldName);
// if (propertyInfo != null)
// {
// return propertyInfo.GetValue(owner.Evaluate());
// }
// throw new MissingFieldException(t.FullName, this.fieldName);
// }
//}
}
}

254
FormContext.cs 100644
View File

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using System.Linq;
using System.ComponentModel;
using System.Resources;
namespace ln.templates
{
//public class FormContext
//{
//private Dictionary<string, object> vars = new Dictionary<string, object>();
//protected MemoryStream contentStream, headStream;
//public Template SharpForm { get; }
//public Request Request { get; }
//public Stream ContentStream => contentStream;
//public TextWriter ContentWriter { get; protected set; }
//public Stream HeadStream => headStream;
//public TextWriter HeadWriter { get; protected set; }
//public FormContext Parent { get; }
//public bool UsesClonedVars { get; }
//public bool IsRootContext => Parent == null;
//public FormContext SubFrameContext { get; protected set; }
//public FormContext(Template sharpForm,Request request,object o)
//{
// Parent = null;
// SharpForm = sharpForm;
// Request = request;
// contentStream = new MemoryStream();
// ContentWriter = new StreamWriter(contentStream);
// headStream = new MemoryStream();
// HeadWriter = new StreamWriter(headStream);
// vars["o"] = o;
// vars["this"] = o;
// vars["StrMax"] = GetType().GetMethod("StrMax");
//}
//public FormContext(FormContext parent,bool cloneVars = false,bool independentContent = false,FormContext subFrameContext = null)
//{
// Parent = parent;
// SharpForm = parent.SharpForm;
// Request = parent.Request;
// contentStream = independentContent ? new MemoryStream() : parent.contentStream;
// ContentWriter = new StreamWriter(ContentStream);
// headStream = parent.headStream;
// HeadWriter = parent.HeadWriter;
// UsesClonedVars = cloneVars;
// SubFrameContext = subFrameContext == null ? parent.SubFrameContext : subFrameContext;
// if (cloneVars){
// foreach (KeyValuePair<string,object> var in parent.vars)
// {
// this.vars.Add(var.Key, var.Value);
// }
// }
// parent.ContentWriter.Flush();
//}
//public byte[] ContentBytes
//{
// get
// {
// ContentWriter.Flush();
// return contentStream.ToArray();
// }
//}
//public byte[] HeadBytes
//{
// get
// {
// HeadWriter.Flush();
// return headStream.ToArray();
// }
//}
//public String Content => Encoding.UTF8.GetString(ContentBytes);
//public String Head => Encoding.UTF8.GetString(HeadBytes);
//public void Insert(FormContext context, bool allToHead = false)
//{
// if (context.headStream != headStream)
// {
// HeadWriter.Flush();
// byte[] headBytes = context.HeadBytes;
// headStream.Write(headBytes, 0, headBytes.Length);
// }
// ContentWriter.Flush();
// byte[] contentBytes = context.ContentBytes;
// if (allToHead)
// headStream.Write(contentBytes, 0, contentBytes.Length);
// else
// contentStream.Write(contentBytes, 0, contentBytes.Length);
//}
//public bool HasVar(String name)
//{
// if (vars.ContainsKey(name))
// return true;
// if (!UsesClonedVars && (Parent != null))
// return Parent.HasVar(name);
// return false;
//}
//public object Get(String name){
// if (vars.ContainsKey(name))
// return vars[name];
// else if (Parent != null)
// return Parent.Get(name);
// else
// return null;
//}
//public void Set(String name,object value){
// if (!UsesClonedVars && (Parent != null) && (Parent.HasVar(name)))
// {
// Parent.Set(name, value);
// } else {
// this.vars[name] = value;
// }
//}
//public object EvaluateExpression(Token[] tokens)
//{
// if (tokens.Length == 0)
// {
// return "";
// }
// Stack<Token> tokenStack = new Stack<Token>(tokens);
// object currentValue = null;
// Token currentToken = null;
// while (tokenStack.Count > 0){
// currentToken = tokenStack.Pop();
// if (currentToken is PathToken)
// {
// PathToken pathToken = (PathToken)currentToken;
// Response response = Request.SubRequest(pathToken.Path);
// if (response.Reference != null)
// currentValue = response.Reference;
// else
// currentValue = Encoding.UTF8.GetString(response.ContentBytes);
// } else if (currentToken is KeywordToken)
// {
// KeywordToken keywordToken = (KeywordToken)currentToken;
// currentValue = keywordToken.Evaluate(this);
// }
// if (tokenStack.Count > 0){
// currentToken = tokenStack.Pop();
// throw new NotImplementedException("Currently no expression operators are implemented, sorry");
// }
// }
// return currentValue;
//}
//public object EvaluateValue(String sValue)
//{
// Stack<String> vpath = new Stack<string>(sValue.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Reverse());
// if (vpath.Count > 0)
// {
// object value = null;
// string key = vpath.Pop();
// value = Get(key);
// while (vpath.Count > 0)
// {
// key = vpath.Pop();
// value = GetObjectFieldOrProperty(value, key);
// }
// return value;
// }
// return null;
//}
//public bool EvaluateBoolean(String expression)
//{
// bool invert = (expression[0] == '!');
// if (invert)
// expression = expression.Substring(1);
// object value = EvaluateValue(expression);
// if (value != null)
// {
// if (
// ((value is bool) && (((bool)value))) ||
// ((value is string) && (!String.Empty.Equals(value))) ||
// ((value is int) && (((int)value) != 0)) ||
// ((value is long) && (((long)value) != 0)) ||
// ((value is float) && (((float)value) != 0)) ||
// ((value is double) && (((double)value) != 0))
// )
// {
// return !invert;
// }
// }
// return invert;
//}
//private object GetObjectFieldOrProperty(object o, string name)
//{
// if (o == null)
// {
// return null;
// }
// FieldInfo fieldInfo = o.GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
// if (fieldInfo != null)
// {
// return fieldInfo.GetValue(o);
// }
// PropertyInfo propertyInfo = o.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
// if (propertyInfo != null)
// {
// return propertyInfo.GetValue(o);
// }
// throw new KeyNotFoundException(String.Format("object of type {0} has no field/property named {1}", o.GetType().FullName, name));
//}
//public static string StrMax(string s,int len,string suffix = "")
//{
// if (len < s.Length)
// return s.Substring(0, len) + suffix;
// return s;
//}
// }
}

35
FormElement.cs 100644
View File

@ -0,0 +1,35 @@
using System;
using System.Xml;
using System.Collections.Generic;
namespace ln.templates
{
//public abstract class FormElement
//{
// /* Instance Members */
// FormElement Container { get; }
// List<FormElement> children = new List<FormElement>();
// public FormElement(FormElement container)
// {
// Container = container;
// if (container != null)
// {
// container.children.Add(this);
// }
// }
// public abstract void Run(FormContext context);
// public FormElement[] Children => children.ToArray();
// public virtual Template Form => Container.Form;
// public void RunChildren(FormContext context)
// {
// foreach (FormElement child in children)
// {
// child.Run(context);
// }
// }
//}
}

134
Template.cs 100644
View File

@ -0,0 +1,134 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using ln.templates.streams;
using ln.templates.elements;
namespace ln.templates
{
public class Template
{
public TemplateProvider Provider { get; private set; }
public String SourceFilename { get; private set; }
public String PseudoFilename { get; private set; }
public DateTime SourceTimeStamp { get; private set; }
public Element RootElement { get; private set; }
public Expression FrameExpression { get; private set; }
public Template(String sourceFilename)
{
SourceFilename = sourceFilename;
PseudoFilename = sourceFilename;
LoadSource(null);
}
public Template(String sourceFilename, TemplateProvider provider)
{
Provider = provider;
SourceFilename = sourceFilename;
PseudoFilename = sourceFilename;
LoadSource(null);
}
public Template(String source,String pseudoFilename,TemplateProvider provider)
{
Provider = provider;
PseudoFilename = pseudoFilename;
LoadSource(source);
}
private void LoadSource(String source)
{
if (source == null)
{
using (FileStream fileStream = new FileStream(SourceFilename, FileMode.Open))
{
byte[] loadBuffer = new byte[fileStream.Length];
fileStream.Read(loadBuffer, 0, loadBuffer.Length);
fileStream.Close();
source = Encoding.UTF8.GetString(loadBuffer);
SourceTimeStamp = File.GetLastWriteTimeUtc(SourceFilename);
}
}
TemplateReader templateReader = new TemplateReader(source);
RootElement = templateReader.RootElement;
FrameExpression = templateReader.FrameExpression;
}
public String Generate()
{
return Generate(false);
}
public String Generate(bool unframed)
{
Context context = new Context(this);
return Generate(context);
}
public String Generate(Context context)
{
return Generate(context, false);
}
public String Generate(Context context,bool unframed)
{
if (SourceFilename != null)
{
DateTime ts = File.GetLastWriteTimeUtc(SourceFilename);
if (ts > SourceTimeStamp)
LoadSource(null);
}
StringWriter writer = new StringWriter();
RootElement.Generate(writer, context);
if (!unframed && (FrameExpression != null))
{
Template frame = context.Provider.FindTemplate(FrameExpression.Evaluate(context.ExpressionContext) as string);
if (frame == null)
{
throw new NullReferenceException(String.Format("FindTemplate() returned null for {0}",FrameExpression.Source));
}
return frame.Generate(new Context(context, writer.ToString()));
}
return writer.ToString();
}
public class Context
{
public Context Parent { get; }
public Template Template { get; }
public TemplateProvider Provider => Template.Provider;
public String FramedContent { get; }
public Expression.Context ExpressionContext { get; }
public Context(Template template)
{
Template = template;
ExpressionContext = new Expression.Context(null, null);
ExpressionContext.AddMappedValue("__template__", template.SourceFilename);
ExpressionContext.AddMappedValue("__provider__", template.Provider);
}
public Context(Context parent,String framedContent)
{
Parent = parent;
Template = parent.Template;
FramedContent = framedContent;
ExpressionContext = new Expression.Context(parent.ExpressionContext);
if (framedContent != null)
ExpressionContext.SetMappedValue("__frame__", framedContent);
}
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace ln.templates
{
public interface TemplateProvider
{
Template FindTemplate(string templatePath);
}
}

151
TemplateReader.cs 100644
View File

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using ln.templates.elements;
using System.IO;
using System.Text;
namespace ln.templates
{
public class TemplateReader
{
public ContainerElement RootElement { get; private set; } = new ContainerElement(null);
public ContainerElement CurrentElement { get; private set; }
public Expression FrameExpression { get; private set; }
public String Source { get; private set; }
public TemplateReader(String source)
{
Source = source;
Parse();
}
private void Parse()
{
CurrentElement = RootElement;
while (Source.Length > 0)
{
String t, op;
int i, j;
i = Source.IndexOf("<%");
if (i == -1)
{
t = Source;
op = null;
Source = "";
}
else
{
t = Source.Substring(0, i);
j = Source.IndexOf("%>", i + 1);
if (j == -1)
{
throw new FormatException(String.Format("missing '%>' in {0}", Source.Substring(i, 64)));
}
op = Source.Substring(i + 2, j - i - 2);
Source = Source.Substring(j + 2);
}
new TextElement(CurrentElement, t);
if (op != null)
ParseOP(op);
}
}
private void ParseOP(String op)
{
StringReader reader = new StringReader(op);
String opkey;
if (reader.Peek() == '=')
{
reader.Read();
opkey = "=";
}
else
{
opkey = reader.PopWord();
}
switch (opkey)
{
case "=":
new ExpressionElement(CurrentElement, new Expression(reader.ReadToEnd()));
break;
case "frame":
FrameExpression = new Expression(reader.ReadToEnd());
break;
case "iterate":
String iterName = reader.PopWord();
CurrentElement = new IteratorElement(CurrentElement, iterName, new Expression(reader.ReadToEnd()));
break;
case "if":
CurrentElement = new ConditionalElement(CurrentElement, new Expression(reader.ReadToEnd()));
break;
case "else":
if (CurrentElement is ConditionalElement)
{
ConditionalElement ce = CurrentElement as ConditionalElement;
CurrentElement = ce.AlternativeContainer;
}
break;
case "elseif":
if (CurrentElement is ConditionalElement)
{
ConditionalElement ce = CurrentElement as ConditionalElement;
CurrentElement = new ConditionalElement(null, new Expression(reader.ReadToEnd()));
ce.AlternativeContainer = CurrentElement;
}
break;
case "include":
new IncludeElement(CurrentElement, new Expression(reader.ReadToEnd()));
break;
case "set":
String setName = reader.PopWord();
new SetElement(CurrentElement, setName, new Expression(reader.ReadToEnd()));
break;
case "end":
CurrentElement = CurrentElement.Container;
break;
default:
throw new NotImplementedException(opkey);
}
}
}
static class StringHelper
{
public static int IndexOf(this String s,Func<char,bool> predicate)
{
for (int i=0;i<s.Length;i++)
{
if (predicate(s[i]))
return i;
}
return -1;
}
public static void SkipWhiteSpace(this TextReader reader)
{
while (char.IsWhiteSpace((char)reader.Peek()))
{
reader.Read();
}
}
public static String PopWord(this TextReader reader)
{
StringBuilder stringBuilder = new StringBuilder();
while ((reader.Peek()!=-1) && !char.IsWhiteSpace((char)reader.Peek()))
{
stringBuilder.Append((char)reader.Read());
}
reader.SkipWhiteSpace();
return stringBuilder.ToString();
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.IO;
using System.Collections;
namespace ln.templates.elements
{
public class ConditionalElement : ContainerElement
{
private ContainerElement alternativeContainer;
public Expression Expression { get; private set; }
public ConditionalElement(ContainerElement container,Expression expression)
:base(container)
{
Expression = expression;
AlternativeContainer = new ContainerElement(null);
}
private bool IsTrue(object value)
{
return (
(value != null) &&
((value is int) && ((int)value != 0)) ||
((value is double) && ((double)value != 0.0)) ||
((value is bool) && ((bool)value)) ||
((value is string) && (!String.Empty.Equals(value)))
);
}
public override void Generate(TextWriter writer, Template.Context context)
{
if (Expression.IsTrue(context.ExpressionContext))
{
GenerateChildren(writer, context);
}
else if (AlternativeContainer != null)
{
AlternativeContainer.Generate(writer, context);
}
}
public ContainerElement AlternativeContainer {
get
{
return alternativeContainer;
}
set
{
if (alternativeContainer != null)
{
alternativeContainer.Container = null;
}
if (value != null)
{
value.Container = Container;
}
alternativeContainer = value;
}
}
}
public class IteratorElement : ConditionalElement
{
public String IterName { get; private set; }
public IteratorElement(ContainerElement container,String iterName,Expression expression)
:base(container, expression)
{
IterName = iterName;
}
public override void Generate(TextWriter writer, Template.Context context)
{
object v = Expression.Evaluate(context.ExpressionContext);
if (v == null)
{
AlternativeContainer.Generate(writer, context);
}
else if (v is IEnumerable)
{
Template.Context icontext = new Template.Context(context,null);
bool b = false;
IEnumerable en = v as IEnumerable;
foreach (object o in en)
{
b = true;
icontext.ExpressionContext.SetMappedValue(IterName, o);
GenerateChildren(writer, icontext);
}
}
}
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Collections.Generic;
namespace ln.templates.elements
{
public abstract class Element
{
public ContainerElement Container { get; internal set; }
protected Element(ContainerElement container)
{
Container = container;
if (Container != null)
Container.children.Add(this);
}
public abstract void Generate(TextWriter writer, Template.Context context);
}
public class ContainerElement : Element
{
internal List<Element> children { get; } = new List<Element>();
public ContainerElement(ContainerElement container)
:base(container)
{
}
public void GenerateChildren(TextWriter writer, Template.Context context)
{
foreach (Element c in children)
c.Generate(writer, context);
}
public override void Generate(TextWriter writer, Template.Context context)
{
GenerateChildren(writer, context);
}
public Element[] Children
{
get => children.ToArray();
}
}
public class TextElement : Element
{
public String Text { get; }
public TextElement(ContainerElement container,String text)
:base(container)
{
Text = text;
}
public override void Generate(TextWriter writer, Template.Context context)
{
writer.Write(Text);
}
}
public class ExpressionElement : Element
{
public Expression Expression { get; }
public ExpressionElement(ContainerElement container,Expression expression)
:base(container)
{
Expression = expression;
}
public override void Generate(TextWriter writer, Template.Context context)
{
object v = Expression.Evaluate(context.ExpressionContext);
if (v != null)
writer.Write(v.ToString());
}
}
public class SetElement : Element
{
public String SetName { get; }
public Expression Expression { get; }
public SetElement(ContainerElement container, String setName,Expression expression)
: base(container)
{
SetName = setName;
Expression = expression;
}
public override void Generate(TextWriter writer, Template.Context context)
{
object v = Expression.Evaluate(context.ExpressionContext);
context.ExpressionContext.SetMappedValue(SetName, v);
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.IO;
using System.Collections.Generic;
namespace ln.templates.elements
{
public class IncludeElement : Element
{
Expression TargetExpression { get; set; }
public IncludeElement(ContainerElement container,Expression targetExpression)
:base(container)
{
TargetExpression = targetExpression;
}
public void Generate(TextWriter writer, Template.Context context,String templatePath)
{
Template iTemplate = context.Provider.FindTemplate(templatePath);
Template.Context iContext = new Template.Context(context, null);
String iContent = iTemplate.Generate(iContext);
writer.Write(iContent);
}
public override void Generate(TextWriter writer, Template.Context context)
{
object target = TargetExpression.Evaluate(context.ExpressionContext);
if (target is IEnumerable<String>)
{
IEnumerable<String> targets = (IEnumerable<String>)target;
foreach (String t in targets)
{
Generate(writer, context, t);
}
}
else
{
Generate(writer, context, target as String);
}
}
}
}

View File

@ -0,0 +1,31 @@
// /**
// * File: DocumentElement.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.IO;
namespace ln.templates.html
{
public class DocumentElement : Element
{
public DocumentElement()
:base("#document")
{
}
public override void Render(TextWriter writer)
{
writer.Write("<!DOCTYPE html>");
foreach (Element element in Children)
element.Render(writer);
}
}
}

100
html/Element.cs 100644
View File

@ -0,0 +1,100 @@
// /**
// * File: Element.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.Generic;
using System.IO;
using System.Linq;
namespace ln.templates.html
{
public class Element
{
public string Name { get; }
public Element Parent { get; private set; }
public bool IsVoid => IsVoidElement(Name);
List<Element> children = new List<Element>();
Dictionary<string, string> attributes = new Dictionary<string, string>();
public IEnumerable<KeyValuePair<string, string>> Attributes => attributes;
public Element(string elementName)
{
Name = elementName;
}
public Element(string elementName,IEnumerable<KeyValuePair<string,string>> attributes)
:this(elementName)
{
foreach (KeyValuePair<string, string> keyValuePair in attributes)
this.attributes[keyValuePair.Key] = keyValuePair.Value;
}
public virtual bool HasAttribute(string attributeName) => attributes.ContainsKey(attributeName);
public virtual string GetAttribute(string attributeName) => attributes[attributeName];
public virtual void SetAttribute(string attributeName, string attributeValue) => attributes[attributeName] = attributeValue;
public IEnumerable<Element> Children => children;
public void AppendChild(Element element)
{
if (element.Parent != null)
element.Parent.RemoveChild(element);
if (IsVoid)
{
Parent.AppendChild(element);
}
else
{
children.Add(element);
element.Parent = this;
}
}
public void RemoveChild(Element element)
{
if (element.Parent != this)
throw new KeyNotFoundException();
children.Remove(element);
element.Parent = null;
}
public virtual void Render(TextWriter writer)
{
writer.Write("<{0}", Name);
foreach (KeyValuePair<String, String> attributePair in attributes)
writer.Write(" {0}=\"{1}\"", attributePair.Key, attributePair.Value);
writer.Write(">");
if (!IsVoid)
{
foreach (Element element in children)
element.Render(writer);
writer.Write("</{0}>",Name);
}
}
public override string ToString()
{
StringWriter stringWriter = new StringWriter();
Render(stringWriter);
return stringWriter.ToString();
}
static HashSet<string> voidElementNames = new HashSet<string>(){
"!doctype",
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
};
public static string[] VoidElementNames => voidElementNames.ToArray();
public bool IsVoidElement(string elementName) => voidElementNames.Contains(elementName);
}
}

View File

@ -0,0 +1,75 @@
// /**
// * File: ElementReader.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.Generic;
namespace ln.templates.html
{
public class ElementReader : HtmlReader
{
Stack<Element> elements = new Stack<Element>();
public Element CurrentElement => elements.Peek();
public virtual DocumentElement Document { get; }
public ElementReader()
{
Document = CreateDocument();
elements.Push(Document);
}
public virtual DocumentElement CreateDocument() => new DocumentElement();
public virtual Element CreateElement(string tagName)
{
switch (tagName.ToUpper())
{
default:
return new Element(tagName);
}
}
public virtual Element CreateTextElement(string text) => new TextElement(text);
void Push(Element element)
{
if (CurrentElement.IsVoid)
elements.Pop();
CurrentElement.AppendChild(element);
elements.Push(element);
}
void Pop()
{
if (CurrentElement.IsVoid)
elements.Pop();
elements.Pop();
}
public override void OpenTag(string tagName)
{
Push(CreateElement(tagName));
}
public override void CloseTag(string tagName)
{
Pop();
}
public override void Attribute(string attributeName, string attributeValue)
{
CurrentElement.SetAttribute(attributeName, attributeValue);
}
public override void Text(string text)
{
CurrentElement.AppendChild(
CreateTextElement(text)
);
}
}
}

View File

@ -0,0 +1,40 @@
// /**
// * File: ExpressionElement.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 ln.templates.script;
using System.IO;
namespace ln.templates.html
{
public class ExpressionElement : TemplateElement
{
public string ExpressionText { get; }
public NewExpression Expression { get; }
public ExpressionElement(string expression)
:base("#expression")
{
ExpressionText = expression;
Expression = new NewExpression(ExpressionText);
}
public override void Render(TextWriter writer)
{
writer.Write("{{{{{0}}}}}", ExpressionText);
}
public override void RenderTemplate(RenderContext renderContext)
{
object o = Expression.Resolve(renderContext);
renderContext.ContentWriter.Write(o?.ToString());
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ln.templates.html
{
public class FileSystemTemplateSource : ITemplateSource
{
public String BasePath { get; }
Dictionary<string, TemplateDocument> templateDocuments = new Dictionary<string, TemplateDocument>();
public FileSystemTemplateSource(String path)
{
if (!Directory.Exists(path))
throw new FileNotFoundException();
BasePath = path;
}
public TemplateDocument GetTemplateByPath(string path)
{
string templatePath = Path.Combine(BasePath, path);
if (!File.Exists(templatePath))
{
if (templateDocuments.ContainsKey(path))
templateDocuments.Remove(path);
throw new FileNotFoundException();
}
if (!templateDocuments.TryGetValue(path,out TemplateDocument templateDocument))
{
templateDocument = ReadTemplateDocument(path);
templateDocuments[path] = templateDocument;
} else
{
DateTime templateDateTime = File.GetLastWriteTime(templatePath);
if (templateDateTime.Ticks != templateDocument.TemplateVersion)
{
templateDocument = ReadTemplateDocument(path);
templateDocuments[path] = templateDocument;
}
}
return templateDocument;
}
TemplateDocument ReadTemplateDocument(string path)
{
string templatePath = Path.Combine(BasePath, path);
TemplateReader templateReader = new TemplateReader();
using (StreamReader sr = new StreamReader(templatePath))
{
templateReader.Read(sr);
}
templateReader.TemplateDocument.TemplateVersion = File.GetLastWriteTime(templatePath).Ticks;
return templateReader.TemplateDocument;
}
}
}

175
html/HtmlReader.cs 100644
View File

@ -0,0 +1,175 @@
// /**
// * File: Parser.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.IO;
using System.Collections.Generic;
using System.Text;
namespace ln.templates.html
{
public class HtmlReader
{
public HtmlReader()
{
}
public void Read(string source) => Read(new StringReader(source));
public void Read(Stream sourceStream)
{
using (StreamReader streamReader = new StreamReader(sourceStream))
Read(streamReader);
}
public void Read(TextReader textReader)
{
while (textReader.Peek() != -1)
{
switch (textReader.Peek())
{
case '<':
ReadTag(textReader);
break;
default:
ReadText(textReader);
break;
}
}
}
public void ReadTag(TextReader textReader)
{
if (textReader.Read() != '<')
throw new FormatException("Expected <");
bool closing = false;
if (textReader.Peek()=='/')
{
textReader.Read();
closing = true;
}
if (textReader.Peek()=='!')
{
textReader.Read();
string doctype = ReadTagName(textReader);
if (!doctype.Equals("DOCTYPE"))
throw new FormatException("Expected DOCTYPE");
string type = ReadAttributeName(textReader);
DOCTYPE(type);
if (textReader.Read() != '>')
throw new FormatException("Expected >");
return;
}
string tagName = ReadTagName(textReader);
if (closing)
{
CloseTag(tagName);
}
else
{
OpenTag(tagName);
while (TestChar((char)textReader.Peek(), (ch) => !char.IsWhiteSpace(ch) && (ch != '\0') && (ch != '"') && (ch != '\'') && (ch != '>') && (ch != '/') && (ch != '=')))
{
string attributeName = ReadAttributeName(textReader);
string attributeValue = "";
if (textReader.Peek() == '=')
{
textReader.Read();
attributeValue = ReadAttributeValue(textReader);
}
Attribute(attributeName, attributeValue);
}
if (textReader.Peek()=='/')
{
textReader.Read();
CloseTag(tagName);
}
}
if (textReader.Read() != '>')
throw new FormatException("Expected >");
}
bool TestChar(char ch, Func<char, bool> condition) => condition(ch);
public string ReadToken(TextReader textReader, Func<char, bool> condition, bool clearFinalChar = false)
{
StringBuilder characters = new StringBuilder();
while (condition((char)textReader.Peek()) && textReader.Peek() != -1)
{
characters.Append((char)textReader.Read());
}
if ((textReader.Peek() != -1) && clearFinalChar)
textReader.Read();
return characters.ToString();
}
public string ReadTokenLWS(TextReader textReader, Func<char, bool> condition,bool clearFinalChar = false)
{
string token = ReadToken(textReader, condition, clearFinalChar);
ReadToken(textReader, char.IsWhiteSpace);
return token;
}
void ReadLWS(TextReader textReader) => ReadToken(textReader, char.IsWhiteSpace);
public string ReadTagName(TextReader textReader) => ReadTokenLWS(textReader, (ch) => char.IsLetterOrDigit(ch));
public string ReadAttributeName(TextReader textReader) => ReadTokenLWS(textReader, (ch) => !char.IsWhiteSpace(ch) && (ch != '\0') && (ch != '"') && (ch != '\'') && (ch != '>') && (ch != '/') && (ch != '='));
public string ReadAttributeValue(TextReader textReader)
{
switch (textReader.Peek())
{
case '"':
textReader.Read();
return ReadTokenLWS(textReader, (ch) => ch != '"', true);
case '\'':
textReader.Read();
return ReadTokenLWS(textReader, (ch)=> ch != '\'', true);
default:
return ReadTokenLWS(textReader, (ch) => !char.IsWhiteSpace(ch) && (ch != '"') && (ch != '\'') && (ch != '<') && (ch != '>') && (ch != '`'));
}
}
public void ReadText(TextReader textReader)
{
StringBuilder stringBuilder = new StringBuilder();
while (textReader.Peek() != '<')
stringBuilder.Append((char)textReader.Read());
Text(stringBuilder.ToString());
}
public virtual void DOCTYPE(string type)
{
}
public virtual void OpenTag(string tagName)
{
}
public virtual void CloseTag(string tagName)
{
}
public virtual void Attribute(string attributeName,string attributeValue)
{
}
public virtual void Text(string text)
{
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace ln.templates.html
{
public interface ITemplateSource
{
TemplateDocument GetTemplateByPath(string path);
}
}

View File

@ -0,0 +1,39 @@
// /**
// * File: RenderContext.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.IO;
using System.Collections.Generic;
using ln.templates.script;
namespace ln.templates.html
{
public class RenderContext : IScriptContext
{
public TextWriter ContentWriter { get; }
public ITemplateSource TemplateSource { get; set; }
Dictionary<string, object> scriptObjects = new Dictionary<string, object>();
public IEnumerable<string> ScriptObjectNames => scriptObjects.Keys;
public RenderContext(TextWriter contentWriter)
{
ContentWriter = contentWriter;
}
public RenderContext(TextWriter contentWriter,ITemplateSource templateSource)
:this(contentWriter)
{
TemplateSource = templateSource;
}
Dictionary<string, object> values = new Dictionary<string, object>();
public object GetScriptObject(string itemName) => scriptObjects[itemName];
public void SetScriptObject(string itemName, object value) => scriptObjects[itemName] = value;
}
}

View File

@ -0,0 +1,42 @@
// /**
// * File: TemplateDocument.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.IO;
namespace ln.templates.html
{
public class TemplateDocument : DocumentElement
{
public long TemplateVersion { get; set; }
public TemplateDocument()
{
}
public void RenderTemplate(TextWriter contentWriter) => RenderTemplate(new RenderContext(contentWriter));
public void RenderTemplate(RenderContext renderContext,bool withoutDocType = false)
{
if (!withoutDocType)
renderContext.ContentWriter.Write("<!DOCTYPE html>");
foreach (Element element in Children)
{
if (element is TemplateElement templateElement)
{
templateElement.RenderTemplate(renderContext);
} else
{
renderContext.ContentWriter.Write(element.ToString());
}
}
renderContext.ContentWriter.Flush();
}
}
}

View File

@ -0,0 +1,140 @@
// /**
// * 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 ln.templates.script;
namespace ln.templates.html
{
public class TemplateElement : Element
{
Dictionary<string, NewExpression> expressions = new Dictionary<string, NewExpression>();
Dictionary<string, NewExpression> loops = new Dictionary<string, NewExpression>();
List<NewExpression> conditions = new List<NewExpression>();
NewExpression includeExpression;
bool hideTag;
public TemplateElement(string tagName)
:base(tagName)
{
hideTag = "template".Equals(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.Equals("v-include", StringComparison.InvariantCultureIgnoreCase))
includeExpression = new NewExpression(attributeValue);
else if (attributeName[0] == ':')
expressions.Add(attributeName.Substring(1), new NewExpression(attributeValue));
else
base.SetAttribute(attributeName, attributeValue);
}
public virtual void RenderTemplate(RenderContext renderContext)
{
if (loops.Count == 0)
{
RenderElement(renderContext);
} else
{
Stack<KeyValuePair<string, NewExpression>> loopStack = new Stack<KeyValuePair<string, NewExpression>>(loops);
DoLoop(renderContext, loopStack);
}
}
void DoLoop(RenderContext 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);
foreach (object o in enumerable)
{
renderContext.SetScriptObject(loop.Key, o);
DoLoop(renderContext, loopStack);
}
loopStack.Push(loop);
}
}
void RenderElement(RenderContext renderContext)
{
if (checkConditions(renderContext))
{
if (includeExpression != null)
{
object includeValue = includeExpression.Resolve(renderContext);
if (!(includeValue is TemplateDocument includeTemplate))
{
includeTemplate = renderContext.TemplateSource.GetTemplateByPath((string)includeValue);
}
includeTemplate.RenderTemplate(renderContext, true);
}
else
{
if (!hideTag)
{
renderContext.ContentWriter.Write("<{0}", Name);
foreach (KeyValuePair<String, String> attributePair in Attributes)
renderContext.ContentWriter.Write(" {0}=\"{1}\"", attributePair.Key, attributePair.Value);
foreach (KeyValuePair<string,NewExpression> keyValuePair in expressions)
{
object value = keyValuePair.Value.Resolve(renderContext);
if (value != null)
{
renderContext.ContentWriter.Write(" {0}=\"{1}\"", keyValuePair.Key, value);
}
}
renderContext.ContentWriter.Write(">");
}
if (!IsVoid)
{
foreach (Element element in Children)
{
if (element is TemplateElement templateElement)
{
templateElement.RenderTemplate(renderContext);
}
else
{
renderContext.ContentWriter.Write(element.ToString());
}
}
if (!hideTag)
renderContext.ContentWriter.Write("</{0}>", Name);
}
}
}
}
bool checkConditions(RenderContext renderContext)
{
foreach (NewExpression condition in conditions)
if (!condition.IsTrue(renderContext))
return false;
return true;
}
}
}

View File

@ -0,0 +1,70 @@
// /**
// * File: TemplateReader.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.IO;
namespace ln.templates.html
{
public class TemplateReader : ElementReader
{
public virtual TemplateDocument TemplateDocument => Document as TemplateDocument;
public TemplateReader()
{
}
public override DocumentElement CreateDocument() => new TemplateDocument();
public override Element CreateElement(string tagName) => new TemplateElement(tagName);
public override void Text(string text)
{
if (text.Contains("{{"))
{
int pOpen = 0;
int pClose = 0;
while (pOpen < text.Length)
{
pOpen = text.IndexOf("{{", pClose);
if (pOpen == -1)
pOpen = text.Length;
string preText = text.Substring(pClose, pOpen - pClose);
if (preText.Length > 0)
{
CurrentElement.AppendChild(CreateTextElement(preText));
}
if (pOpen == text.Length)
break;
pOpen += 2;
pClose = text.IndexOf("}}", pOpen);
if (pClose == -1)
throw new FormatException("missing }}");
string expr = text.Substring(pOpen, pClose - pOpen - 1);
pClose += 2;
CurrentElement.AppendChild(new ExpressionElement(expr));
}
} else
{
CurrentElement.AppendChild(
CreateTextElement(text)
);
}
}
}
}

View File

@ -0,0 +1,30 @@
// /**
// * File: TextElement.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.IO;
namespace ln.templates.html
{
public class TextElement : Element
{
public string Text { get; }
public TextElement(string text)
:base("#text")
{
Text = text;
}
public override void Render(TextWriter writer)
{
writer.Write(Text);
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ln.templates.test\**" />
<EmbeddedResource Remove="ln.templates.test\**" />
<None Remove="ln.templates.test\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,71 @@
// /**
// * File: HtmlTests.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 NUnit.Framework;
using System;
using ln.templates.html;
using System.IO;
namespace ln.templates.test
{
[TestFixture()]
public class HtmlTests
{
[Test()]
public void TestCase()
{
html.TemplateReader templateReader = new html.TemplateReader();
templateReader.Read("<!DOCTYPE HTML>\n<html>\n<head>\n <title>Hello</title>\n</head>\n<body>\n <p>Welcome to this example.</p>\n <p>{{ 'Ich bin ein ScriptText!' }}</p>\n <p>DateTime: {{ Date }}</p>\n <p>Ein bisschen JavaScript: {{ 5 + ' mal ' + 5 + ' = ' + (5*5) }}</p>\n</body>\n</html>");
Console.WriteLine("Source rendered:\n{0}", templateReader.Document.ToString());
StringWriter stringWriter = new StringWriter();
RenderContext renderContext = new RenderContext(stringWriter);
renderContext.SetScriptObject("Date", DateTime.Now);
templateReader.TemplateDocument.RenderTemplate(renderContext);
Console.WriteLine("Template rendered:\n{0}", stringWriter.ToString());
}
[Test()]
public void TestFileSystemTemplateSource()
{
if (File.Exists("templates.tst/frame.html"))
File.Delete("templates.tst/frame.html");
if (File.Exists("templates.tst/head.html"))
File.Delete("templates.tst/head.html");
Directory.CreateDirectory("templates.tst");
using (FileStream fs = new FileStream("templates.tst/frame.html", FileMode.CreateNew))
using (TextWriter tw = new StreamWriter(fs))
{
tw.Write("<!DOCTYPE html><html><head v-include=\"'head.html'\"/></html>");
tw.Flush();
}
using (FileStream fs = new FileStream("templates.tst/head.html", FileMode.CreateNew))
using (TextWriter tw = new StreamWriter(fs))
{
tw.Write("<head><title v-if=\"title != 'Schwurbel'\" v-for:title=\"['One HTML Template Test','Second Title','Schwurbel','Last Title']\">{{ title }}</title></head>");
tw.Flush();
}
FileSystemTemplateSource fileSystemTemplateSource = new FileSystemTemplateSource("templates.tst");
TemplateDocument templateDocument = fileSystemTemplateSource.GetTemplateByPath("frame.html");
StringWriter stringWriter = new StringWriter();
RenderContext renderContext = new RenderContext(stringWriter,fileSystemTemplateSource);
templateDocument.RenderTemplate(renderContext);
Console.WriteLine("Document rendered to: {0}", stringWriter.ToString());
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ln.templates.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
// /**
// * File: IScriptContext.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.Generic;
namespace ln.templates.script
{
public interface IScriptContext
{
IEnumerable<string> ScriptObjectNames { get; }
object GetScriptObject(string itemName);
void SetScriptObject(string itemName, object value);
}
}

View File

@ -0,0 +1,63 @@
// /**
// * File: NewExpression.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 Jint;
using Jint.Native;
using Jint.Native.String;
namespace ln.templates.script
{
public class NewExpression
{
public string ExpressionText { get; }
public NewExpression(string expression)
{
ExpressionText = expression;
}
public virtual object Resolve(IScriptContext scriptContext)
{
Engine engine = new Engine();
foreach (string itemName in scriptContext.ScriptObjectNames)
engine.SetValue(itemName, scriptContext.GetScriptObject(itemName));
JsValue jsv = engine.Eval.Call(null, new Jint.Native.JsValue[] { ExpressionText });
return jsv.ToObject();
}
public virtual bool IsTrue(IScriptContext scriptContext)
{
object resolved = Resolve(scriptContext);
if (resolved == null)
return false;
if (resolved is bool b)
return b;
if (resolved is string s)
return s.Length > 0;
if (resolved is int i)
return i != 0;
if (resolved is long l)
return l != 0;
if (resolved is short sh)
return sh != 0;
if (resolved is byte by)
return by != 0;
if (resolved is float f)
return Math.Abs(f) > float.Epsilon;
if (resolved is double d)
return Math.Abs(d) > double.Epsilon;
return true;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Text;
namespace ln.templates.streams
{
public class CharStream : PeekAbleStream<char>
{
public CharStream(char[] buffer)
:base(buffer)
{
}
public CharStream(byte[] byteBuffer, Encoding encoding)
: this(encoding.GetChars(byteBuffer))
{
}
public CharStream(byte[] byteBuffer)
:this(Encoding.UTF8.GetChars(byteBuffer))
{
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Text;
namespace ln.templates.streams
{
public class PeekAbleStream<T>
{
private T[] buffer;
private int position;
public int Position => position;
public bool EndOfBuffer => (position >= buffer.Length);
public int MarkedPosition { get; set; }
public PeekAbleStream(T[] buffer)
{
this.buffer = buffer;
}
public T Read(){
if (position < buffer.Length)
return buffer[position++];
throw new IndexOutOfRangeException("Tried to read after end of buffer");
}
public T Peek(int shift = 0)
{
if ((position + shift) < buffer.Length)
return buffer[position + shift];
throw new IndexOutOfRangeException("Tried to peek after end of buffer");
}
public void Mark()
{
MarkedPosition = position;
}
public T[] GetMarkedIntervall()
{
T[] intervall = new T[position - MarkedPosition];
Array.Copy(buffer, MarkedPosition, intervall, 0, intervall.Length);
return intervall;
}
public int Remaining => buffer.Length - position;
}
}