RegexMatchScorable.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
36 using Microsoft.Bot.Connector;
37 using System;
38 using System.Collections.Generic;
39 using System.Linq;
40 using System.Reflection;
41 using System.Text;
42 using System.Text.RegularExpressions;
43 using System.Threading;
44 using System.Threading.Tasks;
45 
46 namespace Microsoft.Bot.Builder.Scorables.Internals
47 {
48  [Serializable]
49  public abstract class AttributeString : Attribute, IEquatable<AttributeString>
50  {
51  protected abstract string Text { get; }
52 
53  public override string ToString()
54  {
55  return $"{this.GetType().Name}({this.Text})";
56  }
57 
58  bool IEquatable<AttributeString>.Equals(AttributeString other)
59  {
60  return other != null
61  && object.Equals(this.Text, other.Text);
62  }
63 
64  public override bool Equals(object other)
65  {
66  return base.Equals(other as AttributeString);
67  }
68 
69  public override int GetHashCode()
70  {
71  return this.Text.GetHashCode();
72  }
73  }
74 }
75 
76 namespace Microsoft.Bot.Builder.Scorables
77 {
82  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
83  [Serializable]
84  public sealed class RegexPatternAttribute : AttributeString
85  {
89  public readonly string Pattern;
90 
95  public RegexPatternAttribute(string pattern)
96  {
97  SetField.NotNull(out this.Pattern, nameof(pattern), pattern);
98  }
99 
100  protected override string Text
101  {
102  get
103  {
104  return this.Pattern;
105  }
106  }
107  }
108 }
109 
110 namespace Microsoft.Bot.Builder.Scorables.Internals
111 {
112  public sealed class RegexMatchScorableFactory : IScorableFactory<IResolver, Match>
113  {
114  private readonly Func<string, Regex> make;
115 
116  public RegexMatchScorableFactory(Func<string, Regex> make)
117  {
118  SetField.NotNull(out this.make, nameof(make), make);
119  }
120 
122  {
123  var specs =
124  from method in methods
125  from pattern in InheritedAttributes.For<RegexPatternAttribute>(method)
126  select new { method, pattern };
127 
128  var scorableByMethod = methods.ToDictionary(m => m, m => new MethodScorable(m));
129 
130  // for a given regular expression pattern, fold the corresponding method scorables together to enable overload resolution
131  var scorables =
132  from spec in specs
133  group spec by spec.pattern into patterns
134  let method = patterns.Select(m => scorableByMethod[m.method]).ToArray().Fold(BindingComparer.Instance)
135  let regex = this.make(patterns.Key.Pattern)
136  select new RegexMatchScorable<IBinding, IBinding>(regex, method);
137 
138  var all = scorables.ToArray().Fold(MatchComparer.Instance);
139 
140  return all;
141  }
142  }
143 
147  public static partial class RegexMatchScorable
148  {
149  public static readonly Func<Capture, string> GetOriginalString
150  = (Func<Capture, string>)
151  typeof(Capture)
152  .GetMethod("GetOriginalString", BindingFlags.Instance | BindingFlags.NonPublic)
153  .CreateDelegate(typeof(Func<Capture, string>));
154 
158  public static double ScoreFor(Match match)
159  {
160  var numerator = match.Value.Length;
161  var denominator = GetOriginalString(match).Length;
162  var score = ((double)numerator) / denominator;
163  return score;
164  }
165  }
166 
170  [Serializable]
171  public sealed class RegexMatchScorable<InnerState, InnerScore> : ResolverScorable<RegexMatchScorable<InnerState, InnerScore>.Scope, Match, InnerState, InnerScore>
172  {
173  private readonly Regex regex;
174 
175  public sealed class Scope : ResolverScope<InnerScore>
176  {
177  public readonly Regex Regex;
178  public readonly Match Match;
179 
180  public Scope(Regex regex, Match match, IResolver inner)
181  : base(inner)
182  {
183  SetField.NotNull(out this.Regex, nameof(regex), regex);
184  SetField.NotNull(out this.Match, nameof(match), match);
185  }
186 
187  public override bool TryResolve(Type type, object tag, out object value)
188  {
189  var name = tag as string;
190  if (name != null)
191  {
192  var capture = this.Match.Groups[name];
193  if (capture != null && capture.Success)
194  {
195  if (type.IsAssignableFrom(typeof(Capture)))
196  {
197  value = capture;
198  return true;
199  }
200  else if (type.IsAssignableFrom(typeof(string)))
201  {
202  value = capture.Value;
203  return true;
204  }
205  }
206  }
207 
208  if (type.IsAssignableFrom(typeof(Regex)))
209  {
210  value = this.Regex;
211  return true;
212  }
213 
214  if (type.IsAssignableFrom(typeof(Match)))
215  {
216  value = this.Match;
217  return true;
218  }
219 
220  var captures = this.Match.Captures;
221  if (type.IsAssignableFrom(typeof(CaptureCollection)))
222  {
223  value = captures;
224  return true;
225  }
226 
227  // i.e. for IActivity
228  return base.TryResolve(type, tag, out value);
229  }
230  }
231 
233  : base(inner)
234  {
235  SetField.NotNull(out this.regex, nameof(regex), regex);
236  }
237 
238  public override string ToString()
239  {
240  return $"{this.GetType().Name}({this.regex}, {this.inner})";
241  }
242 
243  protected override async Task<Scope> PrepareAsync(IResolver resolver, CancellationToken token)
244  {
245  IMessageActivity message;
246  if (!resolver.TryResolve(null, out message))
247  {
248  return null;
249  }
250 
251  var text = message.Text;
252  if (text == null)
253  {
254  return null;
255  }
256 
257  var match = this.regex.Match(text);
258  if (!match.Success)
259  {
260  return null;
261  }
262 
263  var scope = new Scope(this.regex, match, resolver);
264  scope.Item = resolver;
265  scope.Scorable = this.inner;
266  scope.State = await this.inner.PrepareAsync(scope, token);
267  return scope;
268  }
269 
270  protected override Match GetScore(IResolver resolver, Scope state)
271  {
272  return state.Match;
273  }
274  }
275 
276  public sealed class MatchComparer : IComparer<Match>
277  {
278  public static readonly IComparer<Match> Instance = new MatchComparer();
279 
280  private MatchComparer()
281  {
282  }
283 
284  int IComparer<Match>.Compare(Match one, Match two)
285  {
286  Func<Match, Pair<bool, double>> PairFor = match => Pair.Create
287  (
288  match.Success,
289  RegexMatchScorable.ScoreFor(match)
290  );
291 
292  var pairOne = PairFor(one);
293  var pairTwo = PairFor(two);
294  return pairOne.CompareTo(pairTwo);
295  }
296  }
297 }
override bool TryResolve(Type type, object tag, out object value)
Namespace for the Microsoft Bot Connector SDK.
Static helper methods for RegexMatchScorable.
string Text
Content for the message
IScorable< Item, Score > ScorableFor(IEnumerable< MethodInfo > methods)
Allow the scoring of items, with external comparison of scores, and enable the winner to take some ac...
Definition: Scorable.cs:49
This attribute is used to specify the regular expression pattern to be used when applying the regular...
Allow the resolution of values based on type and optionally tag.
Namespace for internal scorable implementation that is not useful for most developers and may change ...
Namespace for scorable interfaces, classes and compositions.
Namespace for internal machinery that is not useful for most developers and may change in the future...
readonly string Pattern
The regular expression pattern.
RegexMatchScorable(Regex regex, IScorable< IResolver, InnerScore > inner)
override async Task< Scope > PrepareAsync(IResolver resolver, CancellationToken token)
Scorable to represent binding arguments to a method&#39;s parameters.
override Match GetScore(IResolver resolver, Scope state)
bool TryResolve(Type type, object tag, out object value)
static readonly IComparer< IBinding > Instance
Namespace for the internal fibers machinery that is not useful for most developers and may change in ...
static double ScoreFor(Match match)
Calculate a normalized 0-1 score for a regular expression match.
Helper methods to enumerate inherited attributes for a method.
RegexPatternAttribute(string pattern)
Construct the RegexPatternAttribute.
Root namespace for the Microsoft Bot Builder SDK.