diff --git a/CalcBinding/CalcBinding.cs b/CalcBinding/CalcBinding.cs
index 659f445..f9bc2f2 100644
--- a/CalcBinding/CalcBinding.cs
+++ b/CalcBinding/CalcBinding.cs
@@ -52,7 +52,7 @@ public override object ProvideValue(IServiceProvider serviceProvider)
if (sourcePropertiesPathesWithPositions.Count() == 1)
{
- var binding = new System.Windows.Data.Binding(sourcePropertiesPathesWithPositions.Single().Item1)
+ var binding = new System.Windows.Data.Binding(sourcePropertiesPathesWithPositions.Single().LocalPath)
{
Mode = Mode,
NotifyOnSourceUpdated = NotifyOnSourceUpdated,
@@ -113,7 +113,7 @@ public override object ProvideValue(IServiceProvider serviceProvider)
foreach (var sourcePropertyPathWithPositions in sourcePropertiesPathesWithPositions)
{
- var binding = new System.Windows.Data.Binding(sourcePropertyPathWithPositions.Item1);
+ var binding = new System.Windows.Data.Binding(sourcePropertyPathWithPositions.LocalPath);
if (Source != null)
binding.Source = Source;
@@ -153,55 +153,62 @@ private Type GetPropertyType(IServiceProvider serviceProvider)
///
///
///
- private string GetExpressionTemplate(string path, List>> pathes)
+ private string GetExpressionTemplate(string fullpath, List pathes)
{
- var result = "";
- var sourceIndex = 0;
-
- while (sourceIndex < path.Length)
+ //Flatten parsed paths, get all merged indexes and corresponding localpath keys
+ var pathmap = new List>(); //Item1 = LocalPath, Item2 = order of localpath, Item3 = position of localpath in fullpath
+
+ for (int i = 0; i < pathes.Count; i++)
{
- var replaced = false;
- for (int index = 0; index < pathes.Count; index++)
+ foreach (var index in pathes[i].MergedIndexes)
{
- var replace = index.ToString("{0}");
- var positions = pathes[index].Item2;
- var sourcePropertyPath = pathes[index].Item1;
+ var t = new Tuple(pathes[i].LocalPath, i, index);
+ pathmap.Add(t);
+ }
+ }
+ pathmap = pathmap.OrderBy(x => x.Item3).ToList();
- if (positions.Contains(sourceIndex))
- {
- result += replace;
- sourceIndex += sourcePropertyPath.Length;
- replaced = true;
- break;
- }
+ //Iterate through ascending index values and rebuild the fullpath with replaced values
+ StringBuilder sb = new StringBuilder();
+ int currentindex = 0;
+ foreach (var m in pathmap)
+ {
+ var index = m.Item3;
+ var localpath = m.Item1;
+ var pathorder = m.Item2;
+
+ //Either we are appending the local path, or we are appending what is between the consecutive map values
+ if (currentindex < index)
+ {
+ var s = fullpath.Substring(currentindex, index - currentindex);
+ sb.Append(s);
+ currentindex += s.Length;
}
- if (!replaced)
+ if (currentindex == index)
{
- result += path[sourceIndex];
- sourceIndex++;
+ sb.AppendFormat("{{{0}}}", pathorder);
+ currentindex += localpath.Length;
}
}
+ //Fill in the remaining characters
+ var remaininglength = fullpath.Length - currentindex;
+ var substr = fullpath.Substring(currentindex, remaininglength);
+ sb.Append(substr);
+ var result = sb.ToString();
return result;
}
///
/// Find and return all sourceProperties pathes in Path string
///
- ///
+ ///
/// List of pathes and its start positions
- private List>> GetSourcePropertiesPathes(string normPath)
+ private List GetSourcePropertiesPathes(string fullPath)
{
- var operators = new []
- {
- "(", ")", "+", "-", "*", "/", "%", "^", "&&", "||",
- "&", "|", "?", ":", "<=", ">=", "<", ">", "==", "!=", "!", ","
- };
-
// temporary solution of problem: all string content shouldn't be parsed. Solution - remove strings from sourcePath.
//todo: better solution is to use parser PARSER!!
- var pathsList = GetPathes(normPath, operators);
//detect all start positions
@@ -215,44 +222,116 @@ private List>> GetSourcePropertiesPathes(string normPath
// not other symbols. So, following code perform this check
//may be that task solved by using PARSER!
- var pathIndexList = pathsList
- .Select(path => new Tuple>(path, new List()))
+
+ //Get the set of valid paths
+ var pathsList = GetPathes(fullPath)
+ .Where(p => p.ValidPath)
+ .Where(p => p.LocalPath != "null")
.ToList();
- foreach (var path in pathIndexList)
+ //Merge the groups.
+ //We are combining each distinct value of the LocalPath, merging the differing indexes into the MergedIndexes list
+ //This is so that we don't have duplicate property evaluations for the same data, improving performance
+ var pathGroups = pathsList.GroupBy(p => p.LocalPath);
+ foreach (var g in pathGroups)
{
- var indexes = Regex.Matches(normPath, path.Item1).Cast().Select(m => m.Index).ToList();
+ var first = g.First();
+ first.MergedIndexes = g.Select(x => x.StartIndex).ToList();
+ }
- foreach (var index in indexes)
- {
- bool startPosIsOperator = index == 0;
+ pathsList = pathsList.Where(p => p.MergedIndexes?.Count > 0).ToList();
+
+ return pathsList;
+ }
- foreach (var op in operators)
- if (index >= op.Length && normPath.Substring(index - op.Length, op.Length) == op)
- startPosIsOperator = true;
+ class ParsedPath
+ {
+ public static string[] operators = new[]
+ {
+ "(", ")", "+", "-", "*", "/", "%", "^", "&&", "||",
+ "&", "|", "?", ":", "<=", ">=", "<", ">", "==", "!=", "!", ","
+ };
- bool endPosIsOperator = index + path.Item1.Length == normPath.Length;
+ static string[] opsWithoutParenthesis = operators.Except(new[] { "(", ")" }).ToArray();
- foreach (var op in operators)
- if (index + path.Item1.Length <= normPath.Length - op.Length && normPath.Substring(index + path.Item1.Length, op.Length) == op)
- endPosIsOperator = true;
+ public string FullPath { get; set; }
+ public string LocalPath { get; set; }
+ public int StartIndex { get; set; }
+ public int EndIndex { get { return StartIndex + LocalPath.Length - 1; } }
+ public List MergedIndexes { get; set; }
- if (startPosIsOperator && endPosIsOperator)
- path.Item2.Add(index);
+ public bool ValidPath
+ {
+ get
+ {
+ return StartPosIsOperator && EndPosIsOperator;
+ }
+ }
+
+ public bool StartPosIsOperator
+ {
+ get
+ {
+ if (StartIndex == 0)
+ return true;
+ return operators.Any(op =>
+ op.Length <= StartIndex &&
+ op == FullPath.Substring(StartIndex - op.Length, op.Length));
+ }
+ }
+
+ public bool EndPosIsOperator
+ {
+ get
+ {
+ if (StartIndex + LocalPath.Length == FullPath.Length)
+ return true;
+ return operators.Any(op =>
+ FullPath.Length - op.Length >= StartIndex + LocalPath.Length &&
+ op == FullPath.Substring(StartIndex + LocalPath.Length, op.Length));
+ }
+ }
+
+ public bool LocalPathIsNotAtEdge
+ {
+ get
+ {
+ return (StartIndex > 0 && EndIndex < FullPath.Length - 1);
+ }
+ }
+
+ public bool SurroundedByParenthesis
+ {
+ get
+ {
+ if (!LocalPathIsNotAtEdge)
+ return false;
+ char start = FullPath[StartIndex - 1];
+ char end = FullPath[EndIndex + 1];
+ return start == '(' && end == ')';
}
}
- return pathIndexList;
+
+ public override string ToString()
+ {
+ return LocalPath;
+ }
}
///
- /// Returns all strings that are pathes
+ /// Returns all strings that are pathes. Includes locations of each path in relation to the fullPath input.
///
- ///
- ///
+ ///
///
- private List GetPathes(string path, string[] operators)
+ private List GetPathes(string fullPath)
{
- var substrings = path.Split(new[] { "\"" }, StringSplitOptions.None);
+ var originalPath = fullPath;
+ var operators = ParsedPath.operators;
+
+ var substrings = originalPath.Split(new[] { "\"" }, StringSplitOptions.None);
+
+ //Keep track of quoted string lengths in order to preserve token indexes
+ var stringlengths = new Queue();
if (substrings.Length > 0)
{
@@ -262,30 +341,162 @@ private List GetPathes(string path, string[] operators)
if (i % 2 == 0)
pathWithoutStringsBuilder.Append(substrings[i]);
else
+ {
pathWithoutStringsBuilder.Append("\"\"");
+ stringlengths.Enqueue(substrings[i].Length);
+ }
}
- path = pathWithoutStringsBuilder.ToString();
+ fullPath = pathWithoutStringsBuilder.ToString();
}
- var matches = path.Split(operators, StringSplitOptions.RemoveEmptyEntries).Distinct();
+
+
+ var matches = SplitWithIndexes(fullPath, operators);
+
+ //If containing text has no operators, but surrounded by ( ), treat the enclosing parenthesis
+ //as part of the string
// detect all pathes
- var pathsList = new List();
+ var pathsList = new List();
- foreach (var match in matches)
+ //Scan through the full path to locate the indexes of the matches
+ int currentIndex = 0;
+ int totalStringLengths = 0;
+ for(int matchIdx = 0; matchIdx < matches.Count; matchIdx++)
{
+ var match = matches[matchIdx].Item2;
+ var matchFoundAtLoc = matches[matchIdx].Item1;
+ if (matchFoundAtLoc < currentIndex) //Already parsed recursively, skip
+ continue;
+
+ if (match == "\"\"")
+ {
+ //Keep track of the current number of characters removed up to this point
+ //So that an offset can be calculated
+ totalStringLengths += stringlengths.Dequeue();
+ }
+
+ currentIndex = fullPath.IndexOf(match, currentIndex);
+ var parsed = new ParsedPath()
+ {
+ FullPath = originalPath,
+ LocalPath = match,
+ StartIndex = currentIndex + totalStringLengths
+ };
+ currentIndex += match.Length;
+
+ //Since (Canvas.Left) is a valid path but Canvas.Left is not, we need to account for this.
+ if (parsed.SurroundedByParenthesis)
+ {
+ //Expand local path by one character in each direction, to include the surrounding parenthesis
+ parsed.LocalPath = parsed.FullPath.Substring(parsed.StartIndex - 1, parsed.LocalPath.Length + 2);
+ parsed.StartIndex--;
+ }
+
if (!isDouble(match) && !match.Contains("\""))
{
// math detection
- if (!Regex.IsMatch(match, @"Math.\w+\(\w+\)") && !Regex.IsMatch(match, @"Math.\w+"))
- if (match != "null")
- pathsList.Add(match);
+ var mathmatches1 = Regex.Matches(match, @"Math.\w+\(\w+\)").Cast();
+ var mathmatches2 = Regex.Matches(match, @"Math.\w+").Cast();
+ bool isMath = mathmatches1.Count() > 0 || mathmatches2.Count() > 0;
+
+ if (isMath)
+ {
+ //Get what is inside the math function, parse it, and unbox
+ int mathIdx = (mathmatches1.FirstOrDefault()?.Index ?? 0) + (mathmatches2.FirstOrDefault()?.Index ?? 0);
+ int beginIdxInner = parsed.FullPath.IndexOf("(", parsed.StartIndex + mathIdx) + 1;
+
+ //Traverse string until we get to the closing parenthesis
+ //An exception will be thrown if there is no closing parenthesis
+ int endIdxInner = beginIdxInner;
+ int depth = 1;
+ bool found = false;
+ for (int i = beginIdxInner; i < parsed.FullPath.Length; i++, endIdxInner++)
+ {
+ if (parsed.FullPath[i] == '(')
+ depth++;
+ if (parsed.FullPath[i] == ')')
+ {
+ depth--;
+ if (depth == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ throw new InvalidOperationException($"Unclosed parenthesis: {parsed.FullPath}");
+
+ string innerArg = parsed.FullPath.Substring(beginIdxInner, endIdxInner - beginIdxInner);
+ var innerRecursiveParsedPaths = GetPathes(innerArg);
+
+ //merge paths
+ foreach (var innerpath in innerRecursiveParsedPaths)
+ {
+ innerpath.FullPath = parsed.FullPath;
+ innerpath.StartIndex += beginIdxInner; //innerpath index is just an offset until this point. Convert to absolute index
+ pathsList.Add(innerpath);
+ }
+
+ //advance currentindex past the point at which we procesed the fullpath, as we do not want to parse the same region twice!
+ // currentIndex +=
+ }
+ else
+ pathsList.Add(parsed);
}
}
return pathsList;
}
+ //Splits a string using splitby as a delimiter, returns each substring along with the original index at which the substring resides
+ private List> SplitWithIndexes(string input, string[] splitby)
+ {
+ //Remove empty strings
+ splitby = splitby.Where(s => s != null && s.Length > 0).ToArray();
+
+ var list = new List>();
+
+ int idx = 0;
+ while (idx < input.Length)
+ {
+ //Get the first location that any of the splitby args are located in input, starting from idx
+ var next = splitby.Select(s =>
+ {
+ int loc = input.IndexOf(s, idx);
+ if (loc < 0)
+ loc = int.MaxValue;
+ return new { op = s, loc = loc };
+ });
+ var minidx = next.Min(o => o.loc);
+
+ if (minidx == int.MaxValue) //nothing found, finish up
+ {
+ var remainingText = input.Substring(idx);
+ if (remainingText.Length > 0)
+ list.Add(new Tuple(idx, remainingText));
+ break;
+ }
+
+ //Get the next delimiter that the string is split by
+ var nextop = next.Where(x => x.loc == minidx).First();
+
+ //Get the end index of the current substring that we are including in the results
+ int endIdxCurrent = nextop.loc - 1;
+
+ //Add the item to the results
+ string nextSubstring = input.Substring(idx, endIdxCurrent - idx + 1);
+ if (nextSubstring.Length > 0)
+ list.Add(new Tuple(idx, nextSubstring));
+
+ //Get the next index to continue string traversal
+ idx = nextop.loc + nextop.op.Length;
+ }
+
+ return list;
+ }
+
///
/// Return true, is string can be converted to Double type, and false otherwise
///