File indexing completed on 2024-12-01 06:33:14
0001 /* 0002 This file is part of Kiten, a KDE Japanese Reference Tool 0003 SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com> 0004 SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 /* 0010 TODO: Add features to limit the number of hits on a per-search basis. 0011 0012 Add a mechanism (either through subclassing, or directly) for use 0013 for marking "requested" fields for the dcop system. 0014 */ 0015 0016 #include "dictquery.h" 0017 0018 #include <QDebug> 0019 0020 #include <QString> 0021 0022 class DictQuery::Private 0023 { 0024 public: 0025 Private() 0026 : matchType(DictQuery::Exact) 0027 , matchWordType(DictQuery::Any) 0028 , filterType(DictQuery::NoFilter) 0029 { 0030 } 0031 0032 /** Stores the (english or otherwise non-japanese) meaning */ 0033 QString meaning; 0034 /** Stores the pronunciation in kana */ 0035 QString pronunciation; 0036 /** The main word, this usually contains kanji */ 0037 QString word; 0038 /** Any amount of extended attributes, grade level, heisig/henshall/etc index numbers, whatever you want */ 0039 QHash<QString, QString> extendedAttributes; 0040 /** The order that various attributes, meanings, and pronunciations were entered, so we can 0041 * regenerate the list for the user if they need them again */ 0042 QStringList entryOrder; 0043 /** A list of dictionaries to limit the search to, and empty list implies "all loaded dictionaries" */ 0044 QStringList targetDictionaries; 0045 /** What MatchType is this set to */ 0046 MatchType matchType; 0047 /** What MatchWordType is this set to */ 0048 MatchWordType matchWordType; 0049 /** What FilterType is this set to */ 0050 FilterType filterType; 0051 0052 /** Marker in the m_entryOrder for the location of the pronunciation element */ 0053 static const QString pronunciationMarker; 0054 /** Marker in the m_entryOrder for the location of the translated meaning element */ 0055 static const QString meaningMarker; 0056 /** Marker in the m_entryOrder for the location of the word (kanji) element */ 0057 static const QString wordMarker; 0058 }; 0059 0060 const QString DictQuery::Private::pronunciationMarker(QStringLiteral("__@\\p")); 0061 const QString DictQuery::Private::meaningMarker(QStringLiteral("__@\\m")); 0062 const QString DictQuery::Private::wordMarker(QStringLiteral("_@\\w")); 0063 0064 /***************************************************************************** 0065 * Constructors, Destructors, Initializers, and 0066 * Global Status Indicators. 0067 *****************************************************************************/ 0068 DictQuery::DictQuery() 0069 : d(new Private) 0070 { 0071 } 0072 0073 DictQuery::DictQuery(const QString &str) 0074 : d(new Private) 0075 { 0076 this->operator=((QString)str); 0077 } 0078 0079 DictQuery::DictQuery(const DictQuery &orig) 0080 : d(new Private) 0081 { 0082 this->operator=((DictQuery &)orig); 0083 } 0084 0085 DictQuery *DictQuery::clone() const 0086 { 0087 return new DictQuery(*this); 0088 } 0089 0090 DictQuery::operator QString() const 0091 { 0092 // kDebug() << "DictQuery toString operator called!"; 0093 return toString(); 0094 } 0095 0096 DictQuery::~DictQuery() 0097 { 0098 delete d; 0099 } 0100 0101 bool DictQuery::isEmpty() const 0102 { 0103 // We're only empty if the two strings are empty too 0104 return d->extendedAttributes.isEmpty() && d->meaning.isEmpty() && d->pronunciation.isEmpty() && d->word.isEmpty(); 0105 } 0106 0107 void DictQuery::clear() 0108 { 0109 d->extendedAttributes.clear(); 0110 d->meaning = QLatin1String(""); 0111 d->pronunciation = QLatin1String(""); 0112 d->word = QLatin1String(""); 0113 d->entryOrder.clear(); 0114 } 0115 0116 /***************************************************************************** 0117 * Methods that involve multiple instances of the class 0118 * (comparison, copy etc) 0119 *****************************************************************************/ 0120 DictQuery &DictQuery::operator=(const DictQuery &old) 0121 { 0122 if (&old == this) { 0123 return *this; 0124 } 0125 0126 clear(); 0127 d->matchType = old.d->matchType; 0128 d->matchWordType = old.d->matchWordType; 0129 d->filterType = old.d->filterType; 0130 d->extendedAttributes = old.d->extendedAttributes; 0131 d->meaning = old.d->meaning; 0132 d->pronunciation = old.d->pronunciation; 0133 d->word = old.d->word; 0134 d->entryOrder = old.d->entryOrder; 0135 return *this; 0136 } 0137 0138 DictQuery &DictQuery::operator+=(const DictQuery &old) 0139 { 0140 for (const QString &item : old.d->entryOrder) { 0141 if (item == d->meaningMarker) { 0142 if (d->entryOrder.removeAll(d->meaningMarker) > 0) { 0143 setMeaning(getMeaning() + mainDelimiter + old.getMeaning()); 0144 } else { 0145 setMeaning(old.getMeaning()); 0146 } 0147 } else if (item == d->pronunciationMarker) { 0148 if (d->entryOrder.removeAll(d->pronunciationMarker) > 0) { 0149 setPronunciation(getPronunciation() + mainDelimiter + old.getPronunciation()); 0150 } else { 0151 setPronunciation(old.getPronunciation()); 0152 } 0153 } else if (item == d->wordMarker) { 0154 d->entryOrder.removeAll(d->wordMarker); 0155 // Only one of these allowed 0156 setWord(old.getWord()); 0157 } else { 0158 setProperty(item, old.getProperty(item)); 0159 } 0160 } 0161 0162 return *this; 0163 } 0164 0165 DictQuery operator+(const DictQuery &a, const DictQuery &b) 0166 { 0167 DictQuery val(a); 0168 val += b; 0169 return val; 0170 } 0171 0172 bool operator==(const DictQuery &a, const DictQuery &b) 0173 { 0174 if ((a.d->pronunciation != b.d->pronunciation) || (a.d->meaning != b.d->meaning) || (a.d->word != b.d->word) || (a.d->entryOrder != b.d->entryOrder) 0175 || (a.d->extendedAttributes != b.d->extendedAttributes) || (a.d->matchType != b.d->matchType) || (a.d->matchWordType != b.d->matchWordType) 0176 || (a.d->filterType != b.d->filterType)) { 0177 return false; 0178 } 0179 0180 return true; 0181 } 0182 0183 bool operator!=(const DictQuery &a, const DictQuery &b) 0184 { 0185 return !(a == b); 0186 } 0187 0188 bool operator<(const DictQuery &a, const DictQuery &b) 0189 { 0190 QHash<QString, QString>::const_iterator it = a.d->extendedAttributes.constBegin(); 0191 QHash<QString, QString>::const_iterator it_end = a.d->extendedAttributes.constEnd(); 0192 for (; it != it_end; ++it) { 0193 QString B_version = b.d->extendedAttributes.value(it.key()); 0194 if (a.d->extendedAttributes[it.key()] != B_version) { 0195 if (!B_version.contains(QLatin1Char(',')) && !B_version.contains(QLatin1Char('-'))) { 0196 return false; 0197 } 0198 // TODO: check for multi-values or ranges in DictQuery operator< 0199 } 0200 } 0201 0202 if (!a.d->pronunciation.isEmpty()) { 0203 QStringList aList = a.d->pronunciation.split(DictQuery::mainDelimiter); 0204 QStringList bList = b.d->pronunciation.split(DictQuery::mainDelimiter); 0205 for (const QString &str : aList) { 0206 if (bList.contains(str) == 0) { 0207 return false; 0208 } 0209 } 0210 } 0211 0212 if (!a.d->meaning.isEmpty()) { 0213 QStringList aList = a.d->meaning.split(DictQuery::mainDelimiter); 0214 QStringList bList = b.d->meaning.split(DictQuery::mainDelimiter); 0215 for (const QString &str : aList) { 0216 if (bList.contains(str) == 0) { 0217 return false; 0218 } 0219 } 0220 } 0221 0222 // Assume only one entry for word 0223 if (!a.d->word.isEmpty()) { 0224 if (a.d->word != b.d->word) { 0225 return false; 0226 } 0227 } 0228 0229 return true; 0230 } 0231 0232 /***************************************************************************** 0233 * Methods to extract from QStrings and recreate QStrings 0234 * 0235 *****************************************************************************/ 0236 const QString DictQuery::toString() const 0237 { 0238 if (isEmpty()) { 0239 return QString(); 0240 } 0241 0242 QString reply; 0243 for (const QString &it : d->entryOrder) { 0244 if (it == d->pronunciationMarker) { 0245 reply += d->pronunciation + mainDelimiter; 0246 } else if (it == d->meaningMarker) { 0247 reply += d->meaning + mainDelimiter; 0248 } else if (it == d->wordMarker) { 0249 reply += d->word + mainDelimiter; 0250 } else { 0251 reply += it + propertySeperator + d->extendedAttributes.value(it) + mainDelimiter; 0252 } 0253 } 0254 reply.truncate(reply.length() - mainDelimiter.length()); 0255 0256 return reply; 0257 } 0258 0259 DictQuery &DictQuery::operator=(const QString &str) 0260 { 0261 QStringList parts = str.split(mainDelimiter); 0262 DictQuery result; 0263 if (str.length() > 0) { 0264 for (const QString &it : parts) { 0265 if (it.contains(propertySeperator)) { 0266 QStringList prop = it.split(propertySeperator); 0267 if (prop.count() != 2) { 0268 break; 0269 } 0270 result.setProperty(prop[0], prop[1]); 0271 // replace or throw an error with duplicates? 0272 } else { 0273 switch (stringTypeCheck(it)) { 0274 case DictQuery::Latin: 0275 if (result.d->entryOrder.removeAll(d->meaningMarker) > 0) { 0276 result.setMeaning(result.getMeaning() + mainDelimiter + it); 0277 } else { 0278 result.setMeaning(it); 0279 } 0280 break; 0281 0282 case DictQuery::Kana: 0283 if (result.d->entryOrder.removeAll(d->pronunciationMarker) > 0) { 0284 result.setPronunciation(result.getPronunciation() + mainDelimiter + it); 0285 } else { 0286 result.setPronunciation(it); 0287 } 0288 break; 0289 0290 case DictQuery::Kanji: 0291 result.d->entryOrder.removeAll(d->wordMarker); 0292 result.setWord(it); // Only one of these allowed 0293 break; 0294 0295 case DictQuery::Mixed: 0296 qWarning() << "DictQuery: String parsing error - mixed type"; 0297 break; 0298 0299 case DictQuery::ParseError: 0300 qWarning() << "DictQuery: String parsing error"; 0301 break; 0302 } 0303 } 0304 } 0305 } 0306 // kDebug() << "Query: ("<<result.getWord() << ") ["<<result.getPronunciation()<<"] :"<< 0307 // result.getMeaning()<<endl; 0308 this->operator=(result); 0309 return *this; 0310 } 0311 0312 /** 0313 * Private utility method for the above... confirms that an entire string 0314 * is either completely japanese or completely english 0315 */ 0316 DictQuery::StringTypeEnum DictQuery::stringTypeCheck(const QString &in) 0317 { 0318 StringTypeEnum firstType; 0319 // Split into individual characters 0320 if (in.size() <= 0) { 0321 return DictQuery::ParseError; 0322 } 0323 0324 firstType = charTypeCheck(in.at(0)); 0325 for (int i = 1; i < in.size(); i++) { 0326 StringTypeEnum newType = charTypeCheck(in.at(i)); 0327 if (newType != firstType) { 0328 if (firstType == Kana && newType == Kanji) { 0329 firstType = Kanji; 0330 } else if (firstType == Kanji && newType == Kana) 0331 ; // That's okay 0332 else { 0333 return DictQuery::Mixed; 0334 } 0335 } 0336 } 0337 0338 return firstType; 0339 } 0340 0341 /** 0342 * Private utility method for the stringTypeCheck 0343 * Just checks and returns the type of the first character in the string 0344 * that is passed to it. 0345 */ 0346 DictQuery::StringTypeEnum DictQuery::charTypeCheck(const QChar &ch) 0347 { 0348 if (ch.toLatin1()) { 0349 return Latin; 0350 } 0351 // The unicode character boundaries are: 0352 // 3040 - 309F Hiragana 0353 // 30A0 - 30FF Katakana 0354 // 31F0 - 31FF Katakana phonetic expressions (wtf?) 0355 if (0x3040 <= ch.unicode() && ch.unicode() <= 0x30FF /*|| ch.unicode() & 0x31F0*/) { 0356 return Kana; 0357 } 0358 0359 return Kanji; 0360 } 0361 0362 /***************************************************************************** 0363 * An array of Property List accessors and mutators 0364 * 0365 *****************************************************************************/ 0366 QString DictQuery::getProperty(const QString &key) const 0367 { 0368 return (*this)[key]; 0369 } 0370 0371 const QList<QString> DictQuery::listPropertyKeys() const 0372 { 0373 return d->extendedAttributes.keys(); 0374 } 0375 0376 const QString DictQuery::operator[](const QString &key) const 0377 { 0378 return d->extendedAttributes.value(key); 0379 } 0380 0381 QString DictQuery::operator[](const QString &key) 0382 { 0383 return d->extendedAttributes[key]; 0384 } 0385 0386 bool DictQuery::hasProperty(const QString &key) const 0387 { 0388 return d->entryOrder.contains(key) > 0; 0389 } 0390 0391 // TODO: Add i18n handling and alternate versions of property names 0392 // TODO: further break down the barrier between different types 0393 bool DictQuery::setProperty(const QString &key, const QString &value) 0394 { 0395 if (key == d->pronunciationMarker || key == d->meaningMarker || key.isEmpty() || value.isEmpty()) { 0396 return false; 0397 } 0398 0399 if (!d->extendedAttributes.contains(key)) { 0400 d->entryOrder.append(key); 0401 } 0402 0403 d->extendedAttributes.insert(key, value); 0404 return true; 0405 } 0406 0407 bool DictQuery::removeProperty(const QString &key) 0408 { 0409 if (d->extendedAttributes.contains(key)) { 0410 return d->entryOrder.removeAll(key); 0411 } 0412 return false; 0413 } 0414 0415 QString DictQuery::takeProperty(const QString &key) 0416 { 0417 d->entryOrder.removeAll(key); 0418 return d->extendedAttributes.take(key); 0419 } 0420 0421 /***************************************************************************** 0422 * Meaning and Pronunciation Accessors and Mutators 0423 ****************************************************************************/ 0424 QString DictQuery::getMeaning() const 0425 { 0426 return d->meaning; 0427 } 0428 0429 bool DictQuery::setMeaning(const QString &newMeaning) 0430 { 0431 if (newMeaning.isEmpty()) { 0432 #ifdef USING_QUERY_EXCEPTIONS 0433 throw InvalidQueryException(newMeaning); 0434 #else 0435 return false; 0436 #endif 0437 } 0438 0439 d->meaning = newMeaning; 0440 0441 if (!d->entryOrder.contains(d->meaningMarker)) { 0442 d->entryOrder.append(d->meaningMarker); 0443 } 0444 0445 return true; 0446 } 0447 0448 QString DictQuery::getPronunciation() const 0449 { 0450 return d->pronunciation; 0451 } 0452 0453 bool DictQuery::setPronunciation(const QString &newPronunciation) 0454 { 0455 if (newPronunciation.isEmpty()) { 0456 #ifdef USING_QUERY_EXCEPTIONS 0457 throw InvalidQueryException(newPro); 0458 #else 0459 return false; 0460 #endif 0461 } 0462 0463 d->pronunciation = newPronunciation; 0464 0465 if (!d->entryOrder.contains(d->pronunciationMarker)) { 0466 d->entryOrder.append(d->pronunciationMarker); 0467 } 0468 0469 return true; 0470 } 0471 0472 QString DictQuery::getWord() const 0473 { 0474 return d->word; 0475 } 0476 0477 bool DictQuery::setWord(const QString &newWord) 0478 { 0479 if (newWord.isEmpty()) { 0480 #ifdef USING_QUERY_EXCEPTIONS 0481 throw InvalidQueryException(newWord); 0482 #else 0483 return false; 0484 #endif 0485 } 0486 0487 d->word = newWord; 0488 0489 if (!d->entryOrder.contains(d->wordMarker)) { 0490 d->entryOrder.append(d->wordMarker); 0491 } 0492 0493 return true; 0494 } 0495 0496 /************************************************************* 0497 Handlers for getting and setting dictionary types 0498 *************************************************************/ 0499 QStringList DictQuery::getDictionaries() const 0500 { 0501 return d->targetDictionaries; 0502 } 0503 0504 void DictQuery::setDictionaries(const QStringList &newDictionaries) 0505 { 0506 d->targetDictionaries = newDictionaries; 0507 } 0508 0509 /************************************************************** 0510 Match Type Accessors and Mutators 0511 ************************************************************/ 0512 DictQuery::FilterType DictQuery::getFilterType() const 0513 { 0514 return d->filterType; 0515 } 0516 0517 void DictQuery::setFilterType(FilterType newType) 0518 { 0519 d->filterType = newType; 0520 } 0521 0522 DictQuery::MatchType DictQuery::getMatchType() const 0523 { 0524 return d->matchType; 0525 } 0526 0527 void DictQuery::setMatchType(MatchType newType) 0528 { 0529 d->matchType = newType; 0530 } 0531 0532 DictQuery::MatchWordType DictQuery::getMatchWordType() const 0533 { 0534 return d->matchWordType; 0535 } 0536 0537 void DictQuery::setMatchWordType(MatchWordType newType) 0538 { 0539 d->matchWordType = newType; 0540 } 0541 0542 /************************************************************** 0543 * Aliases to handle different forms of operator arguments 0544 * Disabled at the moment 0545 ************************************************************* 0546 bool operator==( const QString &other, const DictQuery &query ) { 0547 DictQuery x(other); return x == query; 0548 } 0549 bool operator==( const DictQuery &query, const QString &other ) { 0550 return other==query; 0551 } 0552 bool operator!=( const DictQuery &q1, const DictQuery &q2 ) { 0553 return !(q1==q2); 0554 } 0555 bool operator!=( const QString &other, const DictQuery &query ) { 0556 return !(other==query); 0557 } 0558 bool operator!=( const DictQuery &query, const QString &other ) { 0559 return !(query==other); 0560 } 0561 inline bool operator<=( const DictQuery &a, const DictQuery &b) { 0562 return (a<b || a==b); 0563 } 0564 bool operator>=( const DictQuery &a, const DictQuery &b) { 0565 return (b>a || a==b); 0566 } 0567 bool operator>( const DictQuery &a, const DictQuery &b) { 0568 return b < a; 0569 } 0570 DictQuery &operator+( const DictQuery &a, const QString &b) { 0571 return (*(new DictQuery(a))) += b; 0572 } 0573 DictQuery &operator+( const QString &a, const DictQuery &b) { 0574 return (*(new DictQuery(a))) += b; 0575 } 0576 DictQuery &DictQuery::operator+=(const QString &str) { 0577 DictQuery x(str); 0578 return operator+=(x); 0579 } 0580 #ifndef QT_NO_CAST_ASCII 0581 DictQuery &DictQuery::operator=(const char *str) { 0582 QString x(str); 0583 return operator=(x); 0584 } 0585 DictQuery &DictQuery::operator+=(const char *str) { 0586 DictQuery x(str); 0587 return operator+=(x); 0588 } 0589 #endif 0590 */ 0591 /************************************************************** 0592 * Set our constants declared in the class 0593 **************************************************************/ 0594 const QString DictQuery::mainDelimiter(QStringLiteral(" ")); 0595 const QString DictQuery::propertySeperator(QStringLiteral(":"));