/// <summary> /// Navigates this node (and recusively into child nodes) for a match to the path passed as an argument /// whilst processing the referenced request. This matching algorithm is a highly restricted version of /// depth-first search, also known as backtracking (hence the name). /// </summary> /// <param name="position">The position in the search path currently being evaluated</param> /// <param name="matchState">The type of wildcard match to store in the Wildcards dictionary</param> /// <param name="wildcardMatches">The contents of the user input absorbed by the AIML wildcards "_" and "*"</param> /// <param name="node">The node we're currently "visiting"</param> /// <returns>The template to process to generate the output</returns> private bool BackTrack(int position, string matchState, StringBuilder wildcardMatches, Node node) { // Check for timeout (and check of last resort should there be circular AIML references) if (this.TimeoutAfter < DateTime.Now) { this.hasTimedOut = true; throw new LearnException(String.Format(CultureInfo.CurrentCulture, rm.GetString("QueryTimedOut"), String.Join(" ", this.path))); } // Check if the query has found a leaf node if (node.Children.Count == 0) { // if there are still words in the path then add them to the wildcard match // (should they be required) if (position < this.path.Length) { for (int i = position; i < this.path.Length; i++) { wildcardMatches.Append(this.path[i] + " "); } } if (node.Template != null) { this.Node = node; return true; } else { return false; } } else if (position == this.path.Length) { // as there is no more path to process then the result is the current node // (whose template *might* be null - but that needs to be dealt with in the // template handling code - this method simply wants to find nodes). if (this.Node == null) { return false; } else { this.Node = node; return true; } } else { // check for update to the matchstate Match m = Regex.Match(this.path[position], "(<.[^(><.)]+>)"); if (m.Success) { matchState = m.Value.Substring(1, m.Value.Length - 2) + "star"; } // search in the children of the current node if(node.Children.ContainsKey("_")) { // first option is to see if the node has a child denoted by the "_" wildcard. // "_" comes first in precedence in the AIML alphabet StringBuilder newWildcardMatch = new StringBuilder(); newWildcardMatch.Append(this.path[position]+" "); if (this.BackTrack((position + 1),matchState, newWildcardMatch, node.Children["_"])) { this.StoreWildcard(matchState, newWildcardMatch.ToString().Trim()); return true; } } string key = this.path[position].ToUpper(CultureInfo.InvariantCulture); if (node.Children.ContainsKey(key)) { // second option - the node may have contained a "_" child, but led to no match // or it didn't contain a "_" child at all. So check for a matching node corresponding // to the word found in the current position in the path. StringBuilder newWildcardMatch = new StringBuilder(); if (this.BackTrack((position + 1), matchState, newWildcardMatch, node.Children[key])) { if (newWildcardMatch.Length > 0) { this.StoreWildcard(matchState, newWildcardMatch.ToString().Trim()); } return true; } } if (node.Children.ContainsKey("*")) { // third option - check to see if the node has a child representing the "*" wildcard. // "*" comes last in precedence in the AIML alphabet. StringBuilder newWildcardMatch = new StringBuilder(); newWildcardMatch.Append(this.path[position] + " "); if (this.BackTrack((position + 1), matchState, newWildcardMatch, node.Children["*"])) { this.StoreWildcard(matchState, newWildcardMatch.ToString().Trim()); return true; } } if ((node.Word == "_") || (node.Word == "*")) { // so far the query has failed to find a match at all: the node's children contain neither // a "_", the word at this.Path[position], or "*" as a means of denoting a child node. However, // if this node itself represents a wildcard then the search continues to be // valid if we proceed with the tail. wildcardMatches.Append(this.path[position] + " "); return this.BackTrack((position + 1), matchState, wildcardMatches, node); } // If we get here then we're at a dead end so return false. Hopefully, if the // AIML files have been set up to include a "* <that> * <topic> *" catch-all this // state won't be reached. Remember to empty the surplus to requirements wildcard matches // so the query doesn't pollute the wildcard matches back up the search tree. if (wildcardMatches.Length > 0) { wildcardMatches.Remove(0, wildcardMatches.Length); } return false; } }
/// <summary> /// Adds extra branches and templates to the graphmaster /// </summary> /// <param name="path">the path from the current parent that identifies the template to learn</param> /// <param name="position">the position in the path currently being evaluated</param> /// <param name="template">the template to find at the end of the path</param> /// <param name="source">the URI that was the source of the category that points to the template</param> private Node AddNode(string[] path, int position, string template, string source) { // check we're not at the leaf node if (position == path.Length) { this.Template = template; this.Source = source; return this; } else { // requires further child nodes for this category to be fully mapped into the graphmaster string firstWord = path[position].ToUpper(CultureInfo.InvariantCulture); position++; // pass the handling of this sentence down the branch to a child node if (this.Children.ContainsKey(firstWord)) { Node childNode = this.Children[firstWord]; return childNode.AddNode(path, position, template, source); } else { // new child node needed Node childNode = new Node(firstWord); this.Children.Add(firstWord, childNode); return childNode.AddNode(path, position, template, source); } } }
/// <summary> /// Starts the query from the passed node for the query path with the started on point designated (should this /// query be the result of a previous query - we need to have the stopwatch start from the beginning of the /// original query) /// </summary> /// <param name="node">The node from which the query is to start</param> /// <param name="StartedOn">When the query is started from</param> public bool Evaluate(Node node, DateTime startedOn) { this.startedOn = startedOn; return this.BackTrack(0, "star", new StringBuilder(), node); }