File indexing completed on 2024-12-08 06:28:23
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 }