/*========================================================================================================================== | MAP QUERY RESULT \-------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Private helper function that maps a successfully validated <see cref="Topic"/> to a <see /// cref="QueryResultTopicViewModel"/>. /// </summary> private void MapQueryResult( Collection<QueryResultTopicViewModel> topicList, Topic topic, TopicQueryOptions options, ref int remainingResults, ReadOnlyTopicCollection related ) { /*------------------------------------------------------------------------------------------------------------------------ | Loop through children \-----------------------------------------------------------------------------------------------------------------------*/ var isValid = IsValidTopic(topic, options, remainingResults); /*------------------------------------------------------------------------------------------------------------------------ | Map topic \-----------------------------------------------------------------------------------------------------------------------*/ if (isValid) { //Decrement counter remainingResults--; //Map topic var mappedTopic = new QueryResultTopicViewModel( topic.Id, topic.Key, options.UseKeyAsText ? topic.Key : topic.Title, topic.GetUniqueKey(), topic.GetWebPath(), options.EnableCheckboxes ? (!options.MarkRelated || related.Contains(topic)) : new bool?(), !topic.Attributes.GetBoolean("DisableDelete") && !topic.Attributes.GetBoolean("IsProtected"), options.ExpandRelated && related.Any(r => r.GetUniqueKey().StartsWith(topic.GetUniqueKey(), StringComparison.Ordinal)) ); //Add topic to topic list topicList.Add(mappedTopic); //Handle recursion, if appropriate topicList = options.FlattenStructure ? topicList : mappedTopic.Children; } /*------------------------------------------------------------------------------------------------------------------------ | Loop through children (asynchronously) \-----------------------------------------------------------------------------------------------------------------------*/ if (isValid && options.IsRecursive || options.FlattenStructure ) { foreach (var childTopic in topic.Children) { MapQueryResult( topicList, childTopic, options, ref remainingResults, related ); } } }
/*========================================================================================================================== | IS VALID TOPIC \-------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Static method confirms whether a topic is valid based on the <see cref="TopicQueryOptions"/>. /// </summary> public static bool IsValidTopic(Topic topic, TopicQueryOptions options, int remainingResults) { /*------------------------------------------------------------------------------------------------------------------------ | Validate parameters \-----------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(topic, nameof(topic)); Contract.Requires(options, nameof(options)); /*------------------------------------------------------------------------------------------------------------------------ | Establish variables \-----------------------------------------------------------------------------------------------------------------------*/ var searchTerms = (options.Query ?? "").Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).ToList(); /*------------------------------------------------------------------------------------------------------------------------ | Validate basic properties \-----------------------------------------------------------------------------------------------------------------------*/ if (!options.ShowAll && !topic.IsVisible()) return false; if (!options.ShowNestedTopics && topic.ContentType is "List") return false; if (remainingResults is 0) return false; /*------------------------------------------------------------------------------------------------------------------------ | Validate filtered attribute \-----------------------------------------------------------------------------------------------------------------------*/ if (!String.IsNullOrEmpty(options.AttributeName)) { var attributeValue = topic.Attributes.GetValue(options.AttributeName, ""); if (options.AttributeName is "ContentType") { attributeValue = topic.ContentType; } if (options.UsePartialMatch && !String.IsNullOrEmpty(options.AttributeValue)) { if (attributeValue.IndexOf(options.AttributeValue, StringComparison.Ordinal) is -1) { return false; } } if (!attributeValue.Equals(options.AttributeValue, StringComparison.Ordinal)) { return false; } } /*------------------------------------------------------------------------------------------------------------------------ | Validate search results \-----------------------------------------------------------------------------------------------------------------------*/ if (searchTerms.Count > 0) { if (!searchTerms.All( searchTerm => topic.Attributes.Any( a => a.Value?.IndexOf(searchTerm, 0, StringComparison.OrdinalIgnoreCase) >= 0 ) || topic.Key.IndexOf(searchTerm, 0, StringComparison.OrdinalIgnoreCase) >= 0 )) { return false; } } return true; }
/*========================================================================================================================== | QUERY \-------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Generates and returns a list of <see cref="QueryResultTopicViewModel"/> objects based on a root <see cref="Topic"/> as /// well as a set of options as specified in a <see cref="TopicQueryOptions"/> object. /// </summary> public Collection<QueryResultTopicViewModel> Query( Topic rootTopic, TopicQueryOptions options, ReadOnlyTopicCollection? related = null ) { /*------------------------------------------------------------------------------------------------------------------------ | Validate parameters \-----------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(rootTopic, nameof(rootTopic)); Contract.Requires(options, nameof(options)); /*------------------------------------------------------------------------------------------------------------------------ | Establish containers for mapped objects, tasks \-----------------------------------------------------------------------------------------------------------------------*/ var topicViewModels = new Collection<QueryResultTopicViewModel>(); /*------------------------------------------------------------------------------------------------------------------------ | Establish counter \-----------------------------------------------------------------------------------------------------------------------*/ var remainingResults = options.ResultLimit; /*------------------------------------------------------------------------------------------------------------------------ | Bootstrap mapping process \-----------------------------------------------------------------------------------------------------------------------*/ if (options.ShowRoot) { MapQueryResult(topicViewModels, rootTopic, options, ref remainingResults, related?? new()); } else { foreach (var topic in rootTopic.Children) { MapQueryResult(topicViewModels, topic, options, ref remainingResults, related?? new()); } } /*------------------------------------------------------------------------------------------------------------------------ | Return results \-----------------------------------------------------------------------------------------------------------------------*/ return topicViewModels; }
/*========================================================================================================================== | METHOD: GET TOPICS >--------------------------------------------------------------------------------------------------------------------------- | Retrieves a collection of topics with optional control call filter properties Scope, AttributeName and AttributeValue. \-------------------------------------------------------------------------------------------------------------------------*/ /// <summary> /// Retrieves a list of <see cref="QueryResultTopicViewModel"/>s that represent topics under the <paramref name="topic"/> /// which have an <see cref="AttributeRecord"/> with <paramref name="attributeKey"/> and <paramref name="attributeValue" /// />. /// </summary> /// <param name="topic"></param> /// <param name="attributeKey"></param> /// <param name="attributeValue"></param> /// <returns></returns> private static Collection<QueryResultTopicViewModel> GetTopics( Topic? topic = null, string? attributeKey = null, string? attributeValue = null ) { /*------------------------------------------------------------------------------------------------------------------------ | Swallow missing topic \-----------------------------------------------------------------------------------------------------------------------*/ if (topic is null) { return new(); } /*------------------------------------------------------------------------------------------------------------------------ | Establish query options \-----------------------------------------------------------------------------------------------------------------------*/ var options = new TopicQueryOptions() { AttributeName = attributeKey, AttributeValue = attributeValue, FlattenStructure = false, IsRecursive = false, ResultLimit = 250, ShowRoot = false }; /*------------------------------------------------------------------------------------------------------------------------ | Get query results \-----------------------------------------------------------------------------------------------------------------------*/ var topicQueryService = new TopicQueryService(); var topicQueryResults = topicQueryService.Query(topic, options); /*------------------------------------------------------------------------------------------------------------------------ | Return Topics list \-----------------------------------------------------------------------------------------------------------------------*/ return topicQueryResults; }