Skip to content

Commit c6ac623

Browse files
authored
Merge pull request #160 from czf/2.0AdvancedSearch
2.0 advanced search
2 parents 17c95f3 + 27f2b91 commit c6ac623

File tree

7 files changed

+514
-0
lines changed

7 files changed

+514
-0
lines changed

RedditSharp/Reddit.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Linq;
66
using System.Security.Authentication;
77
using System.Threading.Tasks;
8+
using RedditSharp.Search;
89
using DefaultWebAgent = RedditSharp.WebAgent;
10+
using System.Linq.Expressions;
911

1012
namespace RedditSharp
1113
{
@@ -37,6 +39,19 @@ public class Reddit
3739
private const string GetLiveEventUrl = "https://www.reddit.com/live/{0}/about";
3840

3941
#endregion
42+
private IAdvancedSearchFormatter _searchFormatter;
43+
private IAdvancedSearchFormatter SearchFormatter
44+
{
45+
get
46+
{
47+
if(_searchFormatter == null)
48+
{
49+
_searchFormatter = new DefaultSearchFormatter();
50+
}
51+
return _searchFormatter;
52+
}
53+
set => _searchFormatter = value;
54+
}
4055

4156
#region Properties
4257
internal IWebAgent WebAgent { get; set; }
@@ -382,6 +397,15 @@ public Listing<T> Search<T>(string query, Sorting sortE = Sorting.Relevance, Tim
382397
return Listing<T>.Create(this.WebAgent, string.Format(SearchUrl, query, sort, time), max, 100);
383398
}
384399

400+
public Listing<Post> AdvancedSearch(Expression<Func<AdvancedSearchFilter, bool>> searchFilter, Sorting sortE = Sorting.Relevance, TimeSorting timeE = TimeSorting.All)
401+
{
402+
string query = SearchFormatter.Format(searchFilter);
403+
string sort = sortE.ToString().ToLower();
404+
string time = timeE.ToString().ToLower();
405+
string final = string.Format(SearchUrl, query, sort, time);
406+
return new Listing<Post>(this,final,WebAgent);
407+
}
408+
385409
/// <summary>
386410
/// Return a <see cref="Listing{T}"/> of items matching search with a given time period.
387411
/// </summary>
@@ -394,6 +418,7 @@ public Listing<T> Search<T>(string query, Sorting sortE = Sorting.Relevance, Tim
394418
/// <param name="timeE">Order by <see cref="TimeSorting"/></param>
395419
/// <param name="max">Maximum number of records to return. -1 for unlimited.</param>
396420
/// <returns></returns>
421+
[Obsolete("time search was discontinued by reddit",true)]
397422
public Listing<T> SearchByTimestamp<T>(DateTime from, DateTime to, string query = "", string subreddit = "", Sorting sortE = Sorting.Relevance, TimeSorting timeE = TimeSorting.All, int max = -1) where T : Thing
398423
{
399424
string sort = sortE.ToString().ToLower();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace RedditSharp.Search
8+
{
9+
public sealed class AdvancedSearchFilter
10+
{
11+
public string Author;
12+
public string Flair;
13+
public bool IsNsfw;
14+
public bool IsSelf;
15+
public string SelfText;
16+
public string Site;
17+
public string Subreddit;
18+
public string Title;
19+
public string Url;
20+
21+
22+
private AdvancedSearchFilter() { }
23+
24+
25+
}
26+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Reflection;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace RedditSharp.Search
10+
{
11+
public class DefaultSearchFormatter : IAdvancedSearchFormatter
12+
{
13+
#region Constants
14+
private const string BOOL_PROPERTY_PREFIX = "Is";
15+
#endregion Constants
16+
17+
18+
string IAdvancedSearchFormatter.Format(Expression<Func<AdvancedSearchFilter, bool>> search)
19+
{
20+
Expression expression = null;
21+
Stack<Expression> expressionStack = new Stack<Expression>();
22+
Stack<FormatInfo> formatInfoStack = new Stack<FormatInfo>();
23+
expressionStack.Push(search.Body);
24+
Stack<string> searchStack = new Stack<string>();
25+
while (expressionStack.Count > 0) {
26+
expression = expressionStack.Pop();
27+
switch (expression)
28+
{
29+
case MemberExpression memberExpression:
30+
MemberExpressionHelper(memberExpression, searchStack, formatInfoStack);
31+
break;
32+
case UnaryExpression unaryExpression:
33+
UnaryExpressionHelper(unaryExpression, expressionStack, formatInfoStack);
34+
break;
35+
case BinaryExpression binaryExpression:
36+
BinaryExpressionHelper(binaryExpression, expressionStack, formatInfoStack);
37+
break;
38+
case ConstantExpression constantExpresssion:
39+
searchStack.Push(ConstantExpressionHelper(constantExpresssion));
40+
break;
41+
case MethodCallExpression methodCallExpression:
42+
searchStack.Push(MethodCallExpressionHelper(methodCallExpression));
43+
break;
44+
default:
45+
throw new NotImplementedException(expression.ToString());
46+
}
47+
}
48+
49+
Stack<string> compoundSearchStack = new Stack<string>();
50+
while (formatInfoStack.Count > 0)
51+
{
52+
FormatInfo current = formatInfoStack.Pop();
53+
string[] formatParameters = new string[current.ParameterCount];
54+
int currentCount = current.ParameterCount;
55+
while (currentCount > 0)
56+
{
57+
formatParameters[formatParameters.Length - currentCount] = current.IsCompound ? compoundSearchStack.Pop() : searchStack.Pop();
58+
currentCount--;
59+
}
60+
61+
compoundSearchStack.Push(string.Format(current.Pattern, formatParameters));
62+
63+
}
64+
65+
return compoundSearchStack.Pop();
66+
}
67+
68+
private string MethodCallExpressionHelper(MethodCallExpression expression)
69+
{
70+
var o = InvokeGetExpression(expression);
71+
return o.ToString();
72+
}
73+
74+
private string ConstantExpressionHelper(ConstantExpression constantExpresssion)
75+
{
76+
return constantExpresssion.ToString().Replace("\"","");
77+
}
78+
79+
private void BinaryExpressionHelper(BinaryExpression expression, Stack<Expression> expressionStack, Stack<FormatInfo> formatInfoStack)
80+
{
81+
if(IsAdvancedSearchMemberExpression(expression.Left) && IsAdvancedSearchMemberExpression(expression.Right))
82+
{
83+
throw new InvalidOperationException("Cannot filter by comparing to fields.");
84+
}
85+
else if(IsAdvancedSearchMemberExpression(expression.Right))
86+
{
87+
expressionStack.Push(expression.Left);
88+
expressionStack.Push(expression.Right);
89+
}
90+
else
91+
{
92+
expressionStack.Push(expression.Right);
93+
expressionStack.Push(expression.Left);
94+
}
95+
96+
if (expression.NodeType != ExpressionType.Equal)
97+
{
98+
formatInfoStack.Push(expression.ToFormatInfo());
99+
}
100+
}
101+
102+
103+
private void UnaryExpressionHelper(UnaryExpression expression, Stack<Expression> expressionStack,Stack<FormatInfo> formatInfoStack)
104+
{
105+
formatInfoStack.Push(expression.ToFormatInfo());
106+
expressionStack.Push(expression.Operand);
107+
}
108+
109+
private void MemberExpressionHelper(MemberExpression expression, Stack<string> searchStack, Stack<FormatInfo> formatInfoStack)
110+
{
111+
MemberInfo member = expression.Member;
112+
113+
if (member.DeclaringType == typeof(AdvancedSearchFilter))
114+
{
115+
string result = member.Name.Replace(BOOL_PROPERTY_PREFIX, string.Empty).ToLower();
116+
formatInfoStack.Push(expression.ToFormatInfo());
117+
searchStack.Push(result);
118+
if (expression.Type == typeof(bool))
119+
{
120+
searchStack.Push("1");
121+
}
122+
}
123+
else
124+
{
125+
searchStack.Push(InvokeGetExpression(expression).ToString());
126+
}
127+
}
128+
129+
private static object InvokeGetExpression(Expression expression)
130+
{
131+
var objectMember = Expression.Convert(expression, typeof(object));
132+
133+
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
134+
135+
var getter = getterLambda.Compile();
136+
137+
return getter();
138+
}
139+
140+
private static bool IsAdvancedSearchMemberExpression(Expression expression)
141+
{
142+
MemberExpression memberExpression = expression as MemberExpression;
143+
return memberExpression?.Member.DeclaringType == typeof(AdvancedSearchFilter);
144+
}
145+
146+
internal class FormatInfo
147+
{
148+
public string Pattern { get; private set; }
149+
public int ParameterCount { get; private set; }
150+
public bool IsCompound { get; private set; }
151+
152+
public FormatInfo(string pattern, int parameterCount = 0, bool isCompound = false)
153+
{
154+
Pattern = pattern;
155+
ParameterCount = parameterCount;
156+
IsCompound = isCompound;
157+
}
158+
159+
internal static FormatInfo Not = new FormatInfo("NOT(+{0}+)", 1, true);
160+
internal static FormatInfo NotEqual = Not;
161+
internal static FormatInfo AndAlso = new FormatInfo("(+{0}+AND+{1}+)", 2, true);
162+
internal static FormatInfo OrElse = new FormatInfo("(+{0}+OR+{1}+)", 2, true);
163+
internal static FormatInfo MemberAccess = new FormatInfo("{1}:{0}", 2);
164+
}
165+
}
166+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
6+
namespace RedditSharp.Search
7+
{
8+
public interface IAdvancedSearchFormatter
9+
{
10+
11+
/// <summary>
12+
/// use an expression to create a search for reddit
13+
/// </summary>
14+
/// <param name="search"></param>
15+
/// <returns>the string representing the search expression in reddits search format</returns>
16+
/// <remarks>
17+
/// has to be Expression Func https://michael-mckenna.com/func-t-vs-expression-func-t-in-linq/
18+
/// </remarks>
19+
string Format(Expression<Func<AdvancedSearchFilter, bool>> search);
20+
}
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
4+
namespace RedditSharp.Search
5+
{
6+
public static class SearchExtensions
7+
{
8+
internal static DefaultSearchFormatter.FormatInfo ToFormatInfo(this Expression expression)
9+
{
10+
ExpressionType? type = expression?.NodeType;
11+
switch (type)
12+
{
13+
case ExpressionType.Not:
14+
case ExpressionType.NotEqual:
15+
return DefaultSearchFormatter.FormatInfo.Not;
16+
case ExpressionType.Equal:
17+
throw new NotImplementedException("Currently not supporting Equal expression.");
18+
case ExpressionType.AndAlso:
19+
return DefaultSearchFormatter.FormatInfo.AndAlso;
20+
case ExpressionType.MemberAccess:
21+
return DefaultSearchFormatter.FormatInfo.MemberAccess;
22+
case ExpressionType.OrElse:
23+
return DefaultSearchFormatter.FormatInfo.OrElse;
24+
}
25+
throw new NotImplementedException($"{type.ToString()} is not implemented.");
26+
}
27+
28+
}
29+
}

0 commit comments

Comments
 (0)