Issue #3402: Implemented List tokens.

This commit is contained in:
Jeff 2015-08-25 17:03:50 +01:00
parent e2e539897e
commit 423fec90b3
6 changed files with 197 additions and 12 deletions

View File

@ -59,7 +59,7 @@ namespace Orchard.Tokens.Implementation {
private static Tuple<string, IEnumerable<string>> Parse(string text, bool hashMode) {
var tokens = new List<string>();
if (!String.IsNullOrEmpty(text)) {
var inToken = false;
var inTokenDepth = 0;
var tokenStart = 0;
for (var i = 0; i < text.Length; i++) {
var c = text[i];
@ -70,29 +70,32 @@ namespace Orchard.Tokens.Implementation {
continue;
}
}
else if (c == '}' && !(inToken)) {
else if (c == '}' && inTokenDepth == 0) {
if (i + 1 < text.Length && text[i + 1] == '}') {
text = text.Substring(0, i) + text.Substring(i + 1);
continue;
}
}
if (inToken) {
if (inTokenDepth > 0) {
if (c == '}') {
inToken = false;
var token = text.Substring(tokenStart + 1, i - tokenStart - 1);
tokens.Add(token);
inTokenDepth--;
if (inTokenDepth == 0) {
var token = text.Substring(tokenStart + 1, i - tokenStart - 1);
tokens.Add(token);
}
}
}
else if (!hashMode && c == '{') {
inToken = true;
tokenStart = i;
if (!hashMode && c == '{') {
if (inTokenDepth == 0) tokenStart = i;
inTokenDepth++;
}
else if (hashMode && c == '#'
&& i + 1 < text.Length && text[i + 1] == '{'
&& (i + 2 > text.Length || text[i + 2] != '{') ) {
inToken = true;
tokenStart = i+1;
&& (i + 2 > text.Length || text[i + 2] != '{') ) {
if (inTokenDepth == 0) tokenStart = i+1;
inTokenDepth++;
}
}
}

View File

@ -100,6 +100,7 @@
<ItemGroup>
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Providers\ContentTokens.cs" />
<Compile Include="Providers\ListTokens.cs" />
<Compile Include="Providers\UrlHelperTokens.cs" />
<Compile Include="Providers\RssPartFeedItemBuilder.cs" />
<Compile Include="Providers\RequestTokens.cs" />

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Web;
using Orchard.ContentManagement;
using Orchard.Localization;
using System.Linq;
namespace Orchard.Tokens.Providers {
public class ListTokens : ITokenProvider {
private readonly Func<ITokenizer> _tokenizer;
public ListTokens(Func<ITokenizer> tokenizer) {
_tokenizer = tokenizer;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void Describe(DescribeContext context) {
context.For("List", T("Lists"), T("Handles lists of Content Items"))
.Token("Join:*", T("Join:<Tokenized text>[,<separator>]"), T("Join each element in the list by applying the tokenized text and concatenating the output with the optional separator."))
.Token("SumInt:*", T("SumInt:<Tokenized text>"), T("Sum each element in the list by applying the tokenized text."))
.Token("SumFloat:*", T("SumFloat:<Tokenized text>"), T("Sum each element in the list by applying the tokenized text."))
.Token("SumDecimal:*", T("SumDecimal:<Tokenized text>"), T("Sum each element in the list by applying the tokenized text."))
.Token("First:*", T("First:<Tokenized text>"), T("Apply the tokenized text to the first element."))
.Token("Last:*", T("Last:<Tokenized text>"), T("Apply the tokenized text to the last element."))
.Token("Count", T("Count"), T("Get the list count."))
;
}
public void Evaluate(EvaluateContext context) {
context.For<IList<IContent>>("List", () => new List<IContent>())
.Token( // {List.ForEach:<string>[,<separator>]}
token => {
if (token.StartsWith("Join:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("Join:".Length));
}
return null;
},
(token, collection) => {
if (String.IsNullOrEmpty(token)) {
return String.Empty;
}
// Split the params to get the tokenized text and optional separator.
var index = token.IndexOf(',');
var text = index == -1 ? token : token.Substring(0, index);
if (String.IsNullOrEmpty(text)) {
return String.Empty;
}
var separator = index == -1 ? String.Empty : token.Substring(index + 1);
return String.Join(separator, collection.Select(content => _tokenizer().Replace(text, new {content}, new ReplaceOptions {Encoding = ReplaceOptions.NoEncode})));
})
.Token( // {List.SumInt:<string>}
token => {
if (token.StartsWith("SumInt:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("SumInt:".Length));
}
return null;
},
(token, collection) => collection.Sum(i => long.Parse(_tokenizer().Replace(token, new { Content = i }, new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }))))
.Token( // {List.SumFloat:<string>}
token => {
if (token.StartsWith("SumFloat:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("SumFloat:".Length));
}
return null;
},
(token, collection) => collection.Sum(i => double.Parse(_tokenizer().Replace(token, new { Content = i }, new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }))))
.Token( // {List.SumDecimal:<string>}
token => {
if (token.StartsWith("SumDecimal:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("SumDecimal:".Length));
}
return null;
},
(token, collection) => collection.Sum(i => decimal.Parse(_tokenizer().Replace(token, new { Content = i }, new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }))))
.Token( // {List.First:<string>}
token => {
if (token.StartsWith("First:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("First:".Length));
}
return null;
},
(token, list) => list.Any() ? _tokenizer().Replace(token, new { Content = list.First() }, new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }) : String.Empty)
.Token( // {List.Last:<string>}
token => {
if (token.StartsWith("Last:", StringComparison.OrdinalIgnoreCase)) {
// html decode to stop double encoding.
return HttpUtility.HtmlDecode(token.Substring("Last:".Length));
}
return null;
},
(token, list) => list.Any() ? _tokenizer().Replace(token, new { Content = list.Last() }, new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }) : String.Empty)
.Token( // {List.Count}
"Count",
list => list.Count)
;
}
}
}

View File

@ -0,0 +1,71 @@
using System.Collections.Generic;
using Autofac;
using NUnit.Framework;
using Orchard.ContentManagement;
using Orchard.Tokens.Implementation;
using Orchard.Tokens.Providers;
namespace Orchard.Tokens.Tests {
[TestFixture]
public class ListTokenTests {
private IContainer _container;
private ITokenizer _tokenizer;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<Tokenizer>().As<ITokenizer>();
builder.RegisterType<TokenManager>().As<ITokenManager>();
builder.RegisterType<ListTokens>().As<ITokenProvider>();
builder.RegisterType<TestTokenProvider>().As<ITokenProvider>();
//builder.RegisterType<Work<Tokenizer>>().As<Work<ITokenizer>>();
_container = builder.Build();
_tokenizer = _container.Resolve<ITokenizer>();
}
[Test]
public void TestListJoinTokens() {
Assert.That(_tokenizer.Replace("{List.Join:{Content.Id}}", new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}}), Is.EqualTo("510"));
}
[Test]
public void TestListJoinWithSeparatorTokens() {
Assert.That(_tokenizer.Replace("{List.Join:UserId is: {Content.Id},, }", new { List = new List<IContent> { new TestUser { Id = 5 }, new TestUser { Id = 10 } } }), Is.EqualTo("UserId is: 5, UserId is: 10"));
}
[Test]
public void TestListSumTokens() {
Assert.That(_tokenizer.Replace("{List.SumInt:{Content.Id}}", new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}}), Is.EqualTo("15"));
}
[Test]
public void TestListFirstTokens() {
Assert.That(_tokenizer.Replace("{List.First:{Content.Id}}", new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}}), Is.EqualTo("5"));
}
[Test]
public void TestListLastTokens() {
Assert.That(_tokenizer.Replace("{List.Last:{Content.Id}}", new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}}), Is.EqualTo("10"));
}
[Test]
public void TestListCountTokens() {
Assert.That(_tokenizer.Replace("{List.Count}", new {List = new List<IContent> {new TestUser(), new TestUser()}}), Is.EqualTo("2"));
}
[Test]
public void TestListTokensAdhereToEncodings() {
Assert.That(_tokenizer.Replace(
"{List.Join:<strong>{Content.Id}<strong>}",
new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}},
new ReplaceOptions {Encoding = ReplaceOptions.HtmlEncode}),
Is.EqualTo("&lt;strong&gt;5&lt;strong&gt;&lt;strong&gt;10&lt;strong&gt;"));
Assert.That(_tokenizer.Replace(
"{List.Join:<strong>{Content.Id}<strong>}",
new {List = new List<IContent> {new TestUser {Id = 5}, new TestUser {Id = 10}}},
new ReplaceOptions {Encoding = ReplaceOptions.NoEncode}),
Is.EqualTo("<strong>5<strong><strong>10<strong>"));
}
}
}

View File

@ -55,6 +55,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DateTokenTests.cs" />
<Compile Include="ListTokenTests.cs" />
<Compile Include="StubClock.cs" />
<Compile Include="StubOrchardServices.cs" />
<Compile Include="StubWorkContextAccessor.cs" />

View File

@ -38,6 +38,9 @@ namespace Orchard.Tokens.Tests {
.Token("CurrentUser", o => new TestUser { UserName = "CurrentUser" })
.Chain("CurrentUser", "User", o => new TestUser { UserName = "CurrentUser" });
context.For<IContent>("Content")
.Token("Id", u => u.Id);
context.For<IUser>("User", () => new TestUser { UserName = "CurrentUser" })
.Token("Name", u => u.UserName)
.Token("Email", u => u.Email)