diff --git a/.idea/.idea.ln.templates/.idea/.gitignore b/.idea/.idea.ln.templates/.idea/.gitignore new file mode 100644 index 0000000..d4e626b --- /dev/null +++ b/.idea/.idea.ln.templates/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/.idea.ln.templates.iml +/modules.xml +/contentModel.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.idea.ln.templates/.idea/encodings.xml b/.idea/.idea.ln.templates/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.ln.templates/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.ln.templates/.idea/indexLayout.xml b/.idea/.idea.ln.templates/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.ln.templates/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.ln.templates/.idea/vcs.xml b/.idea/.idea.ln.templates/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.ln.templates/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.ln b/build.ln index afa2528..2a0641a 100644 --- a/build.ln +++ b/build.ln @@ -3,7 +3,7 @@ "dotnet" ], "env": { - "NUGET_SOURCE": "https://nexus.niclas-thobaben.de/repository/l--n.de/", + "NUGET_SOURCE": "https://nexus.l--n.de/repository/ln.net/", "CONFIGURATION": "Release" }, "stages": [ diff --git a/ln.http.templates/FileSystemTemplateRouter.cs b/ln.http.templates/FileSystemTemplateRouter.cs new file mode 100644 index 0000000..bf75b7b --- /dev/null +++ b/ln.http.templates/FileSystemTemplateRouter.cs @@ -0,0 +1,60 @@ +using ln.http.route; +using ln.templates; +using ln.templates.html; + +namespace ln.http.templates +{ + public class FileSystemTemplateRouter : HttpRoute, IDisposable + { + private HttpRouter _parentRouter; + private FileSystemTemplateSource _fileSystemTemplateSource; + + public FileSystemTemplateRouter(HttpRouter parentRouter, string mapPath, string fileSystemPath) + :this(parentRouter, mapPath, new FileSystemTemplateSource(fileSystemPath)) + { + } + + public FileSystemTemplateRouter(HttpRouter parentRouter, string mapPath, FileSystemTemplateSource fileSystemTemplateSource) + :base(HttpMethod.GET, mapPath) + { + _routerDelegate = RouteTemplate; + _fileSystemTemplateSource = fileSystemTemplateSource; + + _parentRouter = parentRouter; + _parentRouter?.Map(this); + } + + bool RouteTemplate(HttpRequestContext requestContext, string routePath) + { + if (routePath.EndsWith("/")) + routePath = string.Format("{0}index.html", routePath); + + if (!routePath.EndsWith(".html")) + return false; + + TemplateDocument templateDocument = _fileSystemTemplateSource.GetTemplateByPath(routePath.Substring(1)); + if (templateDocument is null) + return false; + + StreamedContent streamedContent = new StreamedContent("text/html"); + requestContext.Response = HttpResponse.OK().Content(streamedContent); + + RenderContext renderContext = new RenderContext( + new StreamWriter(streamedContent.ContentStream), + _fileSystemTemplateSource, new[] + { + new KeyValuePair("request", requestContext.Request), + new KeyValuePair("response", requestContext.Response), + new KeyValuePair("context", requestContext) + } + ); + templateDocument.RenderTemplate(renderContext); + return true; + } + + public void Dispose() + { + _parentRouter?.UnMap(this); + } + } +} \ No newline at end of file diff --git a/ln.http.templates/ln.http.templates.csproj b/ln.http.templates/ln.http.templates.csproj new file mode 100644 index 0000000..6bd7ddd --- /dev/null +++ b/ln.http.templates/ln.http.templates.csproj @@ -0,0 +1,17 @@ + + + + enable + enable + 0.9.2 + true + net5.0;net6.0 + default + + + + + + + + diff --git a/ln.templates.sln b/ln.templates.sln index bef4b5c..82acb01 100644 --- a/ln.templates.sln +++ b/ln.templates.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.templates", "ln.template EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.templates.test", "ln.templates.test\ln.templates.test.csproj", "{36593773-B517-4B62-BAD2-8AA632AA159E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.http.templates", "ln.http.templates\ln.http.templates.csproj", "{011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,5 +46,17 @@ Global {36593773-B517-4B62-BAD2-8AA632AA159E}.Release|x64.Build.0 = Release|Any CPU {36593773-B517-4B62-BAD2-8AA632AA159E}.Release|x86.ActiveCfg = Release|Any CPU {36593773-B517-4B62-BAD2-8AA632AA159E}.Release|x86.Build.0 = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|Any CPU.Build.0 = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|x64.ActiveCfg = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|x64.Build.0 = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|x86.ActiveCfg = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Debug|x86.Build.0 = Debug|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|Any CPU.ActiveCfg = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|Any CPU.Build.0 = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|x64.ActiveCfg = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|x64.Build.0 = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|x86.ActiveCfg = Release|Any CPU + {011E8B81-DF87-4AFB-BEA8-ADA6FD3F3665}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ln.templates.test/HtmlTests.cs b/ln.templates.test/HtmlTests.cs index a2fb594..920e463 100644 --- a/ln.templates.test/HtmlTests.cs +++ b/ln.templates.test/HtmlTests.cs @@ -17,56 +17,35 @@ namespace ln.templates.test [TestFixture()] public class HtmlTests { - [Test()] - public void TestCase() + + [TestCase()] + public void Test_Base() { - 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"); + Template template = new Template("tests/test_base.html", null); + Console.WriteLine("Template Source:\n{0}", template.Document.ToString()); - 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()); + StringWriter targetWriter = new StringWriter(); + template.Render(targetWriter); + + Console.WriteLine("Rendered Document:\n{0}", targetWriter.ToString()); } - [Test()] - public void TestFileSystemTemplateSource() + [TestCase()] + public void Test_Slots() { - 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"); + FileSystemTemplateSource templateSource = new FileSystemTemplateSource("tests"); + Template template = templateSource.GetTemplateByPath("test_slots.html"); + + Console.WriteLine("Template Source:\n{0}", template.Document.ToString()); - 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()); + StringWriter targetWriter = new StringWriter(); + template.Render(targetWriter); + + using (StreamWriter sw = new StreamWriter("test.out.slots.html")) + sw.Write(targetWriter.ToString()); + + Console.WriteLine("Rendered Document:\n{0}", targetWriter.ToString()); } + } } diff --git a/ln.templates.test/ln.templates.test.csproj b/ln.templates.test/ln.templates.test.csproj index bcd3b74..4c3df6f 100644 --- a/ln.templates.test/ln.templates.test.csproj +++ b/ln.templates.test/ln.templates.test.csproj @@ -1,9 +1,12 @@  - netcoreapp3.1 false + + net5.0;net6.0 + + default @@ -16,4 +19,16 @@ + + + Always + + + Always + + + Always + + + diff --git a/ln.templates.test/tests/test_base.html b/ln.templates.test/tests/test_base.html new file mode 100644 index 0000000..f128b70 --- /dev/null +++ b/ln.templates.test/tests/test_base.html @@ -0,0 +1,10 @@ + + + + Basic Test Template + + +

Let's check for iterations...

+

This is paragraph #{{i}}

+ + \ No newline at end of file diff --git a/ln.templates.test/tests/test_slot_a.html b/ln.templates.test/tests/test_slot_a.html new file mode 100644 index 0000000..e16b6f2 --- /dev/null +++ b/ln.templates.test/tests/test_slot_a.html @@ -0,0 +1,3 @@ +
+

Default Title

+
\ No newline at end of file diff --git a/ln.templates.test/tests/test_slots.html b/ln.templates.test/tests/test_slots.html new file mode 100644 index 0000000..90d57c4 --- /dev/null +++ b/ln.templates.test/tests/test_slots.html @@ -0,0 +1,16 @@ + + + +

Slot Test

+
+ + + +
+ + \ No newline at end of file diff --git a/ln.templates/Context.cs b/ln.templates/Context.cs new file mode 100644 index 0000000..407988c --- /dev/null +++ b/ln.templates/Context.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.IO; +using Jint; +using ln.templates.html; + +namespace ln.templates; + + +public class Context +{ + public Context(ITemplateResolver resolver, IEnumerable> scriptObjects, TextWriter targetWriter) + { + Resolver = resolver; + Engine = new Engine(); + foreach (KeyValuePair pair in scriptObjects) + Engine.SetValue(pair.Key, pair.Value); + TargetWriter = targetWriter; + } + public Context(ITemplateResolver resolver, Engine engine, TextWriter targetWriter) + { + Resolver = resolver; + Engine = engine; + TargetWriter = targetWriter; + } + public Context(Context source) + { + Resolver = source.Resolver; + Engine = source.Engine; + TargetWriter = source.TargetWriter; + } + + public Engine Engine { get; private set; } + public ITemplateResolver Resolver { get; } + + public TextWriter TargetWriter { get; } + + /** + * Slots to be used for v-include and v-frame + */ + private Dictionary _slots = new Dictionary(); + public bool TryGetSlot(string slotName, out Element slot) => _slots.TryGetValue(slotName, out slot); + public void SetSlot(string slotName, Element slot) => _slots[slotName] = slot; + public void ClearSlot(string slotName) => _slots.Remove(slotName); + public void ClearSlots() => _slots.Clear(); + + public void SetSlots(IEnumerable> slots) + { + _slots.Clear(); + foreach (var pair in slots) + _slots.Add(pair.Key, pair.Value); + } +} + diff --git a/ln.templates/Expression.cs b/ln.templates/Expression.cs deleted file mode 100644 index 4ad83b9..0000000 --- a/ln.templates/Expression.cs +++ /dev/null @@ -1,559 +0,0 @@ -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/ln.templates/FileSystemTemplateResolver.cs b/ln.templates/FileSystemTemplateResolver.cs new file mode 100644 index 0000000..264434f --- /dev/null +++ b/ln.templates/FileSystemTemplateResolver.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using ln.templates.html; + +namespace ln.templates +{ + public class FileSystemTemplateSource : ITemplateResolver + { + public String BasePath { get; } + public bool Throws { get; } + + Dictionary _templates = new Dictionary(); + + public FileSystemTemplateSource(String path,bool throws) + :this(path) + { + Throws = throws; + } + public FileSystemTemplateSource(String path) + { + path = Path.GetFullPath(path); + + if (!Directory.Exists(path)) + throw new FileNotFoundException(); + BasePath = path; + } + + public Template GetTemplateByPath(string path) => GetTemplateByPath(path, Throws); + public Template GetTemplateByPath(string path,bool _throw) + { + string templatePath = Path.Combine(BasePath, path); + if (!File.Exists(templatePath)) + { + if (_templates.ContainsKey(path)) + _templates.Remove(path); + + if (_throw) + throw new FileNotFoundException(); + return null; + } + + if (!_templates.TryGetValue(path,out Template template)) + { + template = new Template(templatePath, this); + _templates[path] = template; + } + + return template; + } + } +} diff --git a/ln.templates/FormContext.cs b/ln.templates/FormContext.cs deleted file mode 100644 index 5cac569..0000000 --- a/ln.templates/FormContext.cs +++ /dev/null @@ -1,254 +0,0 @@ -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/ln.templates/FormElement.cs b/ln.templates/FormElement.cs deleted file mode 100644 index 4dfa845..0000000 --- a/ln.templates/FormElement.cs +++ /dev/null @@ -1,35 +0,0 @@ -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/ln.templates/ITemplateResolver.cs b/ln.templates/ITemplateResolver.cs new file mode 100644 index 0000000..4bd5fe7 --- /dev/null +++ b/ln.templates/ITemplateResolver.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ln.templates.html; + +namespace ln.templates +{ + public interface ITemplateResolver + { + Template GetTemplateByPath(string path); + } +} diff --git a/ln.templates/RecursiveResolver.cs b/ln.templates/RecursiveResolver.cs new file mode 100644 index 0000000..92d7fbf --- /dev/null +++ b/ln.templates/RecursiveResolver.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Esprima.Ast; +using ln.templates.html; + +namespace ln.templates; + +public class RecursiveResolver : ITemplateResolver +{ + public RecursiveResolver(string path) + :this(null, path) + { + } + + private RecursiveResolver(RecursiveResolver parent, string path) + { + Parent = parent; + Path = path; + if (!Directory.Exists(path)) + throw new DirectoryNotFoundException(path); + } + + public RecursiveResolver Root => Parent?.Root ?? this; + public RecursiveResolver Parent { get; private set; } + public string Path { get; private set; } + + private bool IsAlive => Directory.Exists(Path); + + private Dictionary _children = new Dictionary(); + private Dictionary _templates = new Dictionary(); + + + public RecursiveResolver FindResolver(string path) => FindResolver( + System.IO.Path.GetDirectoryName(path)?.Split(System.IO.Path.PathSeparator) ?? Array.Empty()); + private RecursiveResolver FindResolver(Span pathElements) + { + if (pathElements.Length == 0) + throw new ArgumentOutOfRangeException(nameof(pathElements)); + + if (_children.TryGetValue(pathElements[0], out RecursiveResolver child)) + { + if (!child.IsAlive) + { + _children.Remove(pathElements[0]); + return null; + } + if (pathElements.Length > 1) + return child.FindResolver(pathElements.Slice(1)); + return child; + } + + return null; + } + + + public Template GetTemplateByPath(string templatePath) + { + Span pathElements = templatePath.Split(System.IO.Path.DirectorySeparatorChar); + RecursiveResolver resolver = FindResolver(pathElements.Slice(0, pathElements.Length - 1)); + return resolver?.GetTemplate(pathElements[^1]); + } + + public Template GetTemplate(string name) + { + string filename = System.IO.Path.Combine(Path, name); + if (File.Exists(filename)) + { + if (!_templates.TryGetValue(name, out Template template)) + { + template = new Template(filename, this); + _templates.Add(name, template); + } + + return template; + } + + return null; + } + + +} \ No newline at end of file diff --git a/ln.templates/Template.cs b/ln.templates/Template.cs index 6bd7b31..c9341e2 100644 --- a/ln.templates/Template.cs +++ b/ln.templates/Template.cs @@ -1,132 +1,56 @@ -using System; +using System; +using System.Collections.Generic; using System.IO; -using System.Text; -using ln.templates.elements; +using ln.templates.html; -namespace ln.templates +namespace ln.templates; + +public class Template { - public class Template + public Template(string filename, ITemplateResolver resolver) + :this(filename, resolver, null) + {} + + private Template(string filename, ITemplateResolver resolver, string source) { - public TemplateProvider Provider { get; private set; } - public String SourceFilename { get; private set; } - public String PseudoFilename { get; private set; } - public DateTime SourceTimeStamp { get; private set; } + FileName = filename; + Resolver = resolver; - public Element RootElement { get; private set; } - - public Expression FrameExpression { get; private set; } - - public Template(String sourceFilename) + if (source is null) { - SourceFilename = sourceFilename; - PseudoFilename = sourceFilename; - - LoadSource(null); + Load(); } - - public Template(String sourceFilename, TemplateProvider provider) + else { - Provider = provider; - SourceFilename = sourceFilename; - PseudoFilename = sourceFilename; - - LoadSource(null); + Document = TemplateReader.Parse(source); } - - 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); - } - } - } -} + + private void Load() + { + Document = TemplateReader.ReadTemplate(FileName); + LastWriteTime = File.GetLastWriteTime(FileName); + } + + + public ITemplateResolver Resolver { get; } + public string FileName { get; } + public TemplateDocument Document { get; private set; } + public DateTime LastWriteTime { get; private set; } + + public void Render(TextWriter target) => Render(target, Array.Empty>()); + public void Render(TextWriter target, IEnumerable> scriptObjects) + { + Context context = new Context(Resolver, scriptObjects, target); + Render(context); + } + + public void Render(Context context) + { + DateTime currentLastWriteTime = File.GetLastWriteTime(FileName); + if (currentLastWriteTime > LastWriteTime) + Load(); + + Document.RenderTemplate(context); + } +} \ No newline at end of file diff --git a/ln.templates/TemplateProvider.cs b/ln.templates/TemplateProvider.cs deleted file mode 100644 index 158191b..0000000 --- a/ln.templates/TemplateProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; -namespace ln.templates -{ - public interface TemplateProvider - { - Template FindTemplate(string templatePath); - } -} diff --git a/ln.templates/TemplateReader.cs b/ln.templates/TemplateReader.cs index c1254bd..78c41ca 100644 --- a/ln.templates/TemplateReader.cs +++ b/ln.templates/TemplateReader.cs @@ -1,151 +1,104 @@ -using System; -using System.Collections.Generic; -using ln.templates.elements; +// /** +// * 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; -using System.Text; +using System.Net; +using ln.templates.html; + namespace ln.templates { - public class TemplateReader + public class TemplateReader : ElementReader { - public ContainerElement RootElement { get; private set; } = new ContainerElement(null); - public ContainerElement CurrentElement { get; private set; } - public Expression FrameExpression { get; private set; } + public virtual TemplateDocument TemplateDocument => Document as TemplateDocument; - public String Source { get; private set; } - - public TemplateReader(String source) + protected TemplateReader() { - Source = source; - Parse(); } - private void Parse() + public override DocumentElement CreateDocument() => new TemplateDocument(); + public override Element CreateElement(string tagName) => TemplateElement.Create(tagName); + + public override void CloseTag(string tagName) { - CurrentElement = RootElement; - - while (Source.Length > 0) + Element saveCurrentElement = CurrentElement; + base.CloseTag(tagName); + + if (saveCurrentElement.HasAttribute("v-slot") && (saveCurrentElement.Parent is TemplateElement parentElement)) { - String t, op; - int i, j; + parentElement.SetSlot(saveCurrentElement.GetAttribute("v-slot"), saveCurrentElement); + parentElement.RemoveChild(saveCurrentElement); + saveCurrentElement.RemoveAttribute("v-slot"); + } + } - i = Source.IndexOf("<%"); - if (i == -1) + public override void Text(string text) + { + if (text.Contains("{{")) + { + int pOpen = 0; + int pClose = 0; + + while (pOpen < text.Length) { - t = Source; - op = null; - Source = ""; - } - else - { - t = Source.Substring(0, i); - j = Source.IndexOf("%>", i + 1); - if (j == -1) + pOpen = text.IndexOf("{{", pClose); + if (pOpen == -1) + pOpen = text.Length; + + string preText = text.Substring(pClose, pOpen - pClose); + if (preText.Length > 0) { - throw new FormatException(String.Format("missing '%>' in {0}", Source.Substring(i, 64))); + CurrentElement.AppendChild(CreateTextElement(preText)); } - op = Source.Substring(i + 2, j - i - 2); - Source = Source.Substring(j + 2); + + 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); + + pClose += 2; + + CurrentElement.AppendChild(new ExpressionElement(expr)); } - new TextElement(CurrentElement, t); - if (op != null) - ParseOP(op); + } else + { + CurrentElement.AppendChild( + CreateTextElement(text) + ); } } - private void ParseOP(String op) + + public static TemplateDocument Parse(string source) { - 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); - } + using (StringReader reader = new StringReader(source)) + return ReadTemplate(reader); } - } - - static class StringHelper - { - public static int IndexOf(this String s,Func predicate) + public static TemplateDocument ReadTemplate(string filename) { - 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/ln.templates/elements/IncludeElement.cs b/ln.templates/elements/IncludeElement.cs deleted file mode 100644 index 7cd04d3..0000000 --- a/ln.templates/elements/IncludeElement.cs +++ /dev/null @@ -1,45 +0,0 @@ -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/ln.templates/html/DocumentElement.cs b/ln.templates/html/DocumentElement.cs index a80f486..7a4c85b 100644 --- a/ln.templates/html/DocumentElement.cs +++ b/ln.templates/html/DocumentElement.cs @@ -19,9 +19,12 @@ namespace ln.templates.html { } + public string DocType { get; set; } + public override void Render(TextWriter writer) { - writer.Write(""); + if (DocType is not null) + writer.Write("", DocType); foreach (Element element in Children) element.Render(writer); diff --git a/ln.templates/html/Element.cs b/ln.templates/html/Element.cs index 5691e1b..5311cda 100644 --- a/ln.templates/html/Element.cs +++ b/ln.templates/html/Element.cs @@ -38,9 +38,17 @@ namespace ln.templates.html 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 virtual void RemoveAttribute(string attributeName) => attributes.Remove(attributeName); + public virtual string GetAttribute(string attributeName, string defaultValue) + { + if (attributes.TryGetValue(attributeName, out string value)) + return value; + return defaultValue; + } + public IEnumerable Children => children; - public void AppendChild(Element element) + public virtual void AppendChild(Element element) { if (element.Parent != null) element.Parent.RemoveChild(element); @@ -55,7 +63,7 @@ namespace ln.templates.html element.Parent = this; } } - public void RemoveChild(Element element) + public virtual void RemoveChild(Element element) { if (element.Parent != this) throw new KeyNotFoundException(); diff --git a/ln.templates/html/ElementReader.cs b/ln.templates/html/ElementReader.cs index 8bcaf16..1220764 100644 --- a/ln.templates/html/ElementReader.cs +++ b/ln.templates/html/ElementReader.cs @@ -61,6 +61,12 @@ namespace ln.templates.html { Pop(); } + + public override void DOCTYPE(string type) + { + Document.DocType = type; + } + public override void Attribute(string attributeName, string attributeValue) { CurrentElement.SetAttribute(attributeName, attributeValue); diff --git a/ln.templates/html/ExpressionElement.cs b/ln.templates/html/ExpressionElement.cs index 435beab..33200a1 100644 --- a/ln.templates/html/ExpressionElement.cs +++ b/ln.templates/html/ExpressionElement.cs @@ -29,10 +29,10 @@ namespace ln.templates.html writer.Write("{{{{{0}}}}}", ExpressionText); } - public override void RenderTemplate(RenderContext renderContext) + public override void RenderTemplate(Context renderContext) { - object o = Expression.Resolve(renderContext); - renderContext.ContentWriter.Write(o?.ToString()); + object o = Expression.Resolve(renderContext.Engine); + renderContext.TargetWriter.Write(o?.ToString()); } diff --git a/ln.templates/html/FileSystemTemplateSource.cs b/ln.templates/html/FileSystemTemplateSource.cs deleted file mode 100644 index 4878149..0000000 --- a/ln.templates/html/FileSystemTemplateSource.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace ln.templates.html -{ - public class FileSystemTemplateSource : ITemplateSource - { - public String BasePath { get; } - public bool Throws { get; } - - Dictionary templateDocuments = new Dictionary(); - - public FileSystemTemplateSource(String path,bool throws) - :this(path) - { - Throws = throws; - } - public FileSystemTemplateSource(String path) - { - if (!Directory.Exists(path)) - throw new FileNotFoundException(); - BasePath = path; - } - - public TemplateDocument GetTemplateByPath(string path) => GetTemplateByPath(path, Throws); - public TemplateDocument GetTemplateByPath(string path,bool _throw) - { - string templatePath = Path.Combine(BasePath, path); - if (!File.Exists(templatePath)) - { - if (templateDocuments.ContainsKey(path)) - templateDocuments.Remove(path); - - if (_throw) - throw new FileNotFoundException(); - return null; - } - - 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/ln.templates/html/HtmlReader.cs b/ln.templates/html/HtmlReader.cs index be21375..b9f3679 100644 --- a/ln.templates/html/HtmlReader.cs +++ b/ln.templates/html/HtmlReader.cs @@ -13,7 +13,7 @@ using System.Collections.Generic; using System.Text; namespace ln.templates.html { - public class HtmlReader + public class HtmlReader { public HtmlReader() { @@ -124,7 +124,7 @@ namespace ln.templates.html } void ReadLWS(TextReader textReader) => ReadToken(textReader, char.IsWhiteSpace); - public string ReadTagName(TextReader textReader) => ReadTokenLWS(textReader, (ch) => char.IsLetterOrDigit(ch)); + public string ReadTagName(TextReader textReader) => ReadTokenLWS(textReader, (ch) => char.IsLetterOrDigit(ch) || (ch == '-')); public string ReadAttributeName(TextReader textReader) => ReadTokenLWS(textReader, (ch) => !char.IsWhiteSpace(ch) && (ch != '\0') && (ch != '"') && (ch != '\'') && (ch != '>') && (ch != '/') && (ch != '=')); public string ReadAttributeValue(TextReader textReader) { @@ -147,7 +147,7 @@ namespace ln.templates.html public void ReadText(TextReader textReader) { StringBuilder stringBuilder = new StringBuilder(); - while (textReader.Peek() != '<') + while ((textReader.Peek() >= 0) && (textReader.Peek() != '<')) stringBuilder.Append((char)textReader.Read()); Text(stringBuilder.ToString()); diff --git a/ln.templates/html/ITemplateSource.cs b/ln.templates/html/ITemplateSource.cs deleted file mode 100644 index 1dde637..0000000 --- a/ln.templates/html/ITemplateSource.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ln.templates.html -{ - public interface ITemplateSource - { - TemplateDocument GetTemplateByPath(string path); - } -} diff --git a/ln.templates/html/RenderContext.cs b/ln.templates/html/RenderContext.cs deleted file mode 100644 index 39406fd..0000000 --- a/ln.templates/html/RenderContext.cs +++ /dev/null @@ -1,39 +0,0 @@ -// /** -// * 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/ln.templates/html/TemplateDocument.cs b/ln.templates/html/TemplateDocument.cs index 4d45f19..5ef8c8f 100644 --- a/ln.templates/html/TemplateDocument.cs +++ b/ln.templates/html/TemplateDocument.cs @@ -13,30 +13,22 @@ 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) + + public void RenderTemplate(Context renderContext) { - if (!withoutDocType) - renderContext.ContentWriter.Write(""); + if (DocType is not null) + renderContext.TargetWriter.Write("", DocType); - foreach (Element element in Children) + foreach (var child in Children) { - if (element is TemplateElement templateElement) - { + if (child is TemplateElement templateElement) templateElement.RenderTemplate(renderContext); - } else - { - renderContext.ContentWriter.Write(element.ToString()); - } + else + child.Render(renderContext.TargetWriter); } - - renderContext.ContentWriter.Flush(); } } } diff --git a/ln.templates/html/TemplateElement.SlotElement.cs b/ln.templates/html/TemplateElement.SlotElement.cs new file mode 100644 index 0000000..e6ce7b0 --- /dev/null +++ b/ln.templates/html/TemplateElement.SlotElement.cs @@ -0,0 +1,23 @@ +namespace ln.templates.html; + +public partial class TemplateElement +{ + private class SlotElement : TemplateElement + { + public SlotElement(string tagName) + :base(tagName){} + + public string Name => GetAttribute("name", ""); + public override void RenderElement(Context renderContext) + { + if (renderContext.TryGetSlot(Name, out Element slot)) + { + RenderElements(new Context(renderContext), new []{ slot }); + } + else + { + RenderElements(new Context(renderContext), Children); + } + } + } +} \ No newline at end of file diff --git a/ln.templates/html/TemplateElement.cs b/ln.templates/html/TemplateElement.cs index dc2794d..7b9d5d3 100644 --- a/ln.templates/html/TemplateElement.cs +++ b/ln.templates/html/TemplateElement.cs @@ -10,24 +10,33 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Jint.Native; +using Jint.Native.Object; using ln.templates.script; namespace ln.templates.html { - public class TemplateElement : Element + public partial class TemplateElement : Element { + private static string[] hiddenTags = new[] { "template", "slot" }; + Dictionary expressions = new Dictionary(); Dictionary loops = new Dictionary(); List conditions = new List(); - NewExpression includeExpression; + private Dictionary _slots = new Dictionary(); + + private NewExpression classExpression; bool hideTag; public TemplateElement(string tagName) :base(tagName) { - hideTag = "template".Equals(tagName.ToLower()); + hideTag = hiddenTags.Contains(tagName.ToLower()); } public override void SetAttribute(string attributeName, string attributeValue) @@ -36,27 +45,50 @@ namespace ln.templates.html 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.Equals(":class", StringComparison.InvariantCultureIgnoreCase)) + classExpression = new NewExpression(attributeValue); + else if (attributeName.StartsWith("::")) + base.SetAttribute(attributeName, attributeValue); else if (attributeName[0] == ':') expressions.Add(attributeName.Substring(1), new NewExpression(attributeValue)); else base.SetAttribute(attributeName, attributeValue); } - public virtual void RenderTemplate(RenderContext renderContext) + public void SetSlot(string slotName, Element slotElement) { - if (loops.Count == 0) - { - RenderElement(renderContext); - } else - { - Stack> loopStack = new Stack>(loops); - DoLoop(renderContext, loopStack); - } + _slots.Add(slotName, slotElement); + } + + /** + * This is the render method to be called from external callers + */ + public virtual void RenderTemplate(Context renderContext) + { + if (loops.Count == 0) + { + RenderElement(renderContext); + } + else + { + Stack> loopStack = + new Stack>(loops); + DoLoop(renderContext, loopStack); + } } - void DoLoop(RenderContext renderContext, Stack> loopStack) + private Template TemplateFromExpression(Context renderContext, NewExpression expression) + { + object templateValue = expression.Resolve(renderContext.Engine); + if (!(templateValue is Template template)) + { + template = renderContext.Resolver.GetTemplateByPath((string)templateValue); + } + + return template; + } + + protected virtual void DoLoop(Context renderContext, Stack> loopStack) { if (loopStack.Count == 0) { @@ -64,77 +96,193 @@ namespace ln.templates.html } else { KeyValuePair loop = loopStack.Pop(); - IEnumerable enumerable = (IEnumerable)loop.Value.Resolve(renderContext); - foreach (object o in enumerable) + IEnumerable enumerable = (IEnumerable)loop.Value.Resolve(renderContext.Engine); + if (enumerable != null) { - renderContext.SetScriptObject(loop.Key, o); - DoLoop(renderContext, loopStack); + bool hadBinding = renderContext.Engine.Realm.GlobalEnv.HasBinding(loop.Key); + JsValue _save = null; + if (hadBinding) + _save = renderContext.Engine.Realm.GlobalEnv.GetBindingValue(loop.Key, false); + try + { + + foreach (object o in enumerable) + { + renderContext.Engine.SetValue(loop.Key, o); + DoLoop(renderContext, loopStack); + } + } + finally + { + if (hadBinding) + renderContext.Engine.Realm.GlobalEnv.SetMutableBinding(loop.Key, _save, false); + else + renderContext.Engine.Realm.GlobalEnv.DeleteBinding(loop.Key); + } } loopStack.Push(loop); } } - void RenderElement(RenderContext renderContext) + /** + * This is the render method to be called to render the element itself (tag, attributes, children, ... ) + */ + public virtual void RenderElement(Context renderContext) { if (checkConditions(renderContext)) { - if (includeExpression != null) + if (TryGetAttribute(renderContext, "v-include", out string templatePath)) { - object includeValue = includeExpression.Resolve(renderContext); - if (!(includeValue is TemplateDocument includeTemplate)) - { - includeTemplate = renderContext.TemplateSource.GetTemplateByPath((string)includeValue); - } - includeTemplate.RenderTemplate(renderContext, true); + Template template = renderContext.Resolver.GetTemplateByPath(templatePath); + Context context = new Context(renderContext); + if (this._slots.Count == 0) + context.SetSlot("", this); + else + context.SetSlots(this._slots); + + template.Render(context); } else { if (!hideTag) { - renderContext.ContentWriter.Write("<{0}", Name); - foreach (KeyValuePair attributePair in Attributes) - renderContext.ContentWriter.Write(" {0}=\"{1}\"", attributePair.Key, attributePair.Value); - - foreach (KeyValuePair keyValuePair in expressions) + object classObject = null; + if (classExpression is not null) { - object value = keyValuePair.Value.Resolve(renderContext); - if (value != null) + classObject = renderContext.Engine.Evaluate("(function(){ return " + classExpression + .ExpressionText + ";})()"); + } + + renderContext.TargetWriter.Write("<{0}", Name); + foreach (KeyValuePair attributePair in Attributes) + { + if (attributePair.Key.Equals("class") && (classObject is ObjectInstance classObjectInstance)) { - renderContext.ContentWriter.Write(" {0}=\"{1}\"", keyValuePair.Key, value); + StringBuilder valueBuilder = new StringBuilder(attributePair.Value); + foreach (var property in classObjectInstance.GetOwnProperties()) + { + if ((property.Value.Value is JsBoolean jsBooleanValue) && + jsBooleanValue.Equals(JsBoolean.True)) + valueBuilder.AppendFormat(" {0}", property.Key.ToString()); + } + + renderContext.TargetWriter.Write(" {0}=\"{1}\"", attributePair.Key, + valueBuilder.ToString()); + } + else + { + renderContext.TargetWriter.Write(" {0}=\"{1}\"", attributePair.Key, + attributePair.Value); } } - renderContext.ContentWriter.Write(">"); + foreach (KeyValuePair keyValuePair in expressions) + { + object value = keyValuePair.Value.Resolve(renderContext.Engine); + if (value != null) + { + renderContext.TargetWriter.Write(" {0}=\"{1}\"", keyValuePair.Key, value); + } + } + + renderContext.TargetWriter.Write(">"); } if (!IsVoid) { - foreach (Element element in Children) - { - if (element is TemplateElement templateElement) - { - templateElement.RenderTemplate(renderContext); - } - else - { - renderContext.ContentWriter.Write(element.ToString()); - } - } - + RenderElements(renderContext, Children); + if (!hideTag) - renderContext.ContentWriter.Write("", Name); + renderContext.TargetWriter.Write("", Name); } } } } - bool checkConditions(RenderContext renderContext) + public string GetAttribute(Context context, string attributeName) + { + if (expressions.TryGetValue(attributeName, out NewExpression expression)) + return expression.Resolve(context.Engine).ToString(); + return GetAttribute(attributeName); + } + public bool TryGetAttribute(Context context, string attributeName, out string attributeValue) + { + if (expressions.TryGetValue(attributeName, out NewExpression expression)) + { + attributeValue = expression.Resolve(context.Engine).ToString(); + return true; + } + else if (HasAttribute(attributeName)) + { + attributeValue = GetAttribute(attributeName); + return true; + } + + attributeValue = null; + return false; + } + + protected void RenderElements(Context renderContext, IEnumerable elements) + { + foreach (Element element in elements) + { + if (element is TemplateElement templateElement) + { + templateElement.RenderTemplate(renderContext); + } + else + { + renderContext.TargetWriter.Write(element.ToString()); + } + } + } + + bool checkConditions(Context renderContext) { foreach (NewExpression condition in conditions) - if (!condition.IsTrue(renderContext)) + if (!condition.IsTrue(renderContext.Engine)) return false; return true; } + class TemplateScriptElement : TemplateElement + { + private StringBuilder scriptBuilder = new StringBuilder(); + + public TemplateScriptElement() + : base("template-script") + { + } + + public override void AppendChild(Element element) + { + if (element is TextElement textElement) + { + scriptBuilder.Append(textElement.Text); + } + else + { + throw new NotSupportedException(); + } + } + + + public override void RenderElement(Context renderContext) => + renderContext.Engine.Execute(scriptBuilder.ToString()); + + } + + public static TemplateElement Create(string tagName) + { + switch (tagName) + { + case "slot": + return new SlotElement(tagName); + case "template-script": + return new TemplateScriptElement(); + default: + return new TemplateElement(tagName); + } + } } } diff --git a/ln.templates/html/TemplateReader.cs b/ln.templates/html/TemplateReader.cs deleted file mode 100644 index 6704231..0000000 --- a/ln.templates/html/TemplateReader.cs +++ /dev/null @@ -1,70 +0,0 @@ -// /** -// * 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/ln.templates/ln.templates.csproj b/ln.templates/ln.templates.csproj index e4ebb05..d628acd 100644 --- a/ln.templates/ln.templates.csproj +++ b/ln.templates/ln.templates.csproj @@ -1,7 +1,10 @@  - netcoreapp3.1 + 0.4.2 + net5.0;net6.0 + default + true 0.2.1-ci @@ -13,7 +16,7 @@ - + diff --git a/ln.templates/script/IScriptContext.cs b/ln.templates/script/IScriptContext.cs index 72d89ed..ae7c0e1 100644 --- a/ln.templates/script/IScriptContext.cs +++ b/ln.templates/script/IScriptContext.cs @@ -9,12 +9,12 @@ // **/ using System; using System.Collections.Generic; +using Jint; + namespace ln.templates.script { public interface IScriptContext { - IEnumerable ScriptObjectNames { get; } - object GetScriptObject(string itemName); - void SetScriptObject(string itemName, object value); + Engine GetEngine(); } } diff --git a/ln.templates/script/NewExpression.cs b/ln.templates/script/NewExpression.cs index 74a3353..5be2889 100644 --- a/ln.templates/script/NewExpression.cs +++ b/ln.templates/script/NewExpression.cs @@ -9,8 +9,6 @@ // **/ using System; using Jint; -using Jint.Native; -using Jint.Native.String; namespace ln.templates.script { @@ -18,26 +16,20 @@ namespace ln.templates.script { public string ExpressionText { get; } - public NewExpression(string expression) { ExpressionText = expression; } - public virtual object Resolve(IScriptContext scriptContext) + public virtual object Resolve() => Resolve(new Engine()); + public virtual object Resolve(Engine engine) => + engine.Evaluate(ExpressionText).ToObject(); + + public virtual bool IsTrue() => IsTrue(new Engine()); + public virtual bool IsTrue(Engine engine) { - 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); + object resolved = Resolve(engine); if (resolved == null) return false; if (resolved is bool b) diff --git a/ln.templates/streams/CharStream.cs b/ln.templates/streams/CharStream.cs deleted file mode 100644 index a2572d4..0000000 --- a/ln.templates/streams/CharStream.cs +++ /dev/null @@ -1,20 +0,0 @@ -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/ln.templates/streams/PeekableStream.cs b/ln.templates/streams/PeekableStream.cs deleted file mode 100644 index ebc08ed..0000000 --- a/ln.templates/streams/PeekableStream.cs +++ /dev/null @@ -1,50 +0,0 @@ -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; - - } -}