/// <summary>
        /// Searches the specified text and determines if it contains any occurrence of any keyword.
        /// </summary>
        /// <param name="text">The text to search.</param>
        /// <param name="startIndex">The search starting position.</param>
        /// <returns>
        ///   <c>true</c> if the text searched contains any occurrence of any keyword; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsAny(string text, int startIndex = 0)
        {
            InnerNode ptr = _root;

            while (startIndex < text.Length)
            {
                InnerNode trans = null;
                while (trans == null)
                {
                    trans = ptr.GetTransition(text[startIndex]);
                    if (ptr == _root)
                    {
                        break;
                    }
                    if (trans == null)
                    {
                        ptr = ptr._failure;
                    }
                }
                if (trans != null)
                {
                    ptr = trans;
                }

                if (ptr._results.Count > 0)
                {
                    return(true);
                }
                ++startIndex;
            }
            return(false);
        }
        /// <summary>
        /// Searches the specified text and returns all occurrences of any keyword.
        /// </summary>
        /// <param name="text">The text to search.</param>
        /// <param name="startIndex">The search starting position.</param>
        /// <returns>
        /// An array of <see cref="StringSearchResult" /> objects that store the search result; null if the search fails.
        /// </returns>
        public StringSearchResult[] FindAll(string text, int startIndex = 0)
        {
            var       rlt = new List <StringSearchResult>();
            InnerNode ptr = _root;

            while (startIndex < text.Length)
            {
                InnerNode trans = null;
                while (trans == null)
                {
                    trans = ptr.GetTransition(text[startIndex]);
                    if (ptr == _root)
                    {
                        break;
                    }
                    if (trans == null)
                    {
                        ptr = ptr._failure;
                    }
                }
                if (trans != null)
                {
                    ptr = trans;
                }

                foreach (var found in ptr._results)
                {
                    rlt.Add(new StringSearchResult()
                    {
                        Position = startIndex - found.Key.Length + 1,
                        Value    = found.Key,
                        HitIndex = found.Value
                    });
                }
                ++startIndex;
            }

            if (rlt.Count == 0)
            {
                return(null);
            }
            return(rlt.ToArray());
        }
        void _buildTree(string[] keywords)
        {
            //build keyword tree and transition function
            _root = new InnerNode(null, ' ');
            var keywordCount = keywords.Length;

            for (int i = 0; i < keywordCount; ++i)
            {
                var p = keywords[i];

                // add pattern to tree
                InnerNode nd = _root;
                foreach (var c in p)
                {
                    InnerNode ndNew = null;
                    foreach (InnerNode trans in nd._transitionsArr)
                    {
                        if (trans._char == c)
                        {
                            ndNew = trans; break;
                        }
                    }

                    if (ndNew == null)
                    {
                        ndNew = new InnerNode(nd, c);
                        nd.AddTransition(ndNew);
                    }
                    nd = ndNew;
                }
                nd.AddResult(p, i);
            }

            // Find failure functions
            var nodes = new List <InnerNode>();

            // level 1 nodes - fail to root node
            foreach (InnerNode nd in _root._transitionsArr)
            {
                nd._failure = _root;
                foreach (InnerNode trans in nd._transitionsArr)
                {
                    nodes.Add(trans);
                }
            }
            // other nodes - using BFS
            while (nodes.Count != 0)
            {
                var newNodes = new List <InnerNode>();
                foreach (InnerNode nd in nodes)
                {
                    InnerNode r = nd._parent._failure;
                    char      c = nd._char;

                    while (r != null && !r.ContainsTransition(c))
                    {
                        r = r._failure;
                    }
                    if (r == null)
                    {
                        nd._failure = _root;
                    }
                    else
                    {
                        nd._failure = r.GetTransition(c);
                        foreach (var result in nd._failure._results)
                        {
                            nd.AddResult(result.Key, result.Value);
                        }
                    }

                    // add child nodes to BFS list
                    foreach (InnerNode child in nd._transitionsArr)
                    {
                        newNodes.Add(child);
                    }
                }
                nodes = newNodes;
            }
            _root._failure = _root;
        }