OpenMW
apps/openmw/mwgui/keywordsearch.hpp
Go to the documentation of this file.
00001 #ifndef MWGUI_KEYWORDSEARCH_H
00002 #define MWGUI_KEYWORDSEARCH_H
00003 
00004 #include <map>
00005 #include <locale>
00006 #include <stdexcept>
00007 #include <vector>
00008 #include <algorithm>    // std::reverse
00009 
00010 #include <components/misc/stringops.hpp>
00011 
00012 namespace MWGui
00013 {
00014 
00015 template <typename string_t, typename value_t>
00016 class KeywordSearch
00017 {
00018 public:
00019 
00020     typedef typename string_t::const_iterator Point;
00021 
00022     struct Match
00023     {
00024         Point mBeg;
00025         Point mEnd;
00026         value_t mValue;
00027     };
00028 
00029     void seed (string_t keyword, value_t value)
00030     {
00031         seed_impl  (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot);
00032     }
00033 
00034     void clear ()
00035     {
00036         mRoot.mChildren.clear ();
00037         mRoot.mKeyword.clear ();
00038     }
00039 
00040     bool containsKeyword (string_t keyword, value_t& value)
00041     {
00042         typename Entry::childen_t::iterator current;
00043         typename Entry::childen_t::iterator next;
00044 
00045         current = mRoot.mChildren.find (std::tolower (*keyword.begin(), mLocale));
00046         if (current == mRoot.mChildren.end())
00047             return false;
00048         else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword))
00049         {
00050             value = current->second.mValue;
00051             return true;
00052         }
00053 
00054         for (Point i = ++keyword.begin(); i != keyword.end(); ++i)
00055         {
00056             next = current->second.mChildren.find(std::tolower (*i, mLocale));
00057             if (next == current->second.mChildren.end())
00058                 return false;
00059             if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword))
00060             {
00061                 value = next->second.mValue;
00062                 return true;
00063             }
00064             current = next;
00065         }
00066         return false;
00067     }
00068 
00069     bool search (Point beg, Point end, Match & match)
00070     {
00071         for (Point i = beg; i != end; ++i)
00072         {
00073             // check first character
00074             typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale));
00075 
00076             // no match, on to next character
00077             if (candidate == mRoot.mChildren.end ())
00078                 continue;
00079 
00080             // see how far the match goes
00081             Point j = i;
00082 
00083             // some keywords might be longer variations of other keywords, so we definitely need a list of candidates
00084             // the first element in the pair is length of the match, i.e. depth from the first character on
00085             std::vector< typename std::pair<int, typename Entry::childen_t::iterator> > candidates;
00086 
00087             while ((j + 1) != end)
00088             {
00089                 typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale));
00090 
00091                 if (next == candidate->second.mChildren.end ())
00092                 {
00093                     if (candidate->second.mKeyword.size() > 0)
00094                         candidates.push_back(std::make_pair((j-i), candidate));
00095                     break;
00096                 }
00097 
00098                 candidate = next;
00099 
00100                 if (candidate->second.mKeyword.size() > 0)
00101                     candidates.push_back(std::make_pair((j-i), candidate));
00102             }
00103 
00104             if (candidates.empty())
00105                 continue; // didn't match enough to disambiguate, on to next character
00106 
00107             // shorter candidates will be added to the vector first. however, we want to check against longer candidates first
00108             std::reverse(candidates.begin(), candidates.end());
00109 
00110             for (typename std::vector< std::pair<int, typename Entry::childen_t::iterator> >::iterator it = candidates.begin();
00111                  it != candidates.end(); ++it)
00112             {
00113                 candidate = it->second;
00114                 // try to match the rest of the keyword
00115                 Point k = i + it->first;
00116                 typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (k - i);
00117 
00118 
00119                 while (k != end && t != candidate->second.mKeyword.end ())
00120                 {
00121                     if (std::tolower (*k, mLocale) != std::tolower (*t, mLocale))
00122                         break;
00123 
00124                     ++k, ++t;
00125                 }
00126 
00127                 // didn't match full keyword, try the next candidate
00128                 if (t != candidate->second.mKeyword.end ())
00129                     continue;
00130 
00131                 // we did it, report the good news
00132                 match.mValue = candidate->second.mValue;
00133                 match.mBeg = i;
00134                 match.mEnd = k;
00135 
00136                 return true;
00137             }
00138         }
00139 
00140         // no match in range, report the bad news
00141         return false;
00142     }
00143 
00144 private:
00145 
00146     struct Entry
00147     {
00148         typedef std::map <wchar_t, Entry> childen_t;
00149 
00150         string_t mKeyword;
00151         value_t mValue;
00152         childen_t mChildren;
00153     };
00154 
00155     void seed_impl (string_t keyword, value_t value, size_t depth, Entry  & entry)
00156     {
00157         int ch = tolower (keyword.at (depth), mLocale);
00158 
00159         typename Entry::childen_t::iterator j = entry.mChildren.find (ch);
00160 
00161         if (j == entry.mChildren.end ())
00162         {
00163             entry.mChildren [ch].mValue = /*std::move*/ (value);
00164             entry.mChildren [ch].mKeyword = /*std::move*/ (keyword);
00165         }
00166         else
00167         {
00168             if (j->second.mKeyword.size () > 0)
00169             {
00170                 if (keyword == j->second.mKeyword)
00171                     throw std::runtime_error ("duplicate keyword inserted");
00172 
00173                 value_t pushValue = /*std::move*/ (j->second.mValue);
00174                 string_t pushKeyword = /*std::move*/ (j->second.mKeyword);
00175 
00176                 if (depth >= pushKeyword.size ())
00177                     throw std::runtime_error ("unexpected");
00178 
00179                 if (depth+1 < pushKeyword.size())
00180                 {
00181                     seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second);
00182                     j->second.mKeyword.clear ();
00183                 }
00184             }
00185             if (depth+1 == keyword.size())
00186                 j->second.mKeyword = value;
00187             else // depth+1 < keyword.size()
00188                 seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second);
00189         }
00190 
00191     }
00192 
00193     Entry mRoot;
00194     std::locale mLocale;
00195 };
00196 
00197 }
00198 
00199 #endif