OpenMW
|
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