File indexing completed on 2024-12-08 03:29:07

0001 /*
0002     This file is part of Kiten, a KDE Japanese Reference Tool
0003     SPDX-FileCopyrightText: 2001 Jason Katz-Brown <jason@katzbrown.com>
0004     SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com>
0005     SPDX-FileCopyrightText: 2006 Eric Kjeldergaard <kjelderg@gmail.com>
0006     SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "entry.h"
0012 
0013 #include <KLocalizedString>
0014 
0015 using namespace Qt::StringLiterals;
0016 
0017 /**
0018  * The default constructor, unless you really know what you're doing,
0019  * THIS SHOULD NOT BE USED. For general use, other entities will need
0020  * to have the information provided by the other constructors
0021  * (particularly the sourceDictionary).
0022  */
0023 Entry::Entry()
0024 {
0025     init();
0026 }
0027 
0028 Entry::Entry(const QString &sourceDictionary)
0029     : sourceDict(sourceDictionary)
0030 {
0031     init();
0032 }
0033 
0034 Entry::Entry(const QString &sourceDictionary, const QString &word, const QStringList &reading, const QStringList &meanings)
0035     : Word(word)
0036     , Meanings(meanings)
0037     , Readings(reading)
0038     , sourceDict(sourceDictionary)
0039 {
0040     init();
0041 }
0042 
0043 Entry::Entry(const Entry &src)
0044     : Word(src.Word)
0045     , Meanings(src.Meanings)
0046     , Readings(src.Readings)
0047     , ExtendedInfo(src.ExtendedInfo)
0048     , sourceDict(src.sourceDict)
0049 {
0050     outputListDelimiter = src.outputListDelimiter;
0051 }
0052 
0053 bool Entry::extendedItemCheck(const QString &key, const QString &value) const
0054 {
0055     return getExtendedInfoItem(key) == value;
0056 }
0057 
0058 /**
0059  * Get the dictionary name that generated this Entry. I can't think of a reason to be changing this
0060  */
0061 QString Entry::getDictName() const
0062 {
0063     return sourceDict;
0064 }
0065 
0066 /**
0067  * Get the word from this Entry. If the entry is of type kanji/kana/meaning/etc, this will return
0068  * the kanji. If it is of kana/meaning/etc, it will return kana.
0069  */
0070 QString Entry::getWord() const
0071 {
0072     return Word;
0073 }
0074 
0075 /**
0076  * Get a QString containing all of the meanings known, connected by the outputListDelimiter
0077  */
0078 QString Entry::getMeanings() const
0079 {
0080     return Meanings.join(outputListDelimiter);
0081 }
0082 
0083 /**
0084  * Simple accessor
0085  */
0086 QStringList Entry::getMeaningsList() const
0087 {
0088     return Meanings;
0089 }
0090 
0091 /**
0092  * Simple accessor
0093  */
0094 QString Entry::getReadings() const
0095 {
0096     return Readings.join(outputListDelimiter);
0097 }
0098 
0099 /**
0100  * Simple accessor
0101  */
0102 QStringList Entry::getReadingsList() const
0103 {
0104     return Readings;
0105 }
0106 
0107 /**
0108  * Simple accessor
0109  */
0110 QHash<QString, QString> Entry::getExtendedInfo() const
0111 {
0112     return ExtendedInfo;
0113 }
0114 
0115 /**
0116  * Simple accessor
0117  *
0118  * @param x the key for the extended info item to get
0119  */
0120 QString Entry::getExtendedInfoItem(const QString &x) const
0121 {
0122     return ExtendedInfo[x];
0123 }
0124 
0125 /**
0126  * Prepares Meanings for output as HTML
0127  */
0128 inline QString Entry::HTMLMeanings() const
0129 {
0130     return QStringLiteral("<span class=\"Meanings\">%1</span>").arg(Meanings.join(outputListDelimiter));
0131 }
0132 
0133 /* Prepares Readings for output as HTML */
0134 inline QString Entry::HTMLReadings() const
0135 {
0136     QStringList list;
0137     for (const QString &it : Readings) {
0138         list += makeLink(it);
0139     }
0140 
0141     return QStringLiteral("<span class=\"Readings\">%1</span>").arg(list.join(outputListDelimiter));
0142 }
0143 
0144 /**
0145  * Prepares Word for output as HTML
0146  */
0147 inline QString Entry::HTMLWord() const
0148 {
0149     return QStringLiteral("<span class=\"Word\">%1</span>").arg(Word);
0150 }
0151 
0152 void Entry::init()
0153 {
0154     outputListDelimiter = i18n("; ");
0155 }
0156 
0157 /**
0158  * Determines whether @param character is a kanji character.
0159  */
0160 bool Entry::isKanji(const QChar &character) const
0161 {
0162     ushort value = character.unicode();
0163     if (value < 255) {
0164         return false;
0165     }
0166     if (0x3040 <= value && value <= 0x30FF) {
0167         return false; // Kana
0168     }
0169 
0170     return true; // Note our folly here... we assuming any non-ascii/kana is kanji
0171 }
0172 
0173 /**
0174  * Returns true if all members of test are in list
0175  */
0176 bool Entry::listMatch(const QStringList &list, const QStringList &test, DictQuery::MatchType type) const
0177 {
0178     if (type == DictQuery::Exact) {
0179         for (const QString &it : test) {
0180             if (!list.contains(it)) {
0181                 return false;
0182             }
0183         }
0184     } else if (type == DictQuery::Beginning) {
0185         for (const QString &it : test) {
0186             bool found = false;
0187             for (const QString &it2 : list) {
0188                 if (it2.startsWith(it)) {
0189                     found = true;
0190                     break;
0191                 }
0192             }
0193             if (!found) {
0194                 return false;
0195             }
0196         }
0197     } else if (type == DictQuery::Ending) {
0198         for (const QString &it : test) {
0199             bool found = false;
0200             for (const QString &it2 : list) {
0201                 if (it2.endsWith(it)) {
0202                     found = true;
0203                     break;
0204                 }
0205             }
0206             if (!found) {
0207                 return false;
0208             }
0209         }
0210     } else {
0211         for (const QString &it : test) {
0212             bool found = false;
0213             for (const QString &it2 : list) {
0214                 if (it2.contains(it)) {
0215                     found = true;
0216                     break;
0217                 }
0218             }
0219             if (!found) {
0220                 return false;
0221             }
0222         }
0223     }
0224 
0225     return true;
0226 }
0227 
0228 /**
0229  * New functions for Entry doing direct display.
0230  *
0231  * Creates a link for the given @p entryString.
0232  */
0233 inline QString Entry::makeLink(const QString &entryString) const
0234 {
0235     return QStringLiteral("<a href=\"%1\">%1</a>").arg(entryString);
0236 }
0237 
0238 bool Entry::matchesQuery(const DictQuery &query) const
0239 {
0240     if (!query.getWord().isEmpty()) {
0241         if (query.getMatchType() == DictQuery::Exact && this->getWord() != query.getWord()) {
0242             return false;
0243         }
0244         if (query.getMatchType() == DictQuery::Beginning && !this->getWord().startsWith(query.getWord())) {
0245             return false;
0246         }
0247         if (query.getMatchType() == DictQuery::Ending && !this->getWord().endsWith(query.getWord())) {
0248             return false;
0249         }
0250         if (query.getMatchType() == DictQuery::Anywhere && !this->getWord().contains(query.getWord())) {
0251             return false;
0252         }
0253     }
0254 
0255     if (!query.getPronunciation().isEmpty() && !getReadings().isEmpty()) {
0256         if (!listMatch(Readings, query.getPronunciation().split(DictQuery::mainDelimiter), query.getMatchType())) {
0257             return false;
0258         }
0259     }
0260 
0261     if (!query.getPronunciation().isEmpty() && getReadings().isEmpty() && !getWord().isEmpty()) {
0262         switch (query.getMatchType()) {
0263         case DictQuery::Exact:
0264             if (getWord() != query.getPronunciation()) {
0265                 return false;
0266             }
0267             break;
0268         case DictQuery::Beginning:
0269             if (!getWord().startsWith(query.getPronunciation())) {
0270                 return false;
0271             }
0272             break;
0273         case DictQuery::Ending:
0274             if (!getWord().endsWith(query.getPronunciation())) {
0275                 return false;
0276             } // fallthrough
0277         case DictQuery::Anywhere:
0278             if (!getWord().contains(query.getPronunciation())) {
0279                 return false;
0280             }
0281             break;
0282         }
0283     }
0284 
0285     if (!query.getMeaning().isEmpty()) {
0286         if (!listMatch(Meanings.join(QLatin1Char(' ')).toLower().split(' '_L1),
0287                        query.getMeaning().toLower().split(DictQuery::mainDelimiter),
0288                        query.getMatchType())) {
0289             return false;
0290         }
0291     }
0292 
0293     QList<QString> propList = query.listPropertyKeys();
0294     for (const QString &key : propList) {
0295         if (!extendedItemCheck(key, query.getProperty(key))) {
0296             return false;
0297         }
0298     }
0299 
0300     return true;
0301 }
0302 
0303 /**
0304  * Main switching function for displaying to the user
0305  */
0306 QString Entry::toHTML() const
0307 {
0308     return QStringLiteral("<div class=\"Entry\">%1%2%3</div>").arg(HTMLWord()).arg(HTMLReadings()).arg(HTMLMeanings());
0309 }
0310 
0311 inline QString Entry::toKVTML() const
0312 {
0313     /*
0314 <e m="1" s="1">
0315        <o width="414" l="en" q="t">(eh,) excuse me</o>
0316        <t width="417" l="jp" q="o">(あのう、) すみません </t>
0317 </e>
0318 */
0319     // TODO: en should not necessarily be the language here.
0320     return QStringLiteral(
0321                "<e>\n<o l=\"en\">%1</o>\n"
0322                "<t l=\"jp-kanji\">%2</t>\n"
0323                "<t l=\"jp-kana\">%3</t></e>\n\n")
0324         .arg(getMeanings())
0325         .arg(getWord())
0326         .arg(getReadings());
0327 }
0328 
0329 /**
0330  * This method should return the entry object in a simple QString format
0331  * Brief form should be usable in quick summaries, for example
0332  * Verbose form might be used to save a complete list of entries to a file, for example.
0333  */
0334 QString Entry::toString() const
0335 {
0336     return QStringLiteral("%1 (%2) %3").arg(Word).arg(getReadings()).arg(getMeanings());
0337 }
0338 
0339 /**
0340  * This version of sort only sorts dictionaries...
0341  * This is a replacement for an operator\< function... so we return true if
0342  * "this" should show up first on the list.
0343  */
0344 bool Entry::sort(const Entry &that, const QStringList &dictOrder, const QStringList &fields) const
0345 {
0346     if (this->sourceDict != that.sourceDict) {
0347         for (const QString &dict : dictOrder) {
0348             if (dict == that.sourceDict) {
0349                 return false;
0350             }
0351             if (dict == this->sourceDict) {
0352                 return true;
0353             }
0354         }
0355     } else {
0356         for (const QString &field : fields) {
0357             if (field == QLatin1String("Word/Kanji")) {
0358                 return this->getWord() < that.getWord();
0359             } else if (field == QLatin1String("Meaning")) {
0360                 return listMatch(that.getMeaningsList(), this->getMeaningsList(), DictQuery::Exact)
0361                     && (that.getMeaningsList().count() != this->getMeaningsList().count());
0362             } else if (field == QLatin1String("Reading")) {
0363                 return listMatch(that.getReadingsList(), this->getReadingsList(), DictQuery::Exact)
0364                     && (that.getReadingsList().count() != this->getReadingsList().count());
0365             } else {
0366                 const QString thisOne = this->getExtendedInfoItem(field);
0367                 const QString thatOne = that.getExtendedInfoItem(field);
0368                 // Only sort by this field if the values differ, otherwise move to the next field
0369                 if (thisOne != thatOne) {
0370                     // If the second item does not have this field, sort this one first
0371                     if (thatOne.isEmpty()) {
0372                         return true;
0373                     }
0374                     // If we don't have this field, sort "this" to second
0375                     if (thisOne.isEmpty()) {
0376                         return false;
0377                     }
0378                     // Otherwise, send it to a virtual function (to allow dictionaries to override sorting)
0379                     return this->sortByField(that, field);
0380                 }
0381             }
0382         }
0383     }
0384     return false; // If we reach here, they match as much as possible
0385 }
0386 
0387 bool Entry::sortByField(const Entry &that, const QString &field) const
0388 {
0389     return this->getExtendedInfoItem(field) < that.getExtendedInfoItem(field);
0390 }