commit dc520039f9c08c837a72cba71b73f3be65ec6af9 Author: Harald Wolff-Thobaben Date: Wed Nov 18 00:25:16 2020 +0100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd51a9f --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Expression.cs b/Expression.cs new file mode 100644 index 0000000..4ad83b9 --- /dev/null +++ b/Expression.cs @@ -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 tokens = new List(); + while (reader.Peek() != -1) + ReadToken(reader, tokens); + + Queue qtokens = new Queue(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 tokens) + { + if ((tokens.Count > 0) && (tokens.Peek().TokenType == TokenType.OPENLIST)) + { + tokens.Dequeue(); + + List pl = new List(); + 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 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 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 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 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 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 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 MappedValues { get; } + + public Context(Context source) + : this(source.This, source.MappedValues) + { + } + + public Context(object o,IEnumerable> mappedValues) + { + this.This = o; + this.MappedValues = new Dictionary(); + + if (mappedValues != null) + foreach (KeyValuePair 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 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); + // } + //} + + } + + + +} diff --git a/FormContext.cs b/FormContext.cs new file mode 100644 index 0000000..5cac569 --- /dev/null +++ b/FormContext.cs @@ -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 vars = new Dictionary(); + //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 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 tokenStack = new Stack(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 vpath = new Stack(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; + //} + +// } +} diff --git a/FormElement.cs b/FormElement.cs new file mode 100644 index 0000000..4dfa845 --- /dev/null +++ b/FormElement.cs @@ -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 children = new List(); + + // 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); + // } + // } + + //} +} diff --git a/Template.cs b/Template.cs new file mode 100644 index 0000000..683a865 --- /dev/null +++ b/Template.cs @@ -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); + } + } + + } +} diff --git a/TemplateProvider.cs b/TemplateProvider.cs new file mode 100644 index 0000000..158191b --- /dev/null +++ b/TemplateProvider.cs @@ -0,0 +1,8 @@ +using System; +namespace ln.templates +{ + public interface TemplateProvider + { + Template FindTemplate(string templatePath); + } +} diff --git a/TemplateReader.cs b/TemplateReader.cs new file mode 100644 index 0000000..c1254bd --- /dev/null +++ b/TemplateReader.cs @@ -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 predicate) + { + for (int i=0;i children { get; } = new List(); + + 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); + } + } +} diff --git a/elements/IncludeElement.cs b/elements/IncludeElement.cs new file mode 100644 index 0000000..7cd04d3 --- /dev/null +++ b/elements/IncludeElement.cs @@ -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) + { + IEnumerable targets = (IEnumerable)target; + foreach (String t in targets) + { + Generate(writer, context, t); + } + } + else + { + Generate(writer, context, target as String); + } + + } + } +} diff --git a/html/DocumentElement.cs b/html/DocumentElement.cs new file mode 100644 index 0000000..a80f486 --- /dev/null +++ b/html/DocumentElement.cs @@ -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(""); + + foreach (Element element in Children) + element.Render(writer); + } + + } +} diff --git a/html/Element.cs b/html/Element.cs new file mode 100644 index 0000000..5691e1b --- /dev/null +++ b/html/Element.cs @@ -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 children = new List(); + Dictionary attributes = new Dictionary(); + public IEnumerable> Attributes => attributes; + + public Element(string elementName) + { + Name = elementName; + } + public Element(string elementName,IEnumerable> attributes) + :this(elementName) + { + foreach (KeyValuePair 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 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 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("",Name); + } + } + + public override string ToString() + { + StringWriter stringWriter = new StringWriter(); + Render(stringWriter); + return stringWriter.ToString(); + } + + + + static HashSet voidElementNames = new HashSet(){ + "!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); + + } +} diff --git a/html/ElementReader.cs b/html/ElementReader.cs new file mode 100644 index 0000000..8bcaf16 --- /dev/null +++ b/html/ElementReader.cs @@ -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 elements = new Stack(); + + 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) + ); + } + } +} diff --git a/html/ExpressionElement.cs b/html/ExpressionElement.cs new file mode 100644 index 0000000..435beab --- /dev/null +++ b/html/ExpressionElement.cs @@ -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()); + } + + + } +} diff --git a/html/FileSystemTemplateSource.cs b/html/FileSystemTemplateSource.cs new file mode 100644 index 0000000..4bf1014 --- /dev/null +++ b/html/FileSystemTemplateSource.cs @@ -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 templateDocuments = new Dictionary(); + + 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; + } + } +} diff --git a/html/HtmlReader.cs b/html/HtmlReader.cs new file mode 100644 index 0000000..be21375 --- /dev/null +++ b/html/HtmlReader.cs @@ -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 condition) => condition(ch); + public string ReadToken(TextReader textReader, Func 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 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) + { + } + + + } +} diff --git a/html/ITemplateSource.cs b/html/ITemplateSource.cs new file mode 100644 index 0000000..1dde637 --- /dev/null +++ b/html/ITemplateSource.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ln.templates.html +{ + public interface ITemplateSource + { + TemplateDocument GetTemplateByPath(string path); + } +} diff --git a/html/RenderContext.cs b/html/RenderContext.cs new file mode 100644 index 0000000..39406fd --- /dev/null +++ b/html/RenderContext.cs @@ -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 scriptObjects = new Dictionary(); + public IEnumerable ScriptObjectNames => scriptObjects.Keys; + + public RenderContext(TextWriter contentWriter) + { + ContentWriter = contentWriter; + } + public RenderContext(TextWriter contentWriter,ITemplateSource templateSource) + :this(contentWriter) + { + TemplateSource = templateSource; + } + + Dictionary values = new Dictionary(); + + public object GetScriptObject(string itemName) => scriptObjects[itemName]; + public void SetScriptObject(string itemName, object value) => scriptObjects[itemName] = value; + } +} diff --git a/html/TemplateDocument.cs b/html/TemplateDocument.cs new file mode 100644 index 0000000..4d45f19 --- /dev/null +++ b/html/TemplateDocument.cs @@ -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(""); + + foreach (Element element in Children) + { + if (element is TemplateElement templateElement) + { + templateElement.RenderTemplate(renderContext); + } else + { + renderContext.ContentWriter.Write(element.ToString()); + } + } + + renderContext.ContentWriter.Flush(); + } + } +} diff --git a/html/TemplateElement.cs b/html/TemplateElement.cs new file mode 100644 index 0000000..dc2794d --- /dev/null +++ b/html/TemplateElement.cs @@ -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 expressions = new Dictionary(); + Dictionary loops = new Dictionary(); + List conditions = new List(); + + 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> loopStack = new Stack>(loops); + DoLoop(renderContext, loopStack); + } + } + + void DoLoop(RenderContext renderContext, Stack> loopStack) + { + if (loopStack.Count == 0) + { + RenderElement(renderContext); + } else + { + KeyValuePair 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 attributePair in Attributes) + renderContext.ContentWriter.Write(" {0}=\"{1}\"", attributePair.Key, attributePair.Value); + + foreach (KeyValuePair 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("", Name); + } + } + } + } + + bool checkConditions(RenderContext renderContext) + { + foreach (NewExpression condition in conditions) + if (!condition.IsTrue(renderContext)) + return false; + return true; + } + + } +} diff --git a/html/TemplateReader.cs b/html/TemplateReader.cs new file mode 100644 index 0000000..6704231 --- /dev/null +++ b/html/TemplateReader.cs @@ -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) + ); + } + } + + } +} diff --git a/html/TextElement.cs b/html/TextElement.cs new file mode 100644 index 0000000..8541eec --- /dev/null +++ b/html/TextElement.cs @@ -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); + } + } +} diff --git a/ln.templates.csproj b/ln.templates.csproj new file mode 100644 index 0000000..21463e2 --- /dev/null +++ b/ln.templates.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/ln.templates.test/HtmlTests.cs b/ln.templates.test/HtmlTests.cs new file mode 100644 index 0000000..dabd2de --- /dev/null +++ b/ln.templates.test/HtmlTests.cs @@ -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("\n\n\n Hello\n\n\n

Welcome to this example.

\n

{{ 'Ich bin ein ScriptText!' }}

\n

DateTime: {{ Date }}

\n

Ein bisschen JavaScript: {{ 5 + ' mal ' + 5 + ' = ' + (5*5) }}

\n\n"); + + 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(""); + tw.Flush(); + } + + using (FileStream fs = new FileStream("templates.tst/head.html", FileMode.CreateNew)) + using (TextWriter tw = new StreamWriter(fs)) + { + tw.Write("{{ title }}"); + 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()); + } + } +} diff --git a/ln.templates.test/ln.templates.test.csproj b/ln.templates.test/ln.templates.test.csproj new file mode 100644 index 0000000..01b8a74 --- /dev/null +++ b/ln.templates.test/ln.templates.test.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + diff --git a/script/IScriptContext.cs b/script/IScriptContext.cs new file mode 100644 index 0000000..72d89ed --- /dev/null +++ b/script/IScriptContext.cs @@ -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 ScriptObjectNames { get; } + object GetScriptObject(string itemName); + void SetScriptObject(string itemName, object value); + } +} diff --git a/script/NewExpression.cs b/script/NewExpression.cs new file mode 100644 index 0000000..74a3353 --- /dev/null +++ b/script/NewExpression.cs @@ -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; + } + } +} diff --git a/streams/CharStream.cs b/streams/CharStream.cs new file mode 100644 index 0000000..a2572d4 --- /dev/null +++ b/streams/CharStream.cs @@ -0,0 +1,20 @@ +using System; +using System.Text; +namespace ln.templates.streams +{ + public class CharStream : PeekAbleStream + { + 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)) + { + } + } +} diff --git a/streams/PeekableStream.cs b/streams/PeekableStream.cs new file mode 100644 index 0000000..ebc08ed --- /dev/null +++ b/streams/PeekableStream.cs @@ -0,0 +1,50 @@ +using System; +using System.Text; +namespace ln.templates.streams +{ + public class PeekAbleStream + { + 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; + + } +}