PageRenderTime 82ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Caliburn.Micro.Silverlight/Parser.cs

https://github.com/paulcbetts/CaliburnMicro
C# | 271 lines | 199 code | 35 blank | 37 comment | 40 complexity | bb054c5f19c96c76acd945dc7ec3bcae MD5 | raw file
  1. namespace Caliburn.Micro
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text.RegularExpressions;
  8. using System.Windows;
  9. using System.Windows.Data;
  10. using EventTrigger = System.Windows.Interactivity.EventTrigger;
  11. using TriggerBase = System.Windows.Interactivity.TriggerBase;
  12. using System.Text;
  13. /// <summary>
  14. /// Parses text into a fully functional set of <see cref="TriggerBase"/> instances with <see cref="ActionMessage"/>.
  15. /// </summary>
  16. public static class Parser
  17. {
  18. static readonly Regex LongFormatRegularExpression = new Regex(@"^[\s]*\[[^\]]*\][\s]*=[\s]*\[[^\]]*\][\s]*$");
  19. /// <summary>
  20. /// Parses the specified message text.
  21. /// </summary>
  22. /// <param name="target">The target.</param>
  23. /// <param name="text">The message text.</param>
  24. /// <returns>The triggers parsed from the text.</returns>
  25. public static IEnumerable<TriggerBase> Parse(DependencyObject target, string text) {
  26. if(string.IsNullOrEmpty(text))
  27. return new TriggerBase[0];
  28. var triggers = new List<TriggerBase>();
  29. var messageTexts = Split(text, ';');
  30. foreach (var messageText in messageTexts) {
  31. var triggerPlusMessage = LongFormatRegularExpression.IsMatch(messageText)
  32. ? Split(messageText, '=')
  33. : new[] { null, messageText };
  34. var messageDetail = triggerPlusMessage.Last()
  35. .Replace("[", string.Empty)
  36. .Replace("]", string.Empty)
  37. .Trim();
  38. var trigger = CreateTrigger(target, triggerPlusMessage.Length == 1 ? null : triggerPlusMessage[0]);
  39. var message = CreateMessage(target, messageDetail);
  40. trigger.Actions.Add(message);
  41. triggers.Add(trigger);
  42. }
  43. return triggers;
  44. }
  45. /// <summary>
  46. /// The function used to generate a trigger.
  47. /// </summary>
  48. /// <remarks>The parameters passed to the method are the the target of the trigger and string representing the trigger.</remarks>
  49. public static Func<DependencyObject, string, TriggerBase> CreateTrigger = (target, triggerText) => {
  50. if(triggerText == null) {
  51. var defaults = ConventionManager.GetElementConvention(target.GetType());
  52. return defaults.CreateTrigger();
  53. }
  54. var triggerDetail = triggerText
  55. .Replace("[", string.Empty)
  56. .Replace("]", string.Empty)
  57. .Replace("Event", string.Empty)
  58. .Trim();
  59. return new EventTrigger { EventName = triggerDetail };
  60. };
  61. /// <summary>
  62. /// Creates an instance of <see cref="ActionMessage"/> by parsing out the textual dsl.
  63. /// </summary>
  64. /// <param name="target">The target of the message.</param>
  65. /// <param name="messageText">The textual message dsl.</param>
  66. /// <returns>The created message.</returns>
  67. public static System.Windows.Interactivity.TriggerAction CreateMessage(DependencyObject target, string messageText) {
  68. var openingParenthesisIndex = messageText.IndexOf('(');
  69. if(openingParenthesisIndex < 0)
  70. openingParenthesisIndex = messageText.Length;
  71. var closingParenthesisIndex = messageText.LastIndexOf(')');
  72. if(closingParenthesisIndex < 0)
  73. closingParenthesisIndex = messageText.Length;
  74. var core = messageText.Substring(0, openingParenthesisIndex).Trim();
  75. var message = InterpretMessageText(target, core);
  76. var withParameters = message as IHaveParameters;
  77. if(withParameters != null) {
  78. if(closingParenthesisIndex - openingParenthesisIndex > 1) {
  79. var paramString = messageText.Substring(openingParenthesisIndex + 1, closingParenthesisIndex - openingParenthesisIndex - 1);
  80. var parameters = SplitParameters(paramString);
  81. foreach(var parameter in parameters)
  82. withParameters.Parameters.Add(CreateParameter(target, parameter.Trim()));
  83. }
  84. }
  85. return message;
  86. }
  87. /// <summary>
  88. /// Function used to parse a string identified as a message.
  89. /// </summary>
  90. public static Func<DependencyObject, string, System.Windows.Interactivity.TriggerAction> InterpretMessageText = (target, text) => {
  91. return new ActionMessage { MethodName = Regex.Replace(text, "^Action", string.Empty).Trim() };
  92. };
  93. /// <summary>
  94. /// Function used to parse a string identified as a message parameter.
  95. /// </summary>
  96. public static Func<DependencyObject, string, Parameter> CreateParameter = (target, parameterText) => {
  97. var actualParameter = new Parameter();
  98. if(parameterText.StartsWith("'") && parameterText.EndsWith("'"))
  99. actualParameter.Value = parameterText.Substring(1, parameterText.Length - 2);
  100. else if(MessageBinder.SpecialValues.ContainsKey(parameterText.ToLower()) || char.IsNumber(parameterText[0]))
  101. actualParameter.Value = parameterText;
  102. else if(target is FrameworkElement) {
  103. var fe = (FrameworkElement)target;
  104. var nameAndBindingMode = parameterText.Split(':').Select(x => x.Trim()).ToArray();
  105. var index = nameAndBindingMode[0].IndexOf('.');
  106. View.ExecuteOnLoad(fe, delegate {
  107. BindParameter(
  108. fe,
  109. actualParameter,
  110. index == -1 ? nameAndBindingMode[0] : nameAndBindingMode[0].Substring(0, index),
  111. index == -1 ? null : nameAndBindingMode[0].Substring(index + 1),
  112. nameAndBindingMode.Length == 2 ? (BindingMode)Enum.Parse(typeof(BindingMode), nameAndBindingMode[1], true) : BindingMode.OneWay
  113. );
  114. });
  115. }
  116. return actualParameter;
  117. };
  118. /// <summary>
  119. /// Creates a binding on a <see cref="Parameter"/>.
  120. /// </summary>
  121. /// <param name="target">The target to which the message is applied.</param>
  122. /// <param name="parameter">The parameter object.</param>
  123. /// <param name="elementName">The name of the element to bind to.</param>
  124. /// <param name="path">The path of the element to bind to.</param>
  125. /// <param name="bindingMode">The binding mode to use.</param>
  126. public static void BindParameter(FrameworkElement target, Parameter parameter, string elementName, string path, BindingMode bindingMode) {
  127. var element = elementName == "$this"
  128. ? target
  129. : BindingScope.GetNamedElements(target).FindName(elementName);
  130. if(element == null)
  131. return;
  132. if(string.IsNullOrEmpty(path))
  133. path = ConventionManager.GetElementConvention(element.GetType()).ParameterProperty;
  134. var binding = new Binding(path) {
  135. Source = element,
  136. Mode = bindingMode
  137. };
  138. #if SILVERLIGHT
  139. var expression = (BindingExpression)BindingOperations.SetBinding(parameter, Parameter.ValueProperty, binding);
  140. var field = element.GetType().GetField(path + "Property", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
  141. if(field == null)
  142. return;
  143. ConventionManager.ApplySilverlightTriggers(element, (DependencyProperty)field.GetValue(null), x => expression, null, null);
  144. #else
  145. binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
  146. BindingOperations.SetBinding(parameter, Parameter.ValueProperty, binding);
  147. #endif
  148. }
  149. static string[] Split(string message, char separator) {
  150. //Splits a string using the specified separator, if it is found outside of relevant places
  151. //delimited by [ and ]
  152. string str;
  153. var list = new List<string>();
  154. var builder = new StringBuilder();
  155. int squareBrackets = 0;
  156. foreach(var current in message) {
  157. //Square brackets are used as delimiters, so only separators outside them count...
  158. if(current == '[')
  159. squareBrackets++;
  160. else if(current == ']')
  161. squareBrackets--;
  162. else if(current == separator) {
  163. if(squareBrackets == 0) {
  164. str = builder.ToString();
  165. if(!string.IsNullOrEmpty(str))
  166. list.Add(builder.ToString());
  167. builder.Length = 0;
  168. continue;
  169. }
  170. }
  171. builder.Append(current);
  172. }
  173. str = builder.ToString();
  174. if(!string.IsNullOrEmpty(str))
  175. list.Add(builder.ToString());
  176. return list.ToArray();
  177. }
  178. static string[] SplitParameters(string parameters) {
  179. //Splits parameter string taking into account brackets...
  180. var list = new List<string>();
  181. var builder = new StringBuilder();
  182. bool isInString = false;
  183. int curlyBrackets = 0;
  184. int squareBrackets = 0;
  185. int roundBrackets = 0;
  186. for(int i = 0; i < parameters.Length; i++) {
  187. var current = parameters[i];
  188. if(current == '"') {
  189. if(i == 0 || parameters[i - 1] != '\\')
  190. isInString = !isInString;
  191. }
  192. if(!isInString) {
  193. switch(current) {
  194. case '{':
  195. curlyBrackets++;
  196. break;
  197. case '}':
  198. curlyBrackets--;
  199. break;
  200. case '[':
  201. squareBrackets++;
  202. break;
  203. case ']':
  204. squareBrackets--;
  205. break;
  206. case '(':
  207. roundBrackets++;
  208. break;
  209. case ')':
  210. roundBrackets--;
  211. break;
  212. default:
  213. if(current == ',' && roundBrackets == 0 && squareBrackets == 0 && curlyBrackets == 0) {
  214. //The only commas to be considered as parameter separators are outside:
  215. //- Strings
  216. //- Square brackets (to ignore indexers)
  217. //- Parantheses (to ignore method invocations)
  218. //- Curly brackets (to ignore initializers and Bindings)
  219. list.Add(builder.ToString());
  220. builder.Length = 0;
  221. continue;
  222. }
  223. break;
  224. }
  225. }
  226. builder.Append(current);
  227. }
  228. list.Add(builder.ToString());
  229. return list.ToArray();
  230. }
  231. }
  232. }