mirror of https://github.com/ogoun/Zero.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
17 KiB
411 lines
17 KiB
using DOM.DSL.Model;
|
|
using DOM.DSL.Tokens;
|
|
using DOM.Services;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using ZeroLevel;
|
|
using ZeroLevel.DocumentObjectModel;
|
|
|
|
namespace DOM.DSL.Services
|
|
{
|
|
internal class CustomBlocks
|
|
{
|
|
private readonly IDictionary<string, IEnumerable<TToken>> _blocks =
|
|
new Dictionary<string, IEnumerable<TToken>>();
|
|
|
|
public void Append(string name, IEnumerable<TToken> tokens)
|
|
{
|
|
if (false == _blocks.ContainsKey(name))
|
|
_blocks.Add(name, tokens);
|
|
else
|
|
{
|
|
_blocks[name] = null!;
|
|
_blocks[name] = tokens;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<TToken> Get(string name)
|
|
{
|
|
if (_blocks.ContainsKey(name))
|
|
return _blocks[name];
|
|
return Enumerable.Empty<TToken>();
|
|
}
|
|
|
|
public bool Exists(string name)
|
|
{
|
|
return _blocks.ContainsKey(name);
|
|
}
|
|
}
|
|
|
|
internal class TRender
|
|
{
|
|
private readonly Document _document;
|
|
private readonly TEnvironment _environment;
|
|
private readonly CustomBlocks _blocks = new CustomBlocks();
|
|
internal TContainerFactory Factory { get; }
|
|
internal IDictionary<string, object> BufferDictionary { get; } = new Dictionary<string, object>();
|
|
internal DOMRenderElementCounter Counter { get; }
|
|
internal TRenderOptions Options { get; } = new TRenderOptions();
|
|
|
|
public TRender(Document document, TEnvironment environment)
|
|
{
|
|
_document = document;
|
|
_environment = environment;
|
|
Counter = new DOMRenderElementCounter();
|
|
Factory = new TContainerFactory(this);
|
|
}
|
|
|
|
public void Resolve(TToken token, Action<TContainer> handler, bool release = true, TContainer self = null!)
|
|
{
|
|
var self_copy = self == null ? null : Factory.Get(self.Current, self.Index);
|
|
try
|
|
{
|
|
if (token is TTextToken)
|
|
{
|
|
var container = Factory.Get(token.AsTextToken().Text);
|
|
handler(container);
|
|
if (release) Factory.Release(container);
|
|
}
|
|
else if (token is TElementToken)
|
|
{
|
|
var containers = ResolveElementToken(token.AsElementToken(), self_copy!);
|
|
foreach (var c in containers)
|
|
{
|
|
handler(c);
|
|
if (release) Factory.Release(c);
|
|
}
|
|
}
|
|
else if (token is TBlockToken)
|
|
{
|
|
var containers = ResolveBlockToken(token.AsBlockToken(), self_copy!);
|
|
foreach (var c in containers)
|
|
{
|
|
handler(c);
|
|
if (release) Factory.Release(c);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Factory.Release(self_copy!);
|
|
}
|
|
}
|
|
|
|
private TContainer[] ResolveElementToken(TElementToken token, TContainer self = null!)
|
|
{
|
|
TContainer container = null!;
|
|
switch (token.ElementName.Trim().ToLowerInvariant())
|
|
{
|
|
// External
|
|
case "now": container = Factory.Get(DateTime.Now); break;
|
|
case "utc": container = Factory.Get(DateTime.Now.ToUniversalTime()); break;
|
|
case "guid": container = Factory.Get(Guid.NewGuid()); break;
|
|
case "nowutc": container = Factory.Get(DateTime.UtcNow); break;
|
|
|
|
// Document
|
|
case "id": container = Factory.Get(_document.Id); break;
|
|
case "summary": container = Factory.Get(_document.Summary); break;
|
|
case "header": container = Factory.Get(_document.Header); break;
|
|
case "categories": container = Factory.Get(_document.Categories); break;
|
|
case "directions":
|
|
container = Factory.Get(_document.Categories.
|
|
Select(c => c.DirectionCode).Distinct().ToList()); break;
|
|
|
|
// Descriptive
|
|
case "desc":
|
|
case "descriptive": container = Factory.Get(_document.DescriptiveMetadata); break;
|
|
case "author": container = Factory.Get(_document.DescriptiveMetadata.Byline); break;
|
|
case "copyright": container = Factory.Get(_document.DescriptiveMetadata.CopyrightNotice); break;
|
|
case "created": container = Factory.Get(_document.DescriptiveMetadata.Created); break;
|
|
case "lang": container = Factory.Get(_document.DescriptiveMetadata.Language); break;
|
|
case "priority": container = Factory.Get(_document.DescriptiveMetadata.Priority); break;
|
|
case "source": container = Factory.Get(_document.DescriptiveMetadata.Source); break;
|
|
case "publisher": container = Factory.Get(_document.DescriptiveMetadata.Publisher); break;
|
|
case "meta":
|
|
case "headers": container = Factory.Get(_document.DescriptiveMetadata.Headers); break;
|
|
|
|
// Identifier
|
|
case "identifier": container = Factory.Get(_document.Identifier); break;
|
|
case "timestamp": container = Factory.Get(_document.Identifier.Timestamp); break;
|
|
case "datelabel": container = Factory.Get(_document.Identifier.DateLabel); break;
|
|
case "version": container = Factory.Get(_document.Identifier.Version); break;
|
|
|
|
// Tags
|
|
case "tags": container = Factory.Get(_document.TagMetadata); break;
|
|
case "keywords": container = Factory.Get(_document.TagMetadata.Keywords); break;
|
|
case "companies": container = Factory.Get(_document.TagMetadata.Companies); break;
|
|
case "persons": container = Factory.Get(_document.TagMetadata.Persons); break;
|
|
case "places": container = Factory.Get(_document.TagMetadata.Places); break;
|
|
|
|
case "var": container = Factory.Get(_environment.CustomVariables); break;
|
|
case "buf": container = Factory.Get(BufferDictionary); break;
|
|
case "env": container = Factory.Get(_environment); break;
|
|
case "counter": container = Factory.Get(Counter); break;
|
|
case "self": container = Factory.Get(self.Current, self.Index); break;
|
|
case "content": container = Factory.Get(new TContentElement(_document)); break;
|
|
case "aside": container = Factory.Get(_document.Attachments); break;
|
|
case "assotiations": container = Factory.Get(_document.Assotiations); break;
|
|
case "null": container = Factory.Get(null!); break;
|
|
case "empty": container = Factory.Get(string.Empty); break;
|
|
|
|
// Blocks
|
|
case "build":
|
|
{
|
|
if (token.NextToken is TPropertyToken)
|
|
{
|
|
var block = new TBlockToken(_blocks.Get(token.NextToken.AsPropertyToken().PropertyName));
|
|
var result = ResolveBlockToken(block, self);
|
|
container = Factory.Get(result.Where(c => c.Current != null!).Select(c => c.Current).ToList());
|
|
foreach (var c in result)
|
|
Factory.Release(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (container == null!) container = Factory.Get(null!);
|
|
|
|
if (token.NextToken is TPropertyToken)
|
|
{
|
|
return new[] { ResolvePropertyToken(token.NextToken.AsPropertyToken(), container) };
|
|
}
|
|
else if (token.NextToken is TFunctionToken)
|
|
{
|
|
return new[] { ResolveFunctionToken(token.NextToken.AsFunctionToken(), container) };
|
|
}
|
|
else if (token.NextToken is TSystemToken)
|
|
{
|
|
ApplyRenderCommand(token.NextToken.AsSystemToken());
|
|
}
|
|
return new[] { container };
|
|
}
|
|
|
|
private TContainer ResolvePropertyToken(TPropertyToken token, TContainer container)
|
|
{
|
|
string property_index = null!;
|
|
Resolve(token.PropertyIndex, c => property_index = c.ToString());
|
|
container.MoveToProperty(token.PropertyName, property_index);
|
|
if (token.NextToken is TPropertyToken)
|
|
{
|
|
return ResolvePropertyToken(token.NextToken.AsPropertyToken(), container);
|
|
}
|
|
else if (token.NextToken is TFunctionToken)
|
|
{
|
|
return ResolveFunctionToken(token.NextToken.AsFunctionToken(), container);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
private TContainer[] CalculateArguments(TFunctionToken token, TContainer self)
|
|
{
|
|
List<TContainer> args = new List<TContainer>();
|
|
foreach (var a in token?.FunctionArgs ?? Enumerable.Empty<TToken>())
|
|
{
|
|
Resolve(a, c => args.Add(c), false, self);
|
|
}
|
|
return args.ToArray();
|
|
}
|
|
|
|
private TContainer ResolveFunctionToken(TFunctionToken token, TContainer container)
|
|
{
|
|
var args_calc = new Func<TContainer, TContainer[]>(self => CalculateArguments(token, self));
|
|
container.ApplyFunction(token.FunctionName, args_calc);
|
|
if (token.NextToken is TPropertyToken)
|
|
{
|
|
container = ResolvePropertyToken(token.NextToken.AsPropertyToken(), container);
|
|
}
|
|
else if (token.NextToken is TFunctionToken)
|
|
{
|
|
container = ResolveFunctionToken(token.NextToken.AsFunctionToken(), container);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
private IEnumerable<TContainer> ResolveBlockToken(TBlockToken blockToken, TContainer self_parent = null!)
|
|
{
|
|
switch (blockToken.Name)
|
|
{
|
|
case "block":
|
|
{
|
|
string name = null!;
|
|
Resolve(blockToken.Condition, c => name = c.ToString(), true);
|
|
if (false == string.IsNullOrWhiteSpace(name))
|
|
{
|
|
_blocks.Append(name, blockToken.Body);
|
|
return Enumerable.Empty<TContainer>();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "flow":
|
|
{
|
|
return new List<TContainer>
|
|
{
|
|
ResolveFlowBlockToken(blockToken.Body)
|
|
};
|
|
}
|
|
case "if":
|
|
{
|
|
bool success = false;
|
|
Resolve(blockToken.Condition, c => success = c.As<bool>(), true, self_parent);
|
|
List<TContainer> result;
|
|
if (success)
|
|
{
|
|
var ls = self_parent == null ? null : Factory.Get(self_parent.Current, self_parent.Index);
|
|
result = ResolveSimpleBlockToken(blockToken, ls!);
|
|
Factory.Release(ls!);
|
|
}
|
|
else
|
|
{
|
|
result = new List<TContainer> { Factory.Get(null!) };
|
|
}
|
|
return result;
|
|
}
|
|
case "for":
|
|
{
|
|
var list = new List<TContainer>();
|
|
TContainer self_container = null!;
|
|
Resolve(blockToken.Condition, c => self_container = c, false, self_parent);
|
|
if (self_container != null!)
|
|
{
|
|
if (self_container.IsEnumerable)
|
|
{
|
|
int index = 0;
|
|
foreach (var t in (IEnumerable)self_container.Current)
|
|
{
|
|
var self = Factory.Get(t, index);
|
|
foreach (var bt in blockToken.Body)
|
|
{
|
|
Resolve(bt, c => list.Add(c), false, self);
|
|
}
|
|
Factory.Release(self);
|
|
index++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var bt in blockToken.Body)
|
|
{
|
|
Resolve(bt, c => list.Add(c), false, self_container);
|
|
}
|
|
}
|
|
}
|
|
Factory.Release(self_container!);
|
|
return list;
|
|
}
|
|
}
|
|
return ResolveSimpleBlockToken(blockToken, self_parent);
|
|
}
|
|
|
|
private List<TContainer> ResolveSimpleBlockToken(TBlockToken token, TContainer self = null!)
|
|
{
|
|
var block = new List<TContainer>();
|
|
foreach (var t in token.Body)
|
|
{
|
|
Resolve(t, c => block.Add(c), false, self);
|
|
}
|
|
return block;
|
|
}
|
|
|
|
private TContainer ResolveFlowBlockToken(IEnumerable<TToken> tokens)
|
|
{
|
|
var rules = new TFlowRules();
|
|
rules.Bootstrap();
|
|
foreach (var token in tokens)
|
|
{
|
|
if (token is TElementToken)
|
|
{
|
|
var function = token.AsElementToken()?.NextToken?.AsFunctionToken();
|
|
var elementName = token.AsElementToken()?.ElementName;
|
|
if (elementName != null!)
|
|
{
|
|
var functionName = function?.FunctionName ?? string.Empty;
|
|
var rule_token = function?.FunctionArgs == null ?
|
|
null :
|
|
new TBlockToken(function.FunctionArgs.Select(a => a.Clone()));
|
|
string special = null!;
|
|
if (functionName.Equals("special", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var args = new List<TContainer>();
|
|
foreach (var a in function?.FunctionArgs ?? Enumerable.Empty<TToken>())
|
|
{
|
|
Resolve(a, c => args.Add(c), false);
|
|
}
|
|
special = string.Join(",", args.Select(a => a.ToString()));
|
|
foreach (var a in args)
|
|
Factory.Release(a);
|
|
}
|
|
rules.UpdateRule(elementName, functionName, rule_token!, special);
|
|
}
|
|
}
|
|
}
|
|
return Factory.Get(DocumentContentReader.ReadAs<string>(_document, new TContentToStringConverter(this, rules)));
|
|
}
|
|
|
|
private void ApplyRenderCommand(TSystemToken token)
|
|
{
|
|
List<TContainer> _args = new List<TContainer>();
|
|
foreach (var a in token.Arg?.AsFunctionToken()?.FunctionArgs ?? Enumerable.Empty<TToken>())
|
|
{
|
|
Resolve(a, c => _args.Add(c), false);
|
|
}
|
|
var args = _args.ToArray();
|
|
switch (token.Command)
|
|
{
|
|
case "log":
|
|
if (args?.Length > 0)
|
|
{
|
|
Log.Debug(args[0].ToString(), args.Skip(1).ToArray());
|
|
}
|
|
break;
|
|
|
|
case "validate":
|
|
if (args?.Length == 1)
|
|
{
|
|
switch (args[0].ToString().Trim().ToLowerInvariant())
|
|
{
|
|
case "xml":
|
|
Options.ValidateAsXml = true;
|
|
break;
|
|
|
|
case "html":
|
|
Options.ValidateAsHtml = true;
|
|
break;
|
|
|
|
case "json":
|
|
Options.ValidateAsJson = true;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "fixwidth":
|
|
{
|
|
if (args?.Length == 1)
|
|
{
|
|
int width;
|
|
if (int.TryParse(args[0].ToString(), out width))
|
|
{
|
|
Options.MaxStringWidth = width;
|
|
}
|
|
else
|
|
{
|
|
Options.MaxStringWidth = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Options.MaxStringWidth = -1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
foreach (var a in args!)
|
|
{
|
|
Factory.Release(a);
|
|
}
|
|
}
|
|
}
|
|
} |