File indexing completed on 2024-05-12 16:45:37
0001 /*************************************************************************** 0002 Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "bibtexcollection.h" 0026 #include "../entrycomparison.h" 0027 #include "../utils/bibtexhandler.h" 0028 #include "../fieldformat.h" 0029 #include "../tellico_debug.h" 0030 0031 #include <KLocalizedString> 0032 #include <KStringHandler> 0033 0034 using namespace Tellico; 0035 using Tellico::Data::BibtexCollection; 0036 0037 namespace { 0038 static const char* bibtex_general = I18N_NOOP("General"); 0039 static const char* bibtex_publishing = I18N_NOOP("Publishing"); 0040 static const char* bibtex_misc = I18N_NOOP("Miscellaneous"); 0041 } 0042 0043 BibtexCollection::BibtexCollection(bool addDefaultFields_, const QString& title_) 0044 : Collection(title_.isEmpty() ? i18n("Bibliography") : title_) { 0045 setDefaultGroupField(QStringLiteral("author")); 0046 if(addDefaultFields_) { 0047 addFields(defaultFields()); 0048 } 0049 0050 // Bibtex has some default macros for the months 0051 addMacro(QStringLiteral("jan"), QString()); 0052 addMacro(QStringLiteral("feb"), QString()); 0053 addMacro(QStringLiteral("mar"), QString()); 0054 addMacro(QStringLiteral("apr"), QString()); 0055 addMacro(QStringLiteral("may"), QString()); 0056 addMacro(QStringLiteral("jun"), QString()); 0057 addMacro(QStringLiteral("jul"), QString()); 0058 addMacro(QStringLiteral("aug"), QString()); 0059 addMacro(QStringLiteral("sep"), QString()); 0060 addMacro(QStringLiteral("oct"), QString()); 0061 addMacro(QStringLiteral("nov"), QString()); 0062 addMacro(QStringLiteral("dec"), QString()); 0063 } 0064 0065 Tellico::Data::FieldList BibtexCollection::defaultFields() { 0066 FieldList list; 0067 FieldPtr field; 0068 0069 const QString bibtex = QStringLiteral("bibtex"); 0070 0071 /******************* General ****************************/ 0072 0073 field = Field::createDefaultField(Field::TitleField); 0074 field->setProperty(bibtex, QStringLiteral("title")); 0075 list.append(field); 0076 0077 QStringList types; 0078 types << QStringLiteral("article") << QStringLiteral("book") 0079 << QStringLiteral("booklet") << QStringLiteral("inbook") 0080 << QStringLiteral("incollection") << QStringLiteral("inproceedings") 0081 << QStringLiteral("manual") << QStringLiteral("mastersthesis") 0082 << QStringLiteral("misc") << QStringLiteral("phdthesis") 0083 << QStringLiteral("proceedings") << QStringLiteral("techreport") 0084 << QStringLiteral("unpublished") << QStringLiteral("periodical") 0085 << QStringLiteral("conference"); 0086 field = new Field(QStringLiteral("entry-type"), i18n("Entry Type"), types); 0087 field->setProperty(bibtex, QStringLiteral("entry-type")); 0088 field->setCategory(i18n(bibtex_general)); 0089 field->setFlags(Field::AllowGrouped | Field::NoDelete); 0090 field->setDescription(i18n("These entry types are specific to bibtex. See the bibtex documentation.")); 0091 list.append(field); 0092 0093 field = new Field(QStringLiteral("author"), i18n("Author")); 0094 field->setProperty(bibtex, QStringLiteral("author")); 0095 field->setCategory(i18n(bibtex_general)); 0096 field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped); 0097 field->setFormatType(FieldFormat::FormatName); 0098 list.append(field); 0099 0100 field = new Field(QStringLiteral("bibtex-key"), i18n("Bibtex Key")); 0101 field->setProperty(bibtex, QStringLiteral("key")); 0102 field->setCategory(i18n("General")); 0103 field->setFlags(Field::NoDelete); 0104 list.append(field); 0105 0106 field = new Field(QStringLiteral("booktitle"), i18n("Book Title")); 0107 field->setProperty(bibtex, QStringLiteral("booktitle")); 0108 field->setCategory(i18n(bibtex_general)); 0109 field->setFormatType(FieldFormat::FormatTitle); 0110 list.append(field); 0111 0112 field = new Field(QStringLiteral("editor"), i18n("Editor")); 0113 field->setProperty(bibtex, QStringLiteral("editor")); 0114 field->setCategory(i18n(bibtex_general)); 0115 field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped); 0116 field->setFormatType(FieldFormat::FormatName); 0117 list.append(field); 0118 0119 field = new Field(QStringLiteral("organization"), i18n("Organization")); 0120 field->setProperty(bibtex, QStringLiteral("organization")); 0121 field->setCategory(i18n(bibtex_general)); 0122 field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0123 field->setFormatType(FieldFormat::FormatPlain); 0124 list.append(field); 0125 0126 // field = new Field(QLatin1String("institution"), i18n("Institution")); 0127 // field->setProperty(QLatin1String("bibtex"), QLatin1String("institution")); 0128 // field->setCategory(i18n(bibtex_general)); 0129 // field->setFlags(Field::AllowDelete); 0130 // field->setFormatType(FieldFormat::FormatTitle); 0131 // list.append(field); 0132 0133 /******************* Publishing ****************************/ 0134 field = new Field(QStringLiteral("publisher"), i18n("Publisher")); 0135 field->setProperty(bibtex, QStringLiteral("publisher")); 0136 field->setCategory(i18n(bibtex_publishing)); 0137 field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0138 field->setFormatType(FieldFormat::FormatPlain); 0139 list.append(field); 0140 0141 field = new Field(QStringLiteral("address"), i18n("Address")); 0142 field->setProperty(bibtex, QStringLiteral("address")); 0143 field->setCategory(i18n(bibtex_publishing)); 0144 field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0145 list.append(field); 0146 0147 field = new Field(QStringLiteral("edition"), i18n("Edition")); 0148 field->setProperty(bibtex, QStringLiteral("edition")); 0149 field->setCategory(i18n(bibtex_publishing)); 0150 field->setFlags(Field::AllowCompletion); 0151 list.append(field); 0152 0153 // don't make it a number, it could have latex processing commands in it 0154 field = new Field(QStringLiteral("pages"), i18n("Pages")); 0155 field->setProperty(bibtex, QStringLiteral("pages")); 0156 field->setCategory(i18n(bibtex_publishing)); 0157 list.append(field); 0158 0159 field = new Field(QStringLiteral("year"), i18n("Year"), Field::Number); 0160 field->setProperty(bibtex, QStringLiteral("year")); 0161 field->setCategory(i18n(bibtex_publishing)); 0162 field->setFlags(Field::AllowGrouped); 0163 list.append(field); 0164 0165 field = Field::createDefaultField(Field::IsbnField); 0166 field->setProperty(bibtex, QStringLiteral("isbn")); 0167 field->setCategory(i18n(bibtex_publishing)); 0168 list.append(field); 0169 0170 field = new Field(QStringLiteral("journal"), i18n("Journal")); 0171 field->setProperty(bibtex, QStringLiteral("journal")); 0172 field->setCategory(i18n(bibtex_publishing)); 0173 field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0174 field->setFormatType(FieldFormat::FormatPlain); 0175 list.append(field); 0176 0177 field = new Field(QStringLiteral("doi"), i18n("DOI")); 0178 field->setProperty(bibtex, QStringLiteral("doi")); 0179 field->setCategory(i18n(bibtex_publishing)); 0180 field->setDescription(i18n("Digital Object Identifier")); 0181 list.append(field); 0182 0183 // could make this a string list, but since bibtex import could have funky values 0184 // keep it an editbox 0185 field = new Field(QStringLiteral("month"), i18n("Month")); 0186 field->setProperty(bibtex, QStringLiteral("month")); 0187 field->setCategory(i18n(bibtex_publishing)); 0188 field->setFlags(Field::AllowCompletion); 0189 list.append(field); 0190 0191 field = new Field(QStringLiteral("number"), i18n("Number"), Field::Number); 0192 field->setProperty(bibtex, QStringLiteral("number")); 0193 field->setCategory(i18n(bibtex_publishing)); 0194 list.append(field); 0195 0196 field = new Field(QStringLiteral("howpublished"), i18n("How Published")); 0197 field->setProperty(bibtex, QStringLiteral("howpublished")); 0198 field->setCategory(i18n(bibtex_publishing)); 0199 list.append(field); 0200 0201 // field = new Field(QLatin1String("school"), i18n("School")); 0202 // field->setProperty(QLatin1String("bibtex"), QLatin1String("school")); 0203 // field->setCategory(i18n(bibtex_publishing)); 0204 // field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0205 // list.append(field); 0206 0207 /******************* Classification ****************************/ 0208 field = new Field(QStringLiteral("chapter"), i18n("Chapter"), Field::Number); 0209 field->setProperty(bibtex, QStringLiteral("chapter")); 0210 field->setCategory(i18n(bibtex_misc)); 0211 list.append(field); 0212 0213 field = new Field(QStringLiteral("series"), i18n("Series")); 0214 field->setProperty(bibtex, QStringLiteral("series")); 0215 field->setCategory(i18n(bibtex_misc)); 0216 field->setFlags(Field::AllowCompletion | Field::AllowGrouped); 0217 field->setFormatType(FieldFormat::FormatTitle); 0218 list.append(field); 0219 0220 field = new Field(QStringLiteral("volume"), i18nc("A number field in a bibliography", "Volume"), Field::Number); 0221 field->setProperty(bibtex, QStringLiteral("volume")); 0222 field->setCategory(i18n(bibtex_misc)); 0223 list.append(field); 0224 0225 field = new Field(QStringLiteral("crossref"), i18n("Cross-Reference")); 0226 field->setProperty(bibtex, QStringLiteral("crossref")); 0227 field->setCategory(i18n(bibtex_misc)); 0228 list.append(field); 0229 0230 // field = new Field(QLatin1String("annote"), i18n("Annotation")); 0231 // field->setProperty(QLatin1String("bibtex"), QLatin1String("annote")); 0232 // field->setCategory(i18n(bibtex_misc)); 0233 // list.append(field); 0234 0235 field = new Field(QStringLiteral("keyword"), i18n("Keywords")); 0236 field->setProperty(bibtex, QStringLiteral("keywords")); 0237 field->setCategory(i18n(bibtex_misc)); 0238 field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped); 0239 list.append(field); 0240 0241 field = new Field(QStringLiteral("url"), i18n("URL"), Field::URL); 0242 field->setProperty(bibtex, QStringLiteral("url")); 0243 field->setCategory(i18n(bibtex_misc)); 0244 list.append(field); 0245 0246 field = new Field(QStringLiteral("abstract"), i18n("Abstract"), Field::Para); 0247 field->setProperty(bibtex, QStringLiteral("abstract")); 0248 list.append(field); 0249 0250 field = new Field(QStringLiteral("note"), i18n("Notes"), Field::Para); 0251 field->setProperty(bibtex, QStringLiteral("note")); 0252 list.append(field); 0253 0254 field = Field::createDefaultField(Field::IDField); 0255 field->setCategory(i18n(bibtex_misc)); 0256 list.append(field); 0257 0258 field = Field::createDefaultField(Field::CreatedDateField); 0259 field->setCategory(i18n(bibtex_misc)); 0260 list.append(field); 0261 0262 field = Field::createDefaultField(Field::ModifiedDateField); 0263 field->setCategory(i18n(bibtex_misc)); 0264 list.append(field); 0265 0266 return list; 0267 } 0268 0269 bool BibtexCollection::addField(Tellico::Data::FieldPtr field_) { 0270 if(!field_) { 0271 return false; 0272 } 0273 bool success = Collection::addField(field_); 0274 if(success) { 0275 const QString bibtex = field_->property(QStringLiteral("bibtex")); 0276 if(!bibtex.isEmpty()) { 0277 m_bibtexFieldDict.insert(bibtex, field_.data()); 0278 } 0279 } 0280 return success; 0281 } 0282 0283 bool BibtexCollection::modifyField(Tellico::Data::FieldPtr newField_) { 0284 if(!newField_) { 0285 return false; 0286 } 0287 // myDebug(); 0288 const QString bibtex = QStringLiteral("bibtex"); 0289 bool success = Collection::modifyField(newField_); 0290 FieldPtr oldField = fieldByName(newField_->name()); 0291 const QString oldBibtex = oldField->property(bibtex); 0292 const QString newBibtex = newField_->property(bibtex); 0293 if(!oldBibtex.isEmpty()) { 0294 success &= (m_bibtexFieldDict.remove(oldBibtex) != 0); 0295 } 0296 if(!newBibtex.isEmpty()) { 0297 oldField->setProperty(bibtex, newBibtex); 0298 m_bibtexFieldDict.insert(newBibtex, oldField.data()); 0299 } 0300 return success; 0301 } 0302 0303 bool BibtexCollection::removeField(Tellico::Data::FieldPtr field_, bool force_) { 0304 if(!field_) { 0305 return false; 0306 } 0307 // myDebug(); 0308 bool success = true; 0309 const QString bibtex = field_->property(QStringLiteral("bibtex")); 0310 if(!bibtex.isEmpty()) { 0311 success &= (m_bibtexFieldDict.remove(bibtex) != 0); 0312 } 0313 return success && Collection::removeField(field_, force_); 0314 } 0315 0316 bool BibtexCollection::removeField(const QString& name_, bool force_) { 0317 return removeField(fieldByName(name_), force_); 0318 } 0319 0320 Tellico::Data::FieldPtr BibtexCollection::fieldByBibtexName(const QString& bibtex_) const { 0321 return FieldPtr(m_bibtexFieldDict.contains(bibtex_) ? m_bibtexFieldDict.value(bibtex_) : nullptr); 0322 } 0323 0324 Tellico::Data::EntryPtr BibtexCollection::entryByBibtexKey(const QString& key_) const { 0325 EntryPtr entry; 0326 // we do assume unique keys 0327 foreach(EntryPtr e, entries()) { 0328 if(e->field(QStringLiteral("bibtex-key")) == key_) { 0329 entry = e; 0330 break; 0331 } 0332 } 0333 return entry; 0334 } 0335 0336 QString BibtexCollection::prepareText(const QString& text_) const { 0337 QString text = text_; 0338 BibtexHandler::cleanText(text); 0339 return text; 0340 } 0341 0342 // same as BookCollection::sameEntry() 0343 int BibtexCollection::sameEntry(Tellico::Data::EntryPtr entry1_, Tellico::Data::EntryPtr entry2_) const { 0344 // equal identifiers are easy, give it a weight of 100 0345 if(EntryComparison::score(entry1_, entry2_, QStringLiteral("isbn"), this) > 0 || 0346 EntryComparison::score(entry1_, entry2_, QStringLiteral("lccn"), this) > 0 || 0347 EntryComparison::score(entry1_, entry2_, QStringLiteral("doi"), this) > 0 || 0348 EntryComparison::score(entry1_, entry2_, QStringLiteral("pmid"), this) > 0 || 0349 EntryComparison::score(entry1_, entry2_, QStringLiteral("arxiv"), this) > 0) { 0350 return 100; // good match 0351 } 0352 int res = 3*EntryComparison::score(entry1_, entry2_, QStringLiteral("title"), this); 0353 // if(res == 0) { 0354 // myDebug() << "different titles for " << entry1_->title() << " vs. " 0355 // << entry2_->title(); 0356 // } 0357 res += EntryComparison::score(entry1_, entry2_, QStringLiteral("author"), this); 0358 res += EntryComparison::score(entry1_, entry2_, QStringLiteral("cr_year"), this); 0359 res += EntryComparison::score(entry1_, entry2_, QStringLiteral("pub_year"), this); 0360 res += EntryComparison::score(entry1_, entry2_, QStringLiteral("binding"), this); 0361 return res; 0362 } 0363 0364 // static 0365 Tellico::Data::CollPtr BibtexCollection::convertBookCollection(Tellico::Data::CollPtr coll_) { 0366 const QString bibtex = QStringLiteral("bibtex"); 0367 BibtexCollection* coll = new BibtexCollection(false, coll_->title()); 0368 CollPtr collPtr(coll); 0369 FieldList fields = coll_->fields(); 0370 foreach(FieldPtr fIt, fields) { 0371 FieldPtr field(new Field(*fIt)); 0372 0373 // if it already has a bibtex property, skip it 0374 if(!field->property(bibtex).isEmpty()) { 0375 coll->addField(field); 0376 continue; 0377 } 0378 0379 // be sure to set bibtex property before adding it though 0380 QString name = field->name(); 0381 // this first group has bibtex field names the same as their own field name 0382 if(name == QLatin1String("title") 0383 || name == QLatin1String("author") 0384 || name == QLatin1String("editor") 0385 || name == QLatin1String("edition") 0386 || name == QLatin1String("publisher") 0387 || name == QLatin1String("isbn") 0388 || name == QLatin1String("lccn") 0389 || name == QLatin1String("url") 0390 || name == QLatin1String("language") 0391 || name == QLatin1String("pages") 0392 || name == QLatin1String("series")) { 0393 field->setProperty(bibtex, name); 0394 } else if(name == QLatin1String("series_num")) { 0395 field->setProperty(bibtex, QStringLiteral("number")); 0396 } else if(name == QLatin1String("pur_price")) { 0397 field->setProperty(bibtex, QStringLiteral("price")); 0398 } else if(name == QLatin1String("cr_year")) { 0399 field->setProperty(bibtex, QStringLiteral("year")); 0400 } else if(name == QLatin1String("bibtex-id")) { 0401 field->setProperty(bibtex, QStringLiteral("key")); 0402 } else if(name == QLatin1String("keyword")) { 0403 field->setProperty(bibtex, QStringLiteral("keywords")); 0404 } else if(name == QLatin1String("comments")) { 0405 field->setProperty(bibtex, QStringLiteral("note")); 0406 } 0407 coll->addField(field); 0408 } 0409 0410 // also need to add required fields, those with NoDelete set 0411 foreach(FieldPtr defaultField, coll->defaultFields()) { 0412 if(!coll->hasField(defaultField->name()) && defaultField->hasFlag(Field::NoDelete)) { 0413 // but don't add a Bibtex Key if there's already a bibtex-id 0414 if(defaultField->property(bibtex) != QLatin1String("key") 0415 || !coll->hasField(QStringLiteral("bibtex-id"))) { 0416 coll->addField(defaultField); 0417 } 0418 } 0419 } 0420 0421 // set the entry-type to book 0422 FieldPtr field = coll->fieldByBibtexName(QStringLiteral("entry-type")); 0423 QString entryTypeName; 0424 if(field) { 0425 entryTypeName = field->name(); 0426 } else { 0427 myWarning() << "there must be an entry type field"; 0428 } 0429 0430 EntryList newEntries; 0431 foreach(EntryPtr entry, coll_->entries()) { 0432 EntryPtr newEntry(new Entry(*entry)); 0433 newEntry->setCollection(collPtr); 0434 if(!entryTypeName.isEmpty()) { 0435 newEntry->setField(entryTypeName, QStringLiteral("book")); 0436 } 0437 newEntries.append(newEntry); 0438 } 0439 coll->addEntries(newEntries); 0440 0441 return collPtr; 0442 } 0443 0444 bool BibtexCollection::setFieldValue(Data::EntryPtr entry_, const QString& bibtexField_, const QString& value_, Data::CollPtr existingColl_) { 0445 Q_ASSERT(entry_->collection()->type() == Collection::Bibtex); 0446 BibtexCollection* c = static_cast<BibtexCollection*>(entry_->collection().data()); 0447 FieldPtr field = c->fieldByBibtexName(bibtexField_); 0448 // special-case: "keyword" and "keywords" should be the same field. 0449 if(!field && bibtexField_ == QLatin1String("keyword")) { 0450 field = c->fieldByBibtexName(QStringLiteral("keywords")); 0451 } 0452 if(!field) { 0453 // it was the case that the default bibliography did not have a bibtex property for keywords 0454 // so a "keywords" field would get created in the imported collection 0455 // but the existing collection had a field "keyword" so the values would not get imported 0456 // here, check to see if the current collection has a field with the same bibtex name and 0457 // use it instead of creating a new one 0458 BibtexCollection* existingColl = dynamic_cast<BibtexCollection*>(existingColl_.data()); 0459 FieldPtr existingField; 0460 if(existingColl && existingColl->type() == Collection::Bibtex) { 0461 existingField = existingColl->fieldByBibtexName(bibtexField_); 0462 } 0463 if(existingField) { 0464 field = new Field(*existingField); 0465 } else if(value_.length() < 100) { 0466 // arbitrarily say if the value has more than 100 chars, then it's a paragraph 0467 QString vlower = value_.toLower(); 0468 // special case, try to detect URLs 0469 if(bibtexField_ == QLatin1String("url") 0470 || vlower.startsWith(QLatin1String("http")) // may also be https 0471 || vlower.startsWith(QLatin1String("ftp:/")) 0472 || vlower.startsWith(QLatin1String("file:/")) 0473 || vlower.startsWith(QLatin1String("/"))) { // assume this indicates a local path 0474 myDebug() << "creating a URL field for" << bibtexField_; 0475 field = new Field(bibtexField_, KStringHandler::capwords(bibtexField_), Field::URL); 0476 } else { 0477 myDebug() << "creating a LINE field for" << bibtexField_; 0478 field = new Field(bibtexField_, KStringHandler::capwords(bibtexField_), Field::Line); 0479 } 0480 field->setCategory(i18n("Unknown")); 0481 } else { 0482 myDebug() << "creating a PARA field for" << bibtexField_; 0483 field = new Field(bibtexField_, KStringHandler::capwords(bibtexField_), Field::Para); 0484 } 0485 field->setProperty(QStringLiteral("bibtex"), bibtexField_); 0486 c->addField(field); 0487 } 0488 // special case keywords, replace commas with semi-colons so they get separated 0489 QString value = value_; 0490 Q_ASSERT(field); 0491 if(bibtexField_.startsWith(QLatin1String("keyword"))) { 0492 value.replace(QRegularExpression(QLatin1String("\\s*,\\s*")), FieldFormat::delimiterString()); 0493 // special case refbase bibtex export, with multiple keywords fields 0494 QString oValue = entry_->field(field); 0495 if(!oValue.isEmpty()) { 0496 value = oValue + FieldFormat::delimiterString() + value; 0497 } 0498 // special case for tilde, since it's a non-breaking space in LateX 0499 // replace it EXCEPT for URL or DOI fields 0500 } else if(bibtexField_ != QLatin1String("doi") && field->type() != Field::URL) { 0501 value.replace(QLatin1Char('~'), QChar(0xA0)); 0502 } else if(field->type() == Field::URL || bibtexField_ == QLatin1String("url")) { 0503 // special case for url package 0504 if(value.startsWith(QLatin1String("\\url{")) && value.endsWith(QLatin1Char('}'))) { 0505 value.remove(0, 5).chop(1); 0506 } 0507 } 0508 return entry_->setField(field, value); 0509 } 0510 0511 Tellico::Data::EntryList BibtexCollection::duplicateBibtexKeys() const { 0512 QSet<EntryPtr> dupes; 0513 QHash<QString, EntryPtr> keyHash; 0514 0515 const QString keyField = QStringLiteral("bibtex-key"); 0516 QString keyValue; 0517 foreach(EntryPtr entry, entries()) { 0518 keyValue = entry->field(keyField); 0519 if(keyHash.contains(keyValue)) { 0520 dupes << keyHash.value(keyValue) << entry; 0521 } else { 0522 keyHash.insert(keyValue, entry); 0523 } 0524 } 0525 return dupes.values(); 0526 }