commit 09be9fb80fdff55862692955f344b94ebe23ec95 Author: Harald Wolff Date: Thu Feb 14 09:17:05 2019 +0100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf793ed --- /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..d2a731f --- /dev/null +++ b/Expression.cs @@ -0,0 +1,441 @@ +using System; +using System.Reflection; +using System.IO; +using System.Text; +using System.Collections.Generic; +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); + } + + + + + private void Compile() + { + StringReader reader = new StringReader(Source); + List tokens = new List(); + while (reader.Peek() != -1) + ReadToken(reader, tokens); + TopEval = BuildEval(tokens); + } + + private Eval BuildEval(List tokens) + { + Eval currentEval = null; + + while (tokens.Count > 0) + { + Token token = tokens[0]; + tokens.RemoveAt(0); + + switch (token.TokenType) + { + case TokenType.DOT: + token = tokens[0]; + tokens.RemoveAt(0); + if (token.TokenType != TokenType.NAME) + throw new ArgumentException("missing fieldname after ."); + currentEval = new Name(currentEval, token.Value); + break; + case TokenType.NAME: + currentEval = new Name(currentEval, token.Value); + break; + case TokenType.NUMBER: + currentEval = new Number(token.Value); + break; + case TokenType.STRING: + currentEval = new CString(token.Value); + break; + case TokenType.BRACKET: + if (token.Value.Equals("[")) + { + currentEval = new Indexer(currentEval, BuildEval(tokens)); + } else if (token.Value.Equals("]")) + { + return currentEval; + } + break; + } + } + return currentEval; + } + + 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)) + { + 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 == '[')| (ch == ']')) + { + reader.Read(); + tokens.Add(new Token(TokenType.BRACKET, 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()))) + { + 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, BRACKET } + 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 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); + // } + //} + + } + + + +} 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/MyClass.cs b/MyClass.cs new file mode 100644 index 0000000..d0cbc48 --- /dev/null +++ b/MyClass.cs @@ -0,0 +1,10 @@ +using System; +namespace ln.templates +{ + public class MyClass + { + public MyClass() + { + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7e6ca35 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("ln.templates")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("${AuthorCopyright}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/Scanner.cs b/Scanner.cs new file mode 100644 index 0000000..173e879 --- /dev/null +++ b/Scanner.cs @@ -0,0 +1,283 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using ln.templates.streams; +using System.Runtime.InteropServices; +namespace ln.templates +{ + + /** + * + * Expression Syntax + * + * expr := ( | ( | ) [ "(" ")" ] ) + * objpath := [ "." ] + * arguments := [ "," ] + * + * + **/ + + + //public static class Scanner + //{ + // private static char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(); + // private static char[] digits = "0123456789".ToCharArray(); + + // private static char[] keywordChars = letters.Concat(digits).Concat(new char[] { '_' }).ToArray(); + // private static char[] pathChars = keywordChars.Concat(new char[] { '.','/' }).ToArray(); + // private static char[] numberChars = digits.Concat(new char[] { '.' }).ToArray(); + + // private static char[] operatorChars = new char[] { '+', '-', '*', '/' }; + + + // private static char[] ScanValidCharacters(CharStream charStream,char[] validCharacters) + // { + // charStream.Mark(); + // while (!charStream.EndOfBuffer && validCharacters.Contains(charStream.Peek())){ + // charStream.Read(); + // } + // return charStream.GetMarkedIntervall(); + // } + + // public static String ScanKeyword(CharStream charStream) + // { + // if (!Char.IsLetter(charStream.Peek())) + // throw new ArgumentException(String.Format("ScanKeyword positioned on character {0}", charStream.Peek())); + + // return new string(ScanValidCharacters(charStream,keywordChars)); + // } + + // public static String ScanNumber(CharStream charStream) + // { + // if (!Char.IsDigit(charStream.Peek())) + // throw new ArgumentException(String.Format("ScanNumber positioned on character {0}", charStream.Peek())); + + // return new string(ScanValidCharacters(charStream, numberChars)); + // } + + // public static string ScanString(CharStream charStream) + // { + // if (charStream.Peek() != '"') + // throw new ArgumentException(String.Format("ScanString positioned on {0}", charStream.Peek())); + + // charStream.Read(); + // charStream.Mark(); + + // while (charStream.Peek() != '"') + // { + // if (charStream.Read() == '\\') + // { + // charStream.Read(); + // } + // } + + // string result = new string(charStream.GetMarkedIntervall()); + + // charStream.Read(); + + // return result; + // } + + // public static PathToken ScanPath(CharStream charStream) + // { + // if (charStream.Peek() != '/') + // throw new ArgumentException(String.Format("ScanPath positioned on character {0}", charStream.Peek())); + + // return new PathToken(new string(ScanValidCharacters(charStream, pathChars))); + // } + + // public static Token ScanObjectPath(CharStream charStream,ObjectPathToken parent = null){ + // if (!Char.IsLetter(charStream.Peek())) + // throw new ArgumentOutOfRangeException(String.Format("ScanObjectPath is not positioned on a letter: {0}",charStream.Peek())); + + // String component = ScanKeyword(charStream); + + // if (charStream.Peek() == '.') + // { + // charStream.Read(); + // return ScanObjectPath(charStream, new ObjectPathToken(component, parent)); + // } else if (charStream.Peek() == '(') + // { + // Token[] arguments = ScanArguments(charStream); + // return new CallToken(component, arguments, parent); + // } else { + // return new ObjectPathToken(component, parent); + // } + // } + + // public static char[] SeekProcessingInstruction(CharStream charStream) + // { + // charStream.Mark(); + // while (!charStream.EndOfBuffer) + // { + // if ((charStream.Remaining > 1) && (charStream.Peek(0) == '<') && (charStream.Peek(1) == '?')) + // break; + // charStream.Read(); + // } + // return charStream.GetMarkedIntervall(); + // } + + // public static Token ScanExpression(CharStream charStream) + // { + // while (!charStream.EndOfBuffer) + // { + // if (charStream.Peek() == '"') + // { + // return new ValueToken(ScanString(charStream)); + // } + // else if (Char.IsLetter(charStream.Peek())) + // { + // return ScanObjectPath(charStream); + // } + // else if (charStream.Peek() == '/') + // { + // return ScanPath(charStream); + // } + // else if (Char.IsDigit(charStream.Peek())) + // { + // return new NumberToken(ScanNumber(charStream)); + // } + // else + // { + // Console.WriteLine("Unexpected character: {0}", charStream.Read()); + // } + // } + + // return null; + // } + + // public static Token[] ScanArguments(CharStream charStream){ + + // if (charStream.Peek() != '(') + // throw new ArgumentException(String.Format("ScanArguments not positioned on (")); + + // charStream.Read(); + + // List arguments = new List(); + + // while (!charStream.EndOfBuffer && (charStream.Peek() != ')')) + // { + // if (Char.IsWhiteSpace(charStream.Peek())) + // { + // charStream.Read(); + // } else { + // arguments.Add(ScanExpression(charStream)); + // while (Char.IsWhiteSpace(charStream.Peek())) + // { + // charStream.Read(); + // } + // if (charStream.Peek() == ')') + // { + // break; + // } + // if (charStream.Peek() != ',') + // throw new ArgumentException(String.Format("expected , or ) after argument")); + // } + // } + // charStream.Read(); + // return arguments.ToArray(); + // } + + // public static Token[] ScanParameters(CharStream charStream) + // { + // List tokens = new List(); + + // while (!charStream.EndOfBuffer) + // { + // if ((charStream.Remaining > 1) && (charStream.Peek() == '?') && (charStream.Peek(1) == '>')) + // { + // charStream.Read(); + // charStream.Read(); + // break; + // } + // else if (Char.IsWhiteSpace(charStream.Peek())) + // { + // charStream.Read(); + // } else { + // tokens.Add(ScanExpression(charStream)); + // } + // } + // return tokens.ToArray(); + // } + + // public static void Scan(CharStream sourceStream, FormElement container) + // { + // while (!sourceStream.EndOfBuffer) + // { + // char[] textPart = SeekProcessingInstruction(sourceStream); + // if (textPart.Length > 0) + // { + // new Text(container, textPart); + // } + // if (!sourceStream.EndOfBuffer) + // { + // if (!ScanProcessingInstruction(sourceStream, container)) + // { + // return; + // } + // } + // } + // } + + // public static bool ScanProcessingInstruction(CharStream sourceStream, FormElement container) + // { + // if ((sourceStream.Remaining > 1) && (sourceStream.Peek(0) == '<') && (sourceStream.Peek(1) == '?')) + // { + // sourceStream.Read(); + // sourceStream.Read(); + + // if (sourceStream.Peek() == '=') + // { + // sourceStream.Read(); + // new Var(container, ScanParameters(sourceStream)); + // } + // else + // { + // String keyword = ScanKeyword(sourceStream); + // Token[] parameters = ScanParameters(sourceStream); + + // switch (keyword) + // { + // case "include": + // new Include(container, parameters); + // break; + // case "head": + // Scan(sourceStream, new Head(container)); + // break; + // case "set": + // new Set(container, ((ObjectPathToken)parameters[0]).Component ,parameters[1]); + // break; + // case "iterate": + // Scan(sourceStream, new Iterate(container,((ObjectPathToken)parameters[0]).Component,parameters[1])); + // break; + // case "if": + // Scan(sourceStream, new If(container, parameters[0])); + // break; + // case "editor": + // new Editor(container, parameters); + // break; + // case "frame": + // if (!(parameters[0] is PathToken)) + // throw new ArgumentException(" needs path as argument."); + + // container.Form.Frame = (PathToken)parameters[0]; + + // break; + // case "framed": + // new Framed(container); + // break; + // case "end": + // return false; + // } + // } + // return true; + // } + // else + // { + // throw new ArgumentException("ScanProcessingInstruction was not positioned on PI"); + // } + // } + + //} +} diff --git a/Template.cs b/Template.cs new file mode 100644 index 0000000..becc0c8 --- /dev/null +++ b/Template.cs @@ -0,0 +1,107 @@ +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 Element RootElement { get; private set; } + + public Expression FrameExpression { get; private set; } + + public Template(String sourceFilename) + { + SourceFilename = sourceFilename; + + LoadSource(null); + } + + public Template(String sourceFilename, TemplateProvider provider) + { + Provider = provider; + SourceFilename = sourceFilename; + + LoadSource(null); + } + + public Template(String source,String pseudoFilename,TemplateProvider provider) + { + Provider = provider; + SourceFilename = 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); + } + } + + TemplateReader templateReader = new TemplateReader(source); + + RootElement = templateReader.RootElement; + FrameExpression = templateReader.FrameExpression; + } + + public String Generate() + { + Context context = new Context(this); + return Generate(context); + } + public String Generate(Context context) + { + StringWriter writer = new StringWriter(); + RootElement.Generate(writer, context); + + if (FrameExpression != null) + { + Template frame = context.Provider.FindTemplate(FrameExpression.Evaluate(null) as string); + 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); + } + 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..2d2f95b --- /dev/null +++ b/TemplateProvider.cs @@ -0,0 +1,8 @@ +using System; +namespace ln.templates +{ + public abstract class TemplateProvider + { + public abstract Template FindTemplate(string templatePath); + } +} diff --git a/TemplateReader.cs b/TemplateReader.cs new file mode 100644 index 0000000..4adf78e --- /dev/null +++ b/TemplateReader.cs @@ -0,0 +1,150 @@ +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; + } + 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..96cc9dd --- /dev/null +++ b/elements/IncludeElement.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Collections; +using System.Runtime.Remoting.Contexts; + +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/ln.templates.csproj b/ln.templates.csproj new file mode 100644 index 0000000..42f94c4 --- /dev/null +++ b/ln.templates.csproj @@ -0,0 +1,53 @@ + + + + Debug + AnyCPU + {AD0267BB-F08C-4BE1-A88D-010D49041761} + Library + ln.templates + ln.templates + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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; + + } +}