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(":"));