OpenTREP Logo  0.09.01
C++ Open Travel Request Parsing Library
Loading...
Searching...
No Matches
RequestInterpreter.cpp
Go to the documentation of this file.
1// //////////////////////////////////////////////////////////////////////
2// Import section
3// //////////////////////////////////////////////////////////////////////
4// STL
5#include <cassert>
6#include <sstream>
7#include <string>
8#include <vector>
9#include <exception>
10// Boost
11#include <boost/filesystem.hpp>
12#include <boost/regex.hpp>
13// SOCI
14#include <soci/soci.h>
15// OpenTrep
16#include <opentrep/DBType.hpp>
35
36namespace OPENTREP {
37
49 // //////////////////////////////////////////////////////////////////////
50 void addUnmatchedWord (const TravelQuery_T& iQueryString,
51 WordList_T& ioWordList, WordSet_T& ioWordSet) {
52 // Token-ise the given string
53 WordList_T lQueryStringWordList;
55 lQueryStringWordList);
56 if (lQueryStringWordList.size() == 1) {
57 // Add the unmatched/unknown word, only when that latter has not
58 // already been stored, and when it is not black-listed.
59 const bool shouldBeKept = Filter::shouldKeep ("", iQueryString);
60 //const bool shouldBeKept = true;
61
62 WordSet_T::const_iterator itWord = ioWordSet.find (iQueryString);
63 if (shouldBeKept == true && itWord == ioWordSet.end()) {
64 ioWordSet.insert (iQueryString);
65 ioWordList.push_back (iQueryString);
66 }
67 }
68 }
69
70 // //////////////////////////////////////////////////////////////////////
71 void createPlaces (const ResultCombination& iResultCombination,
72 PlaceHolder& ioPlaceHolder) {
73
74 // Retrieve the best matching ResultHolder object.
75 const ResultHolder& lResultHolder =
76 iResultCombination.getBestMatchingResultHolder();
77
78 // Browse the list of result objects
79 const ResultList_T& lResultList = lResultHolder.getResultList();
80 for (ResultList_T::const_iterator itResult = lResultList.begin();
81 itResult != lResultList.end(); ++itResult) {
82 // Retrieve the result object
83 const Result* lResult_ptr = *itResult;
84 assert (lResult_ptr != NULL);
85
90 const bool hasFullTextMatched = lResult_ptr->hasFullTextMatched();
91 if (hasFullTextMatched == false) {
92 continue;
93 }
94 assert (hasFullTextMatched == true);
95
96 // Retrieve the Xapian document data (string)
97 const std::string& lDocDataStr = lResult_ptr->getBestDocData();
98 const RawDataString_T& lDocData = RawDataString_T (lDocDataStr);
99
100 // Parse the POR details and create the corresponding Location structure
101 const Location& lLocation = Result::retrieveLocation (lDocData);
102
103 // Instanciate an empty place object, which will be filled from the
104 // rows retrieved from the database.
105 Place& lPlace = FacPlace::instance().create (lLocation);
106
107 // Insert the Place object within the PlaceHolder object
108 FacPlaceHolder::initLinkWithPlace (ioPlaceHolder, lPlace);
109
110 // Fill the place with the remaining of the Result details.
111 lResult_ptr->fillPlace (lPlace);
112
113 // DEBUG
114 OPENTREP_LOG_DEBUG ("Retrieved Document: " << lPlace.toString());
115 }
116 }
117
130 // //////////////////////////////////////////////////////////////////////
131 void searchString (const StringPartition& iStringPartition,
132 const Xapian::Database& iDatabase,
133 ResultCombination& ioResultCombination,
134 WordList_T& ioWordList,
135 const std::string& iNegativeQuery = "") {
136
137 // Catch any thrown Xapian::Error exceptions
138 try {
139
140 // Set of unknown words (just to eliminate the duplicates)
141 WordSet_T lWordSet;
142
143 // Browse the partitions
144 for (StringPartition::StringPartition_T::const_iterator itSet =
145 iStringPartition._partition.begin();
146 itSet != iStringPartition._partition.end(); ++itSet) {
147 const StringSet& lStringSet = *itSet;
148
149 // DEBUG
150 OPENTREP_LOG_DEBUG (" ==========");
151 OPENTREP_LOG_DEBUG (" String set: " << lStringSet);
152
153 // Create a ResultHolder object.
154 ResultHolder& lResultHolder =
155 FacResultHolder::instance().create (lStringSet.describe(), iDatabase);
156
157 // Add the ResultHolder object to the dedicated list.
159 lResultHolder);
160
161 // Browse through all the word combinations of the partition
162 for (StringSet::StringSet_T::const_iterator itString =
163 lStringSet._set.begin();
164 itString != lStringSet._set.end(); ++itString) {
165 //
166 const std::string lQueryString (*itString);
167
168 // DEBUG
169 OPENTREP_LOG_DEBUG (" --------");
170 OPENTREP_LOG_DEBUG (" Query string: '" << lQueryString << "'");
171
172 // Create an empty Result object
173 Result& lResult = FacResult::instance().create (lQueryString,
174 iDatabase);
175
176 // Add the Result object to the dedicated list.
177 FacResultHolder::initLinkWithResult (lResultHolder, lResult);
178
179 // Perform the Xapian-based full-text match: the set of
180 // matching documents is filled.
181 const std::string& lMatchedString =
182 lResult.fullTextMatch (iDatabase, lQueryString, iNegativeQuery);
183
184 // When a single-word string is unmatched/unknown by/from Xapian,
185 // add it to the dedicated list (i.e., ioWordList).
186 if (lMatchedString.empty() == true) {
187 OPENTREP::addUnmatchedWord (lQueryString, ioWordList, lWordSet);
188 }
189 }
190
191 // DEBUG
192 OPENTREP_LOG_DEBUG (std::endl
193 << "========================================="
194 << std::endl << "Result holder: "
195 << lResultHolder.toString() << std::endl
196 << "========================================="
197 << std::endl << std::endl);
198 }
199
200 // DEBUG
201 OPENTREP_LOG_DEBUG ("*********************");
202
203 } catch (const Xapian::Error& error) {
204 // Error
205 OPENTREP_LOG_ERROR ("Exception: " << error.get_msg());
206 throw XapianException (error.get_msg());
207 }
208 }
209
226 // //////////////////////////////////////////////////////////////////////
228
229 // Calculate the weights for the full-text matches
230 const bool doesBestMatchingResultHolderExist =
231 ioResultCombination.chooseBestMatchingResultHolder();
232
233 if (doesBestMatchingResultHolderExist == true) {
234 const ResultHolder& lBestMatchingResultHolder =
235 ioResultCombination.getBestMatchingResultHolder();
236
237 // DEBUG
238 const StringSet& lCorrectedStringSet =
239 ioResultCombination.getCorrectedStringSet();
240 OPENTREP_LOG_DEBUG ("The best matching string partition for '"
241 << ioResultCombination.describeShortKey() << "' is "
242 << lBestMatchingResultHolder.describeShortKey()
243 << ", and has got a weight of "
244 << ioResultCombination.getBestMatchingWeight()
245 << "%. The corrected string set is: "
246 << lCorrectedStringSet);
247
248 } else {
249 // DEBUG
250 OPENTREP_LOG_DEBUG ("There is no match for '"
251 << ioResultCombination.describeShortKey() << "'");
252 }
253 }
254
255 // //////////////////////////////////////////////////////////////////////
256 bool RequestInterpreter::areAllCodeOrGeoID (const TravelQuery_T& iQueryString,
257 WordList_T& ioWordList) {
258 bool areAllWordsCodes = true;
259
260 // Token-ise the given string
261 WordHolder::tokeniseStringIntoWordList (iQueryString, ioWordList);
262 for (WordList_T::const_iterator itWord = ioWordList.begin();
263 itWord != ioWordList.end(); ++itWord) {
264 const std::string& lWord = *itWord;
265
266 // IATA code: alpha{3}
267 const boost::regex lIATACodeExp ("^[[:alpha:]]{3}$");
268 const bool lMatchesWithIATACode = regex_match (lWord, lIATACodeExp);
269
270 // ICAO code: (alpha|digit){4}
271 const boost::regex lICAOCodeExp ("^([[:alpha:]]|[[:digit:]]){4}$");
272 const bool lMatchesWithICAOCode = regex_match (lWord, lICAOCodeExp);
273
274 // UN/LOCODE code: alpha{2}(alpha|digit){3}
275 const boost::regex
276 lUNLOCodeExp ("^[[:alpha:]]{2}([[:alpha:]]|[[:digit:]]){3}$");
277 const bool lMatchesWithUNLOCode = regex_match (lWord, lUNLOCodeExp);
278
279 // Geonames ID: digit{1,12}
280 const boost::regex lGeoIDCodeExp ("^[[:digit:]]{1,12}$");
281 const bool lMatchesWithGeoID = regex_match (lWord, lGeoIDCodeExp);
282
283 // If the word is neither a IATA/ICAO code or a Geonames ID,
284 // there is nothing more to be done at that stage. The query string
285 // will have to be fully analysed.
286 // Otherwise, we go on analysing the other words.
287 if (lMatchesWithIATACode == false && lMatchesWithICAOCode == false
288 && lMatchesWithUNLOCode == false && lMatchesWithGeoID == false) {
289 areAllWordsCodes = false;
290 break;
291 }
292 }
293
294 return areAllWordsCodes;
295 }
296
303 // //////////////////////////////////////////////////////////////////////
305 Location oLocation;
306 PageRank_T lMaxPageRank = 0.0;
307
308 for (LocationList_T::const_iterator itLocation = iLocationList.begin();
309 itLocation != iLocationList.end(); ++itLocation) {
310 const Location& lLocation = *itLocation;
311
312 // Get the PageRank value
313 const PageRank_T& lPageRank = lLocation.getPageRank();
314 if (lPageRank > lMaxPageRank) {
315 lMaxPageRank = lPageRank;
316 oLocation = lLocation;
317 }
318 }
319
320 return oLocation;
321 }
322
336 // //////////////////////////////////////////////////////////////////////
338 const SQLDBConnectionString_T& iSQLDBConnStr,
339 const WordList_T& iCodeList,
340 LocationList_T& ioLocationList,
341 WordList_T& ioWordList) {
342 NbOfMatches_T oNbOfMatches = 0;
343
344 // Connect to the SQL database/file
345 soci::session* lSociSession_ptr =
346 DBManager::initSQLDBSession (iSQLDBType, iSQLDBConnStr);
347 if (lSociSession_ptr == NULL) {
348 std::ostringstream oStr;
349 oStr << "The " << iSQLDBType.describe()
350 << " database is not accessible. Connection string: "
351 << iSQLDBConnStr << std::endl
352 << "Hint: launch the 'opentrep-dbmgr' program and "
353 << "see the 'tutorial' command.";
354 OPENTREP_LOG_ERROR (oStr.str());
356 }
357 assert (lSociSession_ptr != NULL);
358
359 // Browse the list of words/items
360 for (WordList_T::const_iterator itWord = iCodeList.begin();
361 itWord != iCodeList.end(); ++itWord) {
362 const std::string& lWord = *itWord;
363
364 // Check for IATA code: alpha{3}
365 const boost::regex lIATACodeExp ("^[[:alpha:]]{3}$");
366 const bool lMatchesWithIATACode = regex_match (lWord, lIATACodeExp);
367 if (lMatchesWithIATACode == true) {
368 // Perform the select statement on the underlying SQL database
369 const IATACode_T lIATACode (lWord);
370 const bool lUniqueEntry = true;
371 const NbOfDBEntries_T& lNbOfEntries =
372 DBManager::getPORByIATACode (*lSociSession_ptr, lIATACode,
373 ioLocationList, lUniqueEntry);
374 oNbOfMatches += lNbOfEntries;
375 continue;
376 }
377
378 // Check for ICAO code: (alpha|digit){4}
379 const boost::regex lICAOCodeExp ("^([[:alpha:]]|[[:digit:]]){4}$");
380 const bool lMatchesWithICAOCode = regex_match (lWord, lICAOCodeExp);
381 if (lMatchesWithICAOCode == true) {
382 // Perform the select statement on the underlying SQL database
383 const ICAOCode_T lICAOCode (lWord);
384 const NbOfDBEntries_T& lNbOfEntries =
385 DBManager::getPORByICAOCode (*lSociSession_ptr, lICAOCode,
386 ioLocationList);
387 oNbOfMatches += lNbOfEntries;
388 continue;
389 }
390
391 // Check for UN/LOCODE code: alpha{2}(alpha|digit){3}
392 const boost::regex
393 lUNLOCodeExp ("^[[:alpha:]]{2}([[:alpha:]]|[[:digit:]]){3}$");
394 const bool lMatchesWithUNLOCode = regex_match (lWord, lUNLOCodeExp);
395 if (lMatchesWithUNLOCode == true) {
396 // Perform the select statement on the underlying SQL database
397 const UNLOCode_T lUNLOCode (lWord);
398 const bool lUniqueEntry = true;
399 const NbOfDBEntries_T& lNbOfEntries =
400 DBManager::getPORByUNLOCode (*lSociSession_ptr, lUNLOCode,
401 ioLocationList, lUniqueEntry);
402 oNbOfMatches += lNbOfEntries;
403 continue;
404 }
405
406 // Check for Geonames ID: digit{1,12}
407 const boost::regex lGeoIDCodeExp ("^[[:digit:]]{1,12}$");
408 const bool lMatchesWithGeoID = regex_match (lWord, lGeoIDCodeExp);
409 if (lMatchesWithGeoID == true) {
410 try {
411 // Convert the character string into a number
412 const GeonamesID_T lGeonamesID =
413 boost::lexical_cast<GeonamesID_T> (lWord);
414
415 // Perform the select statement on the underlying SQL database
416 const NbOfDBEntries_T& lNbOfEntries =
417 DBManager::getPORByGeonameID (*lSociSession_ptr, lGeonamesID,
418 ioLocationList);
419 oNbOfMatches += lNbOfEntries;
420
421 } catch (boost::bad_lexical_cast& eCast) {
422 OPENTREP_LOG_ERROR ("The Geoname ID ('" << lWord
423 << "') cannot be understood.");
424 }
425 }
426 }
427
428 return oNbOfMatches;
429 }
430
431 // //////////////////////////////////////////////////////////////////////
432 NbOfMatches_T RequestInterpreter::
433 interpretTravelRequest (const TravelDBFilePath_T& iTravelDBFilePath,
434 const DBType& iSQLDBType,
435 const SQLDBConnectionString_T& iSQLDBConnStr,
436 const TravelQuery_T& iTravelQuery,
437 LocationList_T& ioLocationList,
438 WordList_T& ioWordList,
439 const OTransliterator& iTransliterator) {
440 NbOfMatches_T oNbOfMatches = 0;
441
442 // Sanity check
443 assert (iTravelQuery.empty() == false);
444
445 // Check whether the file-path to the Xapian database/index exists
446 // and is a directory.
447 boost::filesystem::path lTravelDBFilePath (iTravelDBFilePath.begin(),
448 iTravelDBFilePath.end());
449 if (!(boost::filesystem::exists (lTravelDBFilePath)
450 && boost::filesystem::is_directory (lTravelDBFilePath))) {
451 std::ostringstream oStr;
452 oStr << "The file-path to the Xapian database/index ('"
453 << iTravelDBFilePath << "') does not exist or is not a directory. ";
454 oStr << "That usually means that the OpenTREP indexer (opentrep-indexer) "
455 << "has not been launched yet, or that it has operated "
456 << "on a different Xapian database/index file-path.";
457 OPENTREP_LOG_ERROR (oStr.str());
458 throw FileNotFoundException (oStr.str());
459 }
460
461 // Open the Xapian database
462 Xapian::Database lXapianDatabase (iTravelDBFilePath);
463
464 // DEBUG
465 OPENTREP_LOG_DEBUG (std::endl
466 << "=========================================");
467
468
469 // Separate positive and negative words (starting with '-')
470 std::string lPositiveQuery, lNegativeQuery;
471 std::istringstream iss(iTravelQuery);
472 std::string word;
473 bool firstPos = true, firstNeg = true;
474 while (iss >> word) {
475 if (word.length() > 1 && word[0] == '-') {
476 if (!firstNeg) lNegativeQuery += " ";
477 lNegativeQuery += word;
478 firstNeg = false;
479 } else {
480 if (!firstPos) lPositiveQuery += " ";
481 lPositiveQuery += word;
482 firstPos = false;
483 }
484 }
485
486 if (lPositiveQuery.empty()) {
487 lPositiveQuery = iTravelQuery; // Fallback if all were negative
488 lNegativeQuery.clear();
489 }
490
491 // First, cut the positive query in slices and calculate all the partitions
492 // for each of those query slices
493 QuerySlices lQuerySlices (lXapianDatabase, lPositiveQuery, iTransliterator);
494
495 // DEBUG
496 OPENTREP_LOG_DEBUG ("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+");
497 OPENTREP_LOG_DEBUG ("Travel query: `" << iTravelQuery << "'");
498 const TravelQuery_T& lNormalisedQueryString = lQuerySlices.getQueryString();
499 if (!(iTravelQuery == lNormalisedQueryString)) {
500 OPENTREP_LOG_DEBUG ("Normalised travel query: `" << lNormalisedQueryString
501 << "'");
502 }
503 OPENTREP_LOG_DEBUG ("Query slices: `" << lQuerySlices << "'");
504
505 // Browse the travel query slices
506 const StringPartitionList_T& lStringPartitionList =
507 lQuerySlices.getStringPartitionList();
508 for (StringPartitionList_T::const_iterator itSlice =
509 lStringPartitionList.begin();
510 itSlice != lStringPartitionList.end(); ++itSlice) {
511 StringPartition lStringPartition = *itSlice;
512 const std::string& lTravelQuerySlice = lStringPartition.getInitialString();
513
519 ResultCombination& lResultCombination =
520 FacResultCombination::instance().create (lTravelQuerySlice);
521
522 // DEBUG
523 OPENTREP_LOG_DEBUG ("+++++++++++++++++++++");
524 OPENTREP_LOG_DEBUG ("Travel query slice: `" << lTravelQuerySlice << "'");
525 OPENTREP_LOG_DEBUG ("Partitions: " << lStringPartition);
526
527
532 WordList_T lCodeList;
533 const bool areAllWordsCodes =
534 areAllCodeOrGeoID (lTravelQuerySlice, lCodeList);
535
536 NbOfMatches_T lNbOfMatches = 0;
537 if (areAllWordsCodes == true && !(iSQLDBType == DBType::NODB)) {
544 // DEBUG
545 OPENTREP_LOG_DEBUG ("The travel query string (" << lTravelQuerySlice
546 << ") is made only of IATA/ICAO/UNLOCODE codes "
547 << "or Geonames ID. The " << iSQLDBType.describe()
548 << " SQL database (" << iSQLDBConnStr
549 << ") will be used. "
550 << "The Xapian database/index will not be used");
551
552 lNbOfMatches = getLocationList (iSQLDBType, iSQLDBConnStr, lCodeList,
553 ioLocationList, ioWordList);
554 }
555
556 if (lNbOfMatches == 0) {
567 // DEBUG
568 if (iSQLDBType == DBType::NODB) {
569 OPENTREP_LOG_DEBUG ("No SQL database may be used. "
570 << "The Xapian database will be used instead");
571 } else {
572 OPENTREP_LOG_DEBUG ("The travel query string (" << lTravelQuerySlice
573 << ") has got items/words, which are neither "
574 << "IATA/ICAO codes nor Geonames ID. "
575 << "The Xapian database/index will be used");
576 }
577
582 OPENTREP::searchString (lTravelQuerySlice, lXapianDatabase,
583 lResultCombination, ioWordList, lNegativeQuery);
584
588 lResultCombination.calculateAllWeights();
589
593 OPENTREP::chooseBestMatchingResultHolder (lResultCombination);
594
600 // Create a PlaceHolder object, to collect the matching Place objects
601 PlaceHolder& lPlaceHolder = FacPlaceHolder::instance().create();
602 createPlaces (lResultCombination, lPlaceHolder);
603
604 // DEBUG
605 OPENTREP_LOG_DEBUG (std::endl
606 << "========================================="
607 << std::endl << "Summary:" << std::endl
608 << lPlaceHolder.toShortString() << std::endl
609 << "========================================="
610 << std::endl);
611
616 lPlaceHolder.createLocations (ioLocationList);
617 }
618 }
619
620 oNbOfMatches = ioLocationList.size();
621 return oNbOfMatches;
622 }
623
624}
#define OPENTREP_LOG_ERROR(iToBeLogged)
Definition Logger.hpp:24
#define OPENTREP_LOG_DEBUG(iToBeLogged)
Definition Logger.hpp:33
static NbOfDBEntries_T getPORByICAOCode(soci::session &, const ICAOCode_T &, LocationList_T &)
static soci::session * initSQLDBSession(const DBType &, const SQLDBConnectionString_T &)
static NbOfDBEntries_T getPORByUNLOCode(soci::session &, const UNLOCode_T &, LocationList_T &, const bool iUniqueEntry)
static NbOfDBEntries_T getPORByGeonameID(soci::session &, const GeonamesID_T &, LocationList_T &)
static NbOfDBEntries_T getPORByIATACode(soci::session &, const IATACode_T &, LocationList_T &, const bool iUniqueEntry)
static FacPlaceHolder & instance()
static void initLinkWithPlace(PlaceHolder &, Place &)
static FacPlace & instance()
Definition FacPlace.cpp:29
static void initLinkWithResultHolder(ResultCombination &, ResultHolder &)
static FacResultCombination & instance()
ResultCombination & create(const TravelQuery_T &iQueryString)
static void initLinkWithResult(ResultHolder &, Result &)
static FacResultHolder & instance()
ResultHolder & create(const TravelQuery_T &iQueryString, const Xapian::Database &iDatabase)
Result & create(const TravelQuery_T &, const Xapian::Database &)
Definition FacResult.cpp:41
static FacResult & instance()
Definition FacResult.cpp:29
FileNotFoundException(const std::string &iWhat)
Class modelling a place/POR (point of reference).
Definition Place.hpp:29
std::string toString() const
Definition Place.cpp:85
Class wrapping functions on a list of ResultHolder objects.
const ResultHolder & getBestMatchingResultHolder() const
StringSet getCorrectedStringSet() const
const Percentage_T & getBestMatchingWeight() const
std::string describeShortKey() const
Class wrapping functions on a list of Result objects.
std::string describeShortKey() const
const ResultList_T & getResultList() const
std::string toString() const
Class wrapping a set of Xapian documents having matched a given query string.
Definition Result.hpp:48
bool hasFullTextMatched() const
Definition Result.hpp:71
const RawDataString_T & getBestDocData() const
Definition Result.hpp:132
void fillPlace(Place &) const
Definition Result.cpp:211
static Location retrieveLocation(const Xapian::Document &)
Definition Result.cpp:272
std::string fullTextMatch(const Xapian::Database &, const TravelQuery_T &, const std::string &="")
Definition Result.cpp:533
static void tokeniseStringIntoWordList(const TravelQuery_T &, WordList_T &)
XapianException(const std::string &iWhat)
std::list< Word_T > WordList_T
void addUnmatchedWord(const TravelQuery_T &iQueryString, WordList_T &ioWordList, WordSet_T &ioWordSet)
void chooseBestMatchingResultHolder(ResultCombination &ioResultCombination)
unsigned int NbOfDBEntries_T
std::string TravelQuery_T
double PageRank_T
std::list< Location > LocationList_T
void createPlaces(const ResultCombination &iResultCombination, PlaceHolder &ioPlaceHolder)
NbOfMatches_T getLocationList(const DBType &iSQLDBType, const SQLDBConnectionString_T &iSQLDBConnStr, const WordList_T &iCodeList, LocationList_T &ioLocationList, WordList_T &ioWordList)
Location getBestMatchingLocation(const LocationList_T &iLocationList)
std::set< std::string > WordSet_T
unsigned short NbOfMatches_T
unsigned int GeonamesID_T
void searchString(const StringPartition &iStringPartition, const Xapian::Database &iDatabase, ResultCombination &ioResultCombination, WordList_T &ioWordList, const std::string &iNegativeQuery="")
std::list< Result * > ResultList_T
std::list< StringPartition > StringPartitionList_T
std::vector< std::string > WordList_T
Enumeration of database types.
Definition DBType.hpp:17
const std::string describe() const
Definition DBType.cpp:135
static bool shouldKeep(const std::string &iPhrase, const std::string &iWord)
Definition Filter.cpp:144
Structure modelling a (geographical) location.
Definition Location.hpp:25
const PageRank_T & getPageRank() const
Definition Location.hpp:354
RawDataString_T(const std::string &iValue)
Class holding a set of strings, e.g., {"rio", "de", "janeiro"}.
Definition StringSet.hpp:19
std::string describe() const
Definition StringSet.cpp:88