/// <summary> /// Parses the supplied expression into a hierarchical list-based representation for easier consumption. /// </summary> /// <param name="fieldsExpression">The fields expression to be parsed.</param> /// <returns>A hierarchical list-based representation of the fields expression.</returns> public IReadOnlyList <SelectedResourceMember> ParseFields(string fieldsExpression) { if (string.IsNullOrEmpty(fieldsExpression)) { throw new ArgumentException("Field selection expression was not supplied."); } fieldsExpression = fieldsExpression.Trim(); int anchor = 0; int pos = 0; var state = ParseState.SeekingMember; var allMembers = new List <SelectedResourceMember>(); var stack = new Stack <List <SelectedResourceMember> >(); var members = allMembers; while (pos < fieldsExpression.Length || state == ParseState.ChildrenParsed) { switch (state) { case ParseState.AllMembersIncluded: if (fieldsExpression[pos] != ')') { throw new ArgumentException("Fields expression uses '*' without enclosing parenthesis."); } state = ParseState.ChildrenParsed; break; case ParseState.SeekingMember: // Skip over leading spaces if (fieldsExpression[pos] == ' ') { anchor = ++pos; } // Check for the "all" symbol else if (fieldsExpression[pos] == '*') { state = ParseState.AllMembersIncluded; pos++; } // Check for premature child fields closing symbol with no children listed else if (fieldsExpression[pos] == ')' && !members.Any()) { throw new ArgumentException( "Fields sub-expression does not contain any field names."); } else { state = ParseState.ParsingMember; } break; case ParseState.ParsingMember: if (fieldsExpression[pos] == ',') { state = ParseState.MemberParsed; } else if (fieldsExpression[pos] == '(') { state = ParseState.ParsingChildren; } else if (fieldsExpression[pos] == ')') { state = ParseState.ChildrenParsed; } else if (fieldsExpression[pos] == ' ') { state = ParseState.MemberParsed; } else if (!(char.IsLetterOrDigit(fieldsExpression[pos]) || fieldsExpression[pos] == '_')) { throw new ArgumentException( string.Format("Unexpected character '{0}' in member name in field selection expression.", fieldsExpression[pos])); } else { pos++; } break; case ParseState.MemberParsed: string parsedMemberName = fieldsExpression.Substring(anchor, pos - anchor); if (!string.IsNullOrEmpty(parsedMemberName)) { members.Add(new SelectedResourceMember(parsedMemberName)); } state = ParseState.SeekingMember; anchor = ++pos; break; case ParseState.ParsingChildren: var currentMember = new SelectedResourceMember(fieldsExpression.Substring(anchor, pos - anchor)); members.Add(currentMember); state = ParseState.SeekingMember; anchor = ++pos; stack.Push(members); members = currentMember.Children; break; case ParseState.ChildrenParsed: string memberName = fieldsExpression.Substring(anchor, pos - anchor); if (!string.IsNullOrEmpty(memberName) && memberName != ")") { members.Add(new SelectedResourceMember(memberName)); } anchor = ++pos; if (!stack.Any()) { throw new ArgumentException("Fields expression contains too many closing parenthesis."); } members = stack.Pop(); state = ParseState.SeekingMember; break; } } string finalMemberName = fieldsExpression.Substring(anchor, pos - anchor); if (!string.IsNullOrEmpty(finalMemberName)) { members.Add(new SelectedResourceMember(finalMemberName)); } if (stack.Count != 0) { throw new ArgumentException("Fields expression contains unclosed parenthesis."); } return(allMembers); }
public IList <IDictionary> ProcessResults( CompositeQuery query, Hashtable parentRow, string[] parentKeys, IReadOnlyList <SelectedResourceMember> fieldSelections, NullValueHandling nullValueHandling, IDictionary <string, string> recursiveChildKeyMap = null) { var results = new List <IDictionary>(); var currentEnumerator = query.GetEnumerator(parentRow, parentKeys, recursiveChildKeyMap); do { // Nothing to enumerate? if (currentEnumerator == null || currentEnumerator.IsComplete) { return(results); } // Get current row var currentRow = (Hashtable)currentEnumerator.Current; if (currentRow == null) { break; } // If the child is outside the context of the parent, quit processing if (parentRow != null && !IsChildRow(parentKeys, parentRow, recursiveChildKeyMap, currentRow)) { break; } // Convert the row to serializable form var resultItem = GetItem(currentRow, query.DataFields, query.OrderedFieldNames, fieldSelections, nullValueHandling); // Process the children foreach (var childQuery in query.ChildQueries) { SelectedResourceMember selectedMember = null; string childCollectionName = childQuery.DisplayName; // Check to see if this collection should be included if (fieldSelections != null && fieldSelections.Count > 0) { selectedMember = fieldSelections.FirstOrDefault(x => x.Equals(childCollectionName) || x.Equals("*")); if (selectedMember == null) { continue; } } resultItem[childCollectionName] = ProcessResults( childQuery, currentRow, query.KeyFields, selectedMember == null ? null : selectedMember.Children, nullValueHandling) .ApplyCardinality(childQuery.IsSingleItemResult); } // Is the current query recursive? if (query.IsRecursive) { // Need to add a child "self" collection with recursion resultItem[query.DisplayName] = ProcessResults( query, currentRow, query.KeyFields, fieldSelections, nullValueHandling, query.RecursiveChildKeyMap); // Strip out hierarchical support fields from the response resultItem.Keys.OfType <string>() .Where(k => k.StartsWith(CompositeDefinitionHelper.HierarchyMarker)) .ToList() .ForEach(k => resultItem.Remove(k)); // Add the row - we're done. results.Add(resultItem); } else { // Just add the row - we're done. results.Add(resultItem); } }while (currentEnumerator.MoveNext()); return(results); }